1234567891011121314151617import type {IKeyringPair} from '@polkadot/types/types';18import config from '../config.js';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util/index.js';20import {Event} from '@unique/playgrounds/unique.dev.js';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';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 };145146 if(await helper.foreignAssets.foreignCollectionId(location) == null) {147 const tokenPrefix = USDT_ASSET_METADATA_NAME;148 await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});149 } else {150 console.log('Foreign collection is already registered on Unique');151 }152153 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);154 usdtCollectionId = await helper.foreignAssets.foreignCollectionId(location);155 });156157158 159 160 await usingRelayPlaygrounds(relayUrl, async (helper) => {161 const destination = {162 V2: {163 parents: 0,164 interior: {X1: {165 Parachain: UNIQUE_CHAIN,166 },167 },168 }};169170 const beneficiary = {171 V2: {172 parents: 0,173 interior: {X1: {174 AccountId32: {175 network: 'Any',176 id: alice.addressRaw,177 },178 }},179 },180 };181182 const assets = {183 V2: [184 {185 id: {186 Concrete: {187 parents: 0,188 interior: 'Here',189 },190 },191 fun: {192 Fungible: TRANSFER_AMOUNT_RELAY,193 },194 },195 ],196 };197198 const feeAssetItem = 0;199200 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');201 });202203 });204205 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {206 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {207 const dest = {208 V2: {209 parents: 1,210 interior: {X1: {211 Parachain: UNIQUE_CHAIN,212 },213 },214 }};215216 const beneficiary = {217 V2: {218 parents: 0,219 interior: {X1: {220 AccountId32: {221 network: 'Any',222 id: alice.addressRaw,223 },224 }},225 },226 };227228 const assets = {229 V2: [230 {231 id: {232 Concrete: {233 parents: 0,234 interior: {235 X2: [236 {237 PalletInstance: STATEMINT_PALLET_INSTANCE,238 },239 {240 GeneralIndex: USDT_ASSET_ID,241 },242 ]},243 },244 },245 fun: {246 Fungible: TRANSFER_AMOUNT,247 },248 },249 ],250 };251252 const feeAssetItem = 0;253254 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);255 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');256257 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);258259 260 console.log(261 '[Statemint -> Unique] transaction fees on Statemint: %s WND',262 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),263 );264 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;265266 });267268269 270 await helper.wait.newBlocks(3);271272 const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});273274 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);275276 console.log(277 '[Statemint -> Unique] transaction fees on Unique: %s USDT',278 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),279 );280 console.log(281 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',282 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),283 );284 285 expect(free).to.be.equal(TRANSFER_AMOUNT);286 287 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;288 });289290 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {291 const destination = {292 V2: {293 parents: 1,294 interior: {X2: [295 {296 Parachain: STATEMINT_CHAIN,297 },298 {299 AccountId32: {300 network: 'Any',301 id: alice.addressRaw,302 },303 },304 ]},305 },306 };307308 const relayFee = 400_000_000_000_000n;309 const currencies: [any, bigint][] = [310 [311 usdtCollectionId,312 TRANSFER_AMOUNT,313 ],314 [315 relayCollectionId,316 relayFee,317 ],318 ];319320 const feeItem = 1;321322 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');323324 325 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);326 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));327 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;328329 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {330 await helper.wait.newBlocks(3);331332 333 334 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;335 });336 });337338 itSub('Should connect and send Relay token to Unique', async ({helper}) => {339 balanceBobBefore = await helper.balance.getSubstrate(bob.address);340 balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});341342 await usingRelayPlaygrounds(relayUrl, async (helper) => {343 const destination = {344 V2: {345 parents: 0,346 interior: {X1: {347 Parachain: UNIQUE_CHAIN,348 },349 },350 }};351352 const beneficiary = {353 V2: {354 parents: 0,355 interior: {X1: {356 AccountId32: {357 network: 'Any',358 id: bob.addressRaw,359 },360 }},361 },362 };363364 const assets = {365 V2: [366 {367 id: {368 Concrete: {369 parents: 0,370 interior: 'Here',371 },372 },373 fun: {374 Fungible: TRANSFER_AMOUNT_RELAY,375 },376 },377 ],378 };379380 const feeAssetItem = 0;381382 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');383 });384385 await helper.wait.newBlocks(3);386387 balanceBobAfter = await helper.balance.getSubstrate(bob.address);388 balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});389390 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;391 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;392 console.log(393 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',394 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),395 );396 console.log(397 '[Relay (Westend) -> Unique] transaction fees: %s WND',398 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),399 );400 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);401 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;402 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;403 });404405 itSub('Should connect and send Relay token back', async ({helper}) => {406 let relayTokenBalanceBefore: bigint;407 let relayTokenBalanceAfter: bigint;408 await usingRelayPlaygrounds(relayUrl, async (helper) => {409 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);410 });411412 const destination = {413 V2: {414 parents: 1,415 interior: {416 X1:{417 AccountId32: {418 network: 'Any',419 id: bob.addressRaw,420 },421 },422 },423 },424 };425426 const currencies: any = [427 [428 relayCollectionId,429 TRANSFER_AMOUNT_RELAY,430 ],431 ];432433 const feeItem = 0;434435 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');436437 balanceBobFinal = await helper.balance.getSubstrate(bob.address);438 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));439440 await usingRelayPlaygrounds(relayUrl, async (helper) => {441 await helper.wait.newBlocks(10);442 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);443444 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;445 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));446 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;447 });448 });449});450451describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {452 let alice: IKeyringPair;453 let randomAccount: IKeyringPair;454455 let balanceUniqueTokenInit: bigint;456 let balanceUniqueTokenMiddle: bigint;457 let balanceUniqueTokenFinal: bigint;458 let balanceAcalaTokenInit: bigint;459 let balanceAcalaTokenMiddle: bigint;460 let balanceAcalaTokenFinal: bigint;461 let balanceUniqueForeignTokenInit: bigint;462 let balanceUniqueForeignTokenMiddle: bigint;463 let balanceUniqueForeignTokenFinal: bigint;464465 466 467 468 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;469 const acalaEps = 8n * 10n ** 16n;470471 let acalaBackwardTransferAmount: bigint;472473 before(async () => {474 await usingPlaygrounds(async (helper, privateKey) => {475 alice = await privateKey('//Alice');476 [randomAccount] = await helper.arrange.createAccounts([0n], alice);477478 479 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);480 });481482 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {483 const destination = {484 V2: {485 parents: 1,486 interior: {487 X1: {488 Parachain: UNIQUE_CHAIN,489 },490 },491 },492 };493494 const metadata = {495 name: 'Unique Network',496 symbol: 'UNQ',497 decimals: 18,498 minimalBalance: 1250000000000000000n,499 };500 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>501 hexToString(v.toJSON()['symbol'])) as string[];502503 if(!assets.includes('UNQ')) {504 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);505 } else {506 console.log('UNQ token already registered on Acala assetRegistry pallet');507 }508 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);509 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);510 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});511 });512513 await usingPlaygrounds(async (helper) => {514 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);515 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);516 });517 });518519 itSub('Should connect and send UNQ to Acala', async ({helper}) => {520521 const destination = {522 V2: {523 parents: 1,524 interior: {525 X1: {526 Parachain: ACALA_CHAIN,527 },528 },529 },530 };531532 const beneficiary = {533 V2: {534 parents: 0,535 interior: {536 X1: {537 AccountId32: {538 network: 'Any',539 id: randomAccount.addressRaw,540 },541 },542 },543 },544 };545546 const assets = {547 V2: [548 {549 id: {550 Concrete: {551 parents: 0,552 interior: 'Here',553 },554 },555 fun: {556 Fungible: TRANSFER_AMOUNT,557 },558 },559 ],560 };561562 const feeAssetItem = 0;563564 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');565 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);566567 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;568 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));569 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;570571 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {572 await helper.wait.newBlocks(3);573574 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});575 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);576577 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;578 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;579 acalaBackwardTransferAmount = unqIncomeTransfer;580581 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;582583 console.log(584 '[Unique -> Acala] transaction fees on Acala: %s ACA',585 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),586 );587 console.log(588 '[Unique -> Acala] transaction fees on Acala: %s UNQ',589 helper.util.bigIntToDecimals(acaUnqFees),590 );591 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));592 expect(acaFees == 0n).to.be.true;593594 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;595596 expect(597 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,598 'Acala took different income fee, check the Acala foreign asset config',599 ).to.be.true;600 });601 });602603 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {604 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {605 const destination = {606 V2: {607 parents: 1,608 interior: {609 X2: [610 {Parachain: UNIQUE_CHAIN},611 {612 AccountId32: {613 network: 'Any',614 id: randomAccount.addressRaw,615 },616 },617 ],618 },619 },620 };621622 const id = {623 ForeignAsset: 0,624 };625626 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');627 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);628 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);629630 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;631 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;632633 console.log(634 '[Acala -> Unique] transaction fees on Acala: %s ACA',635 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),636 );637 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));638639 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;640 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;641 });642643 await helper.wait.newBlocks(3);644645 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);646 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;647 expect(actuallyDelivered > 0).to.be.true;648649 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));650651 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;652 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));653 expect(unqFees == 0n).to.be.true;654 });655656 itSub('Acala can send only up to its balance', async ({helper}) => {657 658 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);659 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);660 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);661662 const moreThanAcalaHas = acalaBalance * 2n;663664 let targetAccountBalance = 0n;665 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);666667 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(668 targetAccount.addressRaw,669 {670 Concrete: {671 parents: 0,672 interior: 'Here',673 },674 },675 moreThanAcalaHas,676 );677678 let maliciousXcmProgramSent: any;679 const maxWaitBlocks = 3;680681 682 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {683 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);684685 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);686 });687688 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash689 && event.outcome.isFailedToTransactAsset);690691 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);692 expect(targetAccountBalance).to.be.equal(0n);693694 695 const validTransferAmount = acalaBalance / 2n;696 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(697 targetAccount.addressRaw,698 {699 Concrete: {700 parents: 0,701 interior: 'Here',702 },703 },704 validTransferAmount,705 );706707 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {708 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);709 });710711 await helper.wait.newBlocks(maxWaitBlocks);712713 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);714 expect(targetAccountBalance).to.be.equal(validTransferAmount);715 });716717 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {718 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);719 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);720721 const uniqueMultilocation = {722 V2: {723 parents: 1,724 interior: {725 X1: {726 Parachain: UNIQUE_CHAIN,727 },728 },729 },730 };731732 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(733 targetAccount.addressRaw,734 uniqueAssetId,735 testAmount,736 );737738 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(739 targetAccount.addressRaw,740 {741 Concrete: {742 parents: 0,743 interior: 'Here',744 },745 },746 testAmount,747 );748749 let maliciousXcmProgramFullIdSent: any;750 let maliciousXcmProgramHereIdSent: any;751 const maxWaitBlocks = 3;752753 754 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {755 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);756757 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);758 });759760 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash761 && event.outcome.isUntrustedReserveLocation);762763 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);764 expect(accountBalance).to.be.equal(0n);765766 767 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {768 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);769770 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);771 });772773 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash774 && event.outcome.isUntrustedReserveLocation);775776 accountBalance = await helper.balance.getSubstrate(targetAccount.address);777 expect(accountBalance).to.be.equal(0n);778 });779});780781describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {782 let alice: IKeyringPair;783 let randomAccount: IKeyringPair;784 let unqFees: bigint;785 let balanceUniqueTokenInit: bigint;786 let balanceUniqueTokenMiddle: bigint;787 let balanceUniqueTokenFinal: bigint;788 const maxWaitBlocks = 6;789790 before(async () => {791 await usingPlaygrounds(async (helper, privateKey) => {792 alice = await privateKey('//Alice');793 [randomAccount] = await helper.arrange.createAccounts([0n], alice);794795 796 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);797 });798799 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {800 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))801 .toJSON() as [])802 .map(nToBigInt).length != 0;803 804805806807808809 if(isWhitelisted) {810 console.log('UNQ token is already whitelisted on Polkadex');811 } else {812 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);813 }814815 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);816 });817818 await usingPlaygrounds(async (helper) => {819 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);820 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);821 });822 });823824 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {825826 const destination = {827 V2: {828 parents: 1,829 interior: {830 X1: {831 Parachain: POLKADEX_CHAIN,832 },833 },834 },835 };836837 const beneficiary = {838 V2: {839 parents: 0,840 interior: {841 X1: {842 AccountId32: {843 network: 'Any',844 id: randomAccount.addressRaw,845 },846 },847 },848 },849 };850851 const assets = {852 V2: [853 {854 id: {855 Concrete: {856 parents: 0,857 interior: 'Here',858 },859 },860 fun: {861 Fungible: TRANSFER_AMOUNT,862 },863 },864 ],865 };866867 const feeAssetItem = 0;868869 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');870 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);871 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);872873 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;874 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));875 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;876877 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {878 879880881882883884885886887888 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);889 });890 });891892893 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {894895 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(896 randomAccount.addressRaw,897 uniqueAssetId,898 TRANSFER_AMOUNT,899 );900901 let xcmProgramSent: any;902903904 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {905 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);906907 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);908 });909910 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);911912 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);913914 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);915 });916917 itSub('Polkadex can send only up to its balance', async ({helper}) => {918 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);919 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);920 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);921 const moreThanPolkadexHas = 2n * polkadexBalance;922923 const targetAccount = helper.arrange.createEmptyAccount();924925 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(926 targetAccount.addressRaw,927 {928 Concrete: {929 parents: 0,930 interior: 'Here',931 },932 },933 moreThanPolkadexHas,934 );935936 let maliciousXcmProgramSent: any;937938939 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {940 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);941942 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);943 });944945 await expectFailedToTransact(helper, maliciousXcmProgramSent);946947 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);948 expect(targetAccountBalance).to.be.equal(0n);949 });950951 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {952 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);953 const targetAccount = helper.arrange.createEmptyAccount();954955 const uniqueMultilocation = {956 V2: {957 parents: 1,958 interior: {959 X1: {960 Parachain: UNIQUE_CHAIN,961 },962 },963 },964 };965966 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(967 targetAccount.addressRaw,968 uniqueAssetId,969 testAmount,970 );971972 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(973 targetAccount.addressRaw,974 {975 Concrete: {976 parents: 0,977 interior: 'Here',978 },979 },980 testAmount,981 );982983 let maliciousXcmProgramFullIdSent: any;984 let maliciousXcmProgramHereIdSent: any;985 const maxWaitBlocks = 3;986987 988 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {989 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);990991 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);992 });993994 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);995996 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);997 expect(accountBalance).to.be.equal(0n);998999 1000 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1001 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10021003 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1004 });10051006 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10071008 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1009 expect(accountBalance).to.be.equal(0n);1010 });1011});1012101310141015describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1016 let alice: IKeyringPair;1017 let alith: IKeyringPair;10181019 const testAmount = 100_000_000_000n;1020 let uniqueParachainJunction;1021 let uniqueAccountJunction;10221023 let uniqueParachainMultilocation: any;1024 let uniqueAccountMultilocation: any;1025 let uniqueCombinedMultilocation: any;1026 let uniqueCombinedMultilocationAcala: any; 10271028 let messageSent: any;10291030 const maxWaitBlocks = 3;10311032 before(async () => {1033 await usingPlaygrounds(async (helper, privateKey) => {1034 alice = await privateKey('//Alice');10351036 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1037 uniqueAccountJunction = {1038 AccountId32: {1039 network: 'Any',1040 id: alice.addressRaw,1041 },1042 };10431044 uniqueParachainMultilocation = {1045 V2: {1046 parents: 1,1047 interior: {1048 X1: uniqueParachainJunction,1049 },1050 },1051 };10521053 uniqueAccountMultilocation = {1054 V2: {1055 parents: 0,1056 interior: {1057 X1: uniqueAccountJunction,1058 },1059 },1060 };10611062 uniqueCombinedMultilocation = {1063 V2: {1064 parents: 1,1065 interior: {1066 X2: [uniqueParachainJunction, uniqueAccountJunction],1067 },1068 },1069 };10701071 uniqueCombinedMultilocationAcala = {1072 V2: {1073 parents: 1,1074 interior: {1075 X2: [uniqueParachainJunction, uniqueAccountJunction],1076 },1077 },1078 };10791080 1081 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1082 });10831084 1085 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1086 alith = helper.account.alithAccount();1087 });1088 });1089109010911092 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1093 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1094 const id = {1095 Token: 'ACA',1096 };1097 const destination = uniqueCombinedMultilocationAcala;1098 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');10991100 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1101 });11021103 await expectFailedToTransact(helper, messageSent);1104 });11051106 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1107 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1108 const id = 'SelfReserve';1109 const destination = uniqueCombinedMultilocation;1110 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11111112 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1113 });11141115 await expectFailedToTransact(helper, messageSent);1116 });11171118 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1119 await usingAstarPlaygrounds(astarUrl, async (helper) => {1120 const destinationParachain = uniqueParachainMultilocation;1121 const beneficiary = uniqueAccountMultilocation;1122 const assets = {1123 V2: [{1124 id: {1125 Concrete: {1126 parents: 0,1127 interior: 'Here',1128 },1129 },1130 fun: {1131 Fungible: testAmount,1132 },1133 }],1134 };1135 const feeAssetItem = 0;11361137 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1138 destinationParachain,1139 beneficiary,1140 assets,1141 feeAssetItem,1142 ]);11431144 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1145 });11461147 await expectFailedToTransact(helper, messageSent);1148 });11491150 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11511152 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1153 helper.arrange.createEmptyAccount().addressRaw,1154 {1155 Concrete: {1156 parents: 1,1157 interior: {1158 X1: {1159 Parachain: POLKADEX_CHAIN,1160 },1161 },1162 },1163 },1164 testAmount,1165 );11661167 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1168 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1169 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1170 });11711172 await expectFailedToTransact(helper, messageSent);1173 });1174});11751176describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1177 1178 let alice: IKeyringPair;1179 let uniqueAssetLocation;11801181 let randomAccountUnique: IKeyringPair;1182 let randomAccountMoonbeam: IKeyringPair;11831184 1185 let assetId: string;11861187 const uniqueAssetMetadata = {1188 name: 'xcUnique',1189 symbol: 'xcUNQ',1190 decimals: 18,1191 isFrozen: false,1192 minimalBalance: 1n,1193 };11941195 let balanceUniqueTokenInit: bigint;1196 let balanceUniqueTokenMiddle: bigint;1197 let balanceUniqueTokenFinal: bigint;1198 let balanceForeignUnqTokenInit: bigint;1199 let balanceForeignUnqTokenMiddle: bigint;1200 let balanceForeignUnqTokenFinal: bigint;1201 let balanceGlmrTokenInit: bigint;1202 let balanceGlmrTokenMiddle: bigint;1203 let balanceGlmrTokenFinal: bigint;12041205 before(async () => {1206 await usingPlaygrounds(async (helper, privateKey) => {1207 alice = await privateKey('//Alice');1208 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12091210 balanceForeignUnqTokenInit = 0n;12111212 1213 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1214 });12151216 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1217 const alithAccount = helper.account.alithAccount();1218 const baltatharAccount = helper.account.baltatharAccount();1219 const dorothyAccount = helper.account.dorothyAccount();12201221 randomAccountMoonbeam = helper.account.create();12221223 1224 console.log('Sponsoring Dorothy.......');1225 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1226 console.log('Sponsoring Dorothy.......DONE');1227 12281229 uniqueAssetLocation = {1230 XCM: {1231 parents: 1,1232 interior: {X1: {Parachain: UNIQUE_CHAIN}},1233 },1234 };1235 const existentialDeposit = 1n;1236 const isSufficient = true;1237 const unitsPerSecond = 1n;1238 const numAssetsWeightHint = 0;12391240 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1241 console.log('Unique asset is already registered on MoonBeam');1242 } else {1243 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1244 location: uniqueAssetLocation,1245 metadata: uniqueAssetMetadata,1246 existentialDeposit,1247 isSufficient,1248 unitsPerSecond,1249 numAssetsWeightHint,1250 });12511252 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12531254 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1255 }12561257 1258 console.log('Acquire Unique AssetId Info on Moonbeam.......');12591260 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1261 console.log('UNQ asset ID is %s', assetId);1262 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1263 12641265 1266 console.log('Sponsoring random Account.......');1267 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1268 console.log('Sponsoring random Account.......DONE');1269 12701271 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1272 });12731274 await usingPlaygrounds(async (helper) => {1275 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1276 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1277 });1278 });12791280 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1281 const currencyId = {1282 NativeAssetId: 'Here',1283 };1284 const dest = {1285 V2: {1286 parents: 1,1287 interior: {1288 X2: [1289 {Parachain: MOONBEAM_CHAIN},1290 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1291 ],1292 },1293 },1294 };1295 const amount = TRANSFER_AMOUNT;12961297 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');12981299 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1300 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13011302 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1303 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1304 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13051306 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1307 await helper.wait.newBlocks(3);1308 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13091310 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1311 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1312 expect(glmrFees == 0n).to.be.true;13131314 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13151316 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1317 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1318 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1319 });1320 });13211322 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1323 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1324 const asset = {1325 V2: {1326 id: {1327 Concrete: {1328 parents: 1,1329 interior: {1330 X1: {Parachain: UNIQUE_CHAIN},1331 },1332 },1333 },1334 fun: {1335 Fungible: TRANSFER_AMOUNT,1336 },1337 },1338 };1339 const destination = {1340 V2: {1341 parents: 1,1342 interior: {1343 X2: [1344 {Parachain: UNIQUE_CHAIN},1345 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1346 ],1347 },1348 },1349 };13501351 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13521353 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13541355 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1356 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1357 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13581359 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13601361 expect(unqRandomAccountAsset).to.be.null;13621363 balanceForeignUnqTokenFinal = 0n;13641365 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1366 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1367 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1368 });13691370 await helper.wait.newBlocks(3);13711372 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1373 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1374 expect(actuallyDelivered > 0).to.be.true;13751376 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13771378 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1379 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1380 expect(unqFees == 0n).to.be.true;1381 });13821383 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1384 1385 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1386 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1387 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);13881389 const moreThanMoonbeamHas = moonbeamBalance * 2n;13901391 let targetAccountBalance = 0n;1392 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);13931394 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1395 targetAccount.addressRaw,1396 {1397 Concrete: {1398 parents: 0,1399 interior: 'Here',1400 },1401 },1402 moreThanMoonbeamHas,1403 );14041405 let maliciousXcmProgramSent: any;1406 const maxWaitBlocks = 3;14071408 1409 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1410 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14111412 1413 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1414 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14151416 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1417 });14181419 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1420 && event.outcome.isFailedToTransactAsset);14211422 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1423 expect(targetAccountBalance).to.be.equal(0n);14241425 1426 const validTransferAmount = moonbeamBalance / 2n;1427 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1428 targetAccount.addressRaw,1429 {1430 Concrete: {1431 parents: 0,1432 interior: 'Here',1433 },1434 },1435 validTransferAmount,1436 );14371438 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1439 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14401441 1442 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1443 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1444 });14451446 await helper.wait.newBlocks(maxWaitBlocks);14471448 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1449 expect(targetAccountBalance).to.be.equal(validTransferAmount);1450 });14511452 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1453 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1454 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14551456 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1457 targetAccount.addressRaw,1458 uniqueAssetId,1459 testAmount,1460 );14611462 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1463 targetAccount.addressRaw,1464 {1465 Concrete: {1466 parents: 0,1467 interior: 'Here',1468 },1469 },1470 testAmount,1471 );14721473 let maliciousXcmProgramFullIdSent: any;1474 let maliciousXcmProgramHereIdSent: any;1475 const maxWaitBlocks = 3;14761477 1478 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1479 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);14801481 1482 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1483 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);14841485 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1486 });14871488 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1489 && event.outcome.isUntrustedReserveLocation);14901491 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1492 expect(accountBalance).to.be.equal(0n);14931494 1495 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1496 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);14971498 1499 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1500 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15011502 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1503 });15041505 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1506 && event.outcome.isUntrustedReserveLocation);15071508 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1509 expect(accountBalance).to.be.equal(0n);1510 });1511});15121513describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1514 let alice: IKeyringPair;1515 let randomAccount: IKeyringPair;15161517 const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; 1518 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; 15191520 1521 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); 1522 const unitsPerSecond = 9_451_000_000_000_000_000n; 1523 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); 1524 const unqToAstarArrived = 9_962_196_000_000_000_000n; 15251526 1527 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); 1528 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; 15291530 let balanceAfterUniqueToAstarXCM: bigint;15311532 before(async () => {1533 await usingPlaygrounds(async (helper, privateKey) => {1534 alice = await privateKey('//Alice');1535 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1536 console.log('randomAccount', randomAccount.address);15371538 1539 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1540 });15411542 await usingAstarPlaygrounds(astarUrl, async (helper) => {1543 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1544 console.log('1. Create foreign asset and metadata');1545 await helper.getSudo().assets.forceCreate(1546 alice,1547 UNQ_ASSET_ID_ON_ASTAR,1548 alice.address,1549 UNQ_MINIMAL_BALANCE_ON_ASTAR,1550 );15511552 await helper.assets.setMetadata(1553 alice,1554 UNQ_ASSET_ID_ON_ASTAR,1555 'Unique Network',1556 'UNQ',1557 Number(UNQ_DECIMALS),1558 );15591560 console.log('2. Register asset location on Astar');1561 const assetLocation = {1562 V2: {1563 parents: 1,1564 interior: {1565 X1: {1566 Parachain: UNIQUE_CHAIN,1567 },1568 },1569 },1570 };15711572 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);15731574 console.log('3. Set UNQ payment for XCM execution on Astar');1575 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1576 } else {1577 console.log('UNQ is already registered on Astar');1578 }1579 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1580 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1581 });1582 });15831584 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1585 const destination = {1586 V2: {1587 parents: 1,1588 interior: {1589 X1: {1590 Parachain: ASTAR_CHAIN,1591 },1592 },1593 },1594 };15951596 const beneficiary = {1597 V2: {1598 parents: 0,1599 interior: {1600 X1: {1601 AccountId32: {1602 network: 'Any',1603 id: randomAccount.addressRaw,1604 },1605 },1606 },1607 },1608 };16091610 const assets = {1611 V2: [1612 {1613 id: {1614 Concrete: {1615 parents: 0,1616 interior: 'Here',1617 },1618 },1619 fun: {1620 Fungible: unqToAstarTransferred,1621 },1622 },1623 ],1624 };16251626 1627 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1628 console.log(`Initial balance is: ${balanceBefore}`);16291630 const feeAssetItem = 0;1631 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16321633 1634 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1635 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1636 console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1637 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16381639 await usingAstarPlaygrounds(astarUrl, async (helper) => {1640 await helper.wait.newBlocks(3);1641 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1642 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16431644 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1645 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);16461647 expect(xcUNQbalance).to.eq(unqToAstarArrived);1648 1649 expect(astarBalance).to.eq(astarInitialBalance);1650 });1651 });16521653 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1654 await usingAstarPlaygrounds(astarUrl, async (helper) => {1655 const destination = {1656 V2: {1657 parents: 1,1658 interior: {1659 X1: {1660 Parachain: UNIQUE_CHAIN,1661 },1662 },1663 },1664 };16651666 const beneficiary = {1667 V2: {1668 parents: 0,1669 interior: {1670 X1: {1671 AccountId32: {1672 network: 'Any',1673 id: randomAccount.addressRaw,1674 },1675 },1676 },1677 },1678 };16791680 const assets = {1681 V2: [1682 {1683 id: {1684 Concrete: {1685 parents: 1,1686 interior: {1687 X1: {1688 Parachain: UNIQUE_CHAIN,1689 },1690 },1691 },1692 },1693 fun: {1694 Fungible: unqFromAstarTransfered,1695 },1696 },1697 ],1698 };16991700 1701 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1702 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1703 expect(balanceASTRbefore).to.eq(astarInitialBalance);17041705 const feeAssetItem = 0;1706 1707 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17081709 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1710 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1711 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17121713 1714 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1715 1716 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1717 });17181719 await helper.wait.newBlocks(3);1720 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1721 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1722 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1723 });17241725 itSub('Astar can send only up to its balance', async ({helper}) => {1726 1727 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1728 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1729 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17301731 const moreThanAstarHas = astarBalance * 2n;17321733 let targetAccountBalance = 0n;1734 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17351736 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1737 targetAccount.addressRaw,1738 {1739 Concrete: {1740 parents: 0,1741 interior: 'Here',1742 },1743 },1744 moreThanAstarHas,1745 );17461747 let maliciousXcmProgramSent: any;1748 const maxWaitBlocks = 3;17491750 1751 await usingAstarPlaygrounds(astarUrl, async (helper) => {1752 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17531754 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1755 });17561757 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1758 && event.outcome.isFailedToTransactAsset);17591760 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1761 expect(targetAccountBalance).to.be.equal(0n);17621763 1764 const validTransferAmount = astarBalance / 2n;1765 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1766 targetAccount.addressRaw,1767 {1768 Concrete: {1769 parents: 0,1770 interior: 'Here',1771 },1772 },1773 validTransferAmount,1774 );17751776 await usingAstarPlaygrounds(astarUrl, async (helper) => {1777 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1778 });17791780 await helper.wait.newBlocks(maxWaitBlocks);17811782 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1783 expect(targetAccountBalance).to.be.equal(validTransferAmount);1784 });17851786 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1787 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1788 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);17891790 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1791 targetAccount.addressRaw,1792 uniqueAssetId,1793 testAmount,1794 );17951796 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1797 targetAccount.addressRaw,1798 {1799 Concrete: {1800 parents: 0,1801 interior: 'Here',1802 },1803 },1804 testAmount,1805 );18061807 let maliciousXcmProgramFullIdSent: any;1808 let maliciousXcmProgramHereIdSent: any;1809 const maxWaitBlocks = 3;18101811 1812 await usingAstarPlaygrounds(astarUrl, async (helper) => {1813 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18141815 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1816 });18171818 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1819 && event.outcome.isUntrustedReserveLocation);18201821 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1822 expect(accountBalance).to.be.equal(0n);18231824 1825 await usingAstarPlaygrounds(astarUrl, async (helper) => {1826 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18271828 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1829 });18301831 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1832 && event.outcome.isUntrustedReserveLocation);18331834 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1835 expect(accountBalance).to.be.equal(0n);1836 });1837});