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 = 0;1282 const dest = {1283 V2: {1284 parents: 1,1285 interior: {1286 X2: [1287 {Parachain: MOONBEAM_CHAIN},1288 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1289 ],1290 },1291 },1292 };1293 const amount = TRANSFER_AMOUNT;12941295 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');12961297 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1298 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;12991300 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1301 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1302 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13031304 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1305 await helper.wait.newBlocks(3);1306 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13071308 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1309 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1310 expect(glmrFees == 0n).to.be.true;13111312 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13131314 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1315 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1316 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1317 });1318 });13191320 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1321 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1322 const asset = {1323 V2: {1324 id: {1325 Concrete: {1326 parents: 1,1327 interior: {1328 X1: {Parachain: UNIQUE_CHAIN},1329 },1330 },1331 },1332 fun: {1333 Fungible: TRANSFER_AMOUNT,1334 },1335 },1336 };1337 const destination = {1338 V2: {1339 parents: 1,1340 interior: {1341 X2: [1342 {Parachain: UNIQUE_CHAIN},1343 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1344 ],1345 },1346 },1347 };13481349 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13501351 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13521353 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1354 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1355 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13561357 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13581359 expect(unqRandomAccountAsset).to.be.null;13601361 balanceForeignUnqTokenFinal = 0n;13621363 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1364 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1365 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1366 });13671368 await helper.wait.newBlocks(3);13691370 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1371 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1372 expect(actuallyDelivered > 0).to.be.true;13731374 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13751376 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1377 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1378 expect(unqFees == 0n).to.be.true;1379 });13801381 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1382 1383 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1384 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1385 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);13861387 const moreThanMoonbeamHas = moonbeamBalance * 2n;13881389 let targetAccountBalance = 0n;1390 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);13911392 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1393 targetAccount.addressRaw,1394 {1395 Concrete: {1396 parents: 0,1397 interior: 'Here',1398 },1399 },1400 moreThanMoonbeamHas,1401 );14021403 let maliciousXcmProgramSent: any;1404 const maxWaitBlocks = 3;14051406 1407 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1408 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14091410 1411 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1412 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14131414 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1415 });14161417 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1418 && event.outcome.isFailedToTransactAsset);14191420 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1421 expect(targetAccountBalance).to.be.equal(0n);14221423 1424 const validTransferAmount = moonbeamBalance / 2n;1425 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1426 targetAccount.addressRaw,1427 {1428 Concrete: {1429 parents: 0,1430 interior: 'Here',1431 },1432 },1433 validTransferAmount,1434 );14351436 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1437 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14381439 1440 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1441 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1442 });14431444 await helper.wait.newBlocks(maxWaitBlocks);14451446 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1447 expect(targetAccountBalance).to.be.equal(validTransferAmount);1448 });14491450 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1451 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1452 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14531454 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1455 targetAccount.addressRaw,1456 uniqueAssetId,1457 testAmount,1458 );14591460 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1461 targetAccount.addressRaw,1462 {1463 Concrete: {1464 parents: 0,1465 interior: 'Here',1466 },1467 },1468 testAmount,1469 );14701471 let maliciousXcmProgramFullIdSent: any;1472 let maliciousXcmProgramHereIdSent: any;1473 const maxWaitBlocks = 3;14741475 1476 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1477 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);14781479 1480 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1481 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);14821483 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1484 });14851486 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1487 && event.outcome.isUntrustedReserveLocation);14881489 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1490 expect(accountBalance).to.be.equal(0n);14911492 1493 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1494 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);14951496 1497 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1498 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);14991500 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1501 });15021503 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1504 && event.outcome.isUntrustedReserveLocation);15051506 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1507 expect(accountBalance).to.be.equal(0n);1508 });1509});15101511describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1512 let alice: IKeyringPair;1513 let randomAccount: IKeyringPair;15141515 const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; 1516 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; 15171518 1519 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); 1520 const unitsPerSecond = 9_451_000_000_000_000_000n; 1521 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); 1522 const unqToAstarArrived = 9_962_196_000_000_000_000n; 15231524 1525 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); 1526 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; 15271528 let balanceAfterUniqueToAstarXCM: bigint;15291530 before(async () => {1531 await usingPlaygrounds(async (helper, privateKey) => {1532 alice = await privateKey('//Alice');1533 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1534 console.log('randomAccount', randomAccount.address);15351536 1537 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1538 });15391540 await usingAstarPlaygrounds(astarUrl, async (helper) => {1541 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1542 console.log('1. Create foreign asset and metadata');1543 await helper.getSudo().assets.forceCreate(1544 alice,1545 UNQ_ASSET_ID_ON_ASTAR,1546 alice.address,1547 UNQ_MINIMAL_BALANCE_ON_ASTAR,1548 );15491550 await helper.assets.setMetadata(1551 alice,1552 UNQ_ASSET_ID_ON_ASTAR,1553 'Unique Network',1554 'UNQ',1555 Number(UNQ_DECIMALS),1556 );15571558 console.log('2. Register asset location on Astar');1559 const assetLocation = {1560 V2: {1561 parents: 1,1562 interior: {1563 X1: {1564 Parachain: UNIQUE_CHAIN,1565 },1566 },1567 },1568 };15691570 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);15711572 console.log('3. Set UNQ payment for XCM execution on Astar');1573 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1574 } else {1575 console.log('UNQ is already registered on Astar');1576 }1577 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1578 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1579 });1580 });15811582 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1583 const destination = {1584 V2: {1585 parents: 1,1586 interior: {1587 X1: {1588 Parachain: ASTAR_CHAIN,1589 },1590 },1591 },1592 };15931594 const beneficiary = {1595 V2: {1596 parents: 0,1597 interior: {1598 X1: {1599 AccountId32: {1600 network: 'Any',1601 id: randomAccount.addressRaw,1602 },1603 },1604 },1605 },1606 };16071608 const assets = {1609 V2: [1610 {1611 id: {1612 Concrete: {1613 parents: 0,1614 interior: 'Here',1615 },1616 },1617 fun: {1618 Fungible: unqToAstarTransferred,1619 },1620 },1621 ],1622 };16231624 1625 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1626 console.log(`Initial balance is: ${balanceBefore}`);16271628 const feeAssetItem = 0;1629 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16301631 1632 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1633 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1634 console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1635 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16361637 await usingAstarPlaygrounds(astarUrl, async (helper) => {1638 await helper.wait.newBlocks(3);1639 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1640 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16411642 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1643 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);16441645 expect(xcUNQbalance).to.eq(unqToAstarArrived);1646 1647 expect(astarBalance).to.eq(astarInitialBalance);1648 });1649 });16501651 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1652 await usingAstarPlaygrounds(astarUrl, async (helper) => {1653 const destination = {1654 V2: {1655 parents: 1,1656 interior: {1657 X1: {1658 Parachain: UNIQUE_CHAIN,1659 },1660 },1661 },1662 };16631664 const beneficiary = {1665 V2: {1666 parents: 0,1667 interior: {1668 X1: {1669 AccountId32: {1670 network: 'Any',1671 id: randomAccount.addressRaw,1672 },1673 },1674 },1675 },1676 };16771678 const assets = {1679 V2: [1680 {1681 id: {1682 Concrete: {1683 parents: 1,1684 interior: {1685 X1: {1686 Parachain: UNIQUE_CHAIN,1687 },1688 },1689 },1690 },1691 fun: {1692 Fungible: unqFromAstarTransfered,1693 },1694 },1695 ],1696 };16971698 1699 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1700 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1701 expect(balanceASTRbefore).to.eq(astarInitialBalance);17021703 const feeAssetItem = 0;1704 1705 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17061707 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1708 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1709 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17101711 1712 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1713 1714 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1715 });17161717 await helper.wait.newBlocks(3);1718 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1719 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1720 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1721 });17221723 itSub('Astar can send only up to its balance', async ({helper}) => {1724 1725 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1726 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1727 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17281729 const moreThanAstarHas = astarBalance * 2n;17301731 let targetAccountBalance = 0n;1732 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17331734 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1735 targetAccount.addressRaw,1736 {1737 Concrete: {1738 parents: 0,1739 interior: 'Here',1740 },1741 },1742 moreThanAstarHas,1743 );17441745 let maliciousXcmProgramSent: any;1746 const maxWaitBlocks = 3;17471748 1749 await usingAstarPlaygrounds(astarUrl, async (helper) => {1750 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17511752 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1753 });17541755 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1756 && event.outcome.isFailedToTransactAsset);17571758 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1759 expect(targetAccountBalance).to.be.equal(0n);17601761 1762 const validTransferAmount = astarBalance / 2n;1763 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1764 targetAccount.addressRaw,1765 {1766 Concrete: {1767 parents: 0,1768 interior: 'Here',1769 },1770 },1771 validTransferAmount,1772 );17731774 await usingAstarPlaygrounds(astarUrl, async (helper) => {1775 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1776 });17771778 await helper.wait.newBlocks(maxWaitBlocks);17791780 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1781 expect(targetAccountBalance).to.be.equal(validTransferAmount);1782 });17831784 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1785 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1786 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);17871788 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1789 targetAccount.addressRaw,1790 uniqueAssetId,1791 testAmount,1792 );17931794 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1795 targetAccount.addressRaw,1796 {1797 Concrete: {1798 parents: 0,1799 interior: 'Here',1800 },1801 },1802 testAmount,1803 );18041805 let maliciousXcmProgramFullIdSent: any;1806 let maliciousXcmProgramHereIdSent: any;1807 const maxWaitBlocks = 3;18081809 1810 await usingAstarPlaygrounds(astarUrl, async (helper) => {1811 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18121813 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1814 });18151816 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1817 && event.outcome.isUntrustedReserveLocation);18181819 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1820 expect(accountBalance).to.be.equal(0n);18211822 1823 await usingAstarPlaygrounds(astarUrl, async (helper) => {1824 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18251826 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1827 });18281829 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1830 && event.outcome.isUntrustedReserveLocation);18311832 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1833 expect(accountBalance).to.be.equal(0n);1834 });1835});