1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds} from '../util';22import {DevUniqueHelper} from '../util/playgrounds/unique.dev';2324const UNIQUE_CHAIN = 2037;25const STATEMINT_CHAIN = 1000;26const ACALA_CHAIN = 2000;27const MOONBEAM_CHAIN = 2004;28const ASTAR_CHAIN = 2006;2930const STATEMINT_PALLET_INSTANCE = 50;3132const relayUrl = config.relayUrl;33const statemintUrl = config.statemintUrl;34const acalaUrl = config.acalaUrl;35const moonbeamUrl = config.moonbeamUrl;36const astarUrl = config.astarUrl;3738const RELAY_DECIMALS = 12;39const STATEMINT_DECIMALS = 12;40const ACALA_DECIMALS = 12;41const ASTAR_DECIMALS = 18n;42const UNQ_DECIMALS = 18n;4344const TRANSFER_AMOUNT = 2000000000000000000000000n;4546const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4748const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4950const USDT_ASSET_ID = 100;51const USDT_ASSET_METADATA_DECIMALS = 18;52const USDT_ASSET_METADATA_NAME = 'USDT';53const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';54const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;55const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5657const SAFE_XCM_VERSION = 2;5859describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {60 let alice: IKeyringPair;61 let bob: IKeyringPair;6263 let balanceStmnBefore: bigint;64 let balanceStmnAfter: bigint;6566 let balanceUniqueBefore: bigint;67 let balanceUniqueAfter: bigint;68 let balanceUniqueFinal: bigint;6970 let balanceBobBefore: bigint;71 let balanceBobAfter: bigint;72 let balanceBobFinal: bigint;7374 let balanceBobRelayTokenBefore: bigint;75 let balanceBobRelayTokenAfter: bigint;767778 before(async () => {79 await usingPlaygrounds(async (helper, privateKey) => {80 alice = await privateKey('//Alice');81 bob = await privateKey('//Bob'); 8283 84 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);85 });8687 await usingRelayPlaygrounds(relayUrl, async (helper) => {88 89 const relayXcmVersion = 2;90 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT, relayXcmVersion);91 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT, relayXcmVersion);92 });9394 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {95 const sovereignFundingAmount = 3_500_000_000n;9697 await helper.assets.create(98 alice,99 USDT_ASSET_ID,100 alice.address,101 USDT_ASSET_METADATA_MINIMAL_BALANCE,102 );103 await helper.assets.setMetadata(104 alice,105 USDT_ASSET_ID,106 USDT_ASSET_METADATA_NAME,107 USDT_ASSET_METADATA_DESCRIPTION,108 USDT_ASSET_METADATA_DECIMALS,109 );110 await helper.assets.mint(111 alice,112 USDT_ASSET_ID,113 alice.address,114 USDT_ASSET_AMOUNT,115 );116117 118 119 120 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);121 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);122 });123124125 await usingPlaygrounds(async (helper) => {126 const location = {127 V2: {128 parents: 1,129 interior: {X3: [130 {131 Parachain: STATEMINT_CHAIN,132 },133 {134 PalletInstance: STATEMINT_PALLET_INSTANCE,135 },136 {137 GeneralIndex: USDT_ASSET_ID,138 },139 ]},140 },141 };142143 const metadata =144 {145 name: USDT_ASSET_ID,146 symbol: USDT_ASSET_METADATA_NAME,147 decimals: USDT_ASSET_METADATA_DECIMALS,148 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,149 };150 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);151 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);152 });153154155 156 157 await usingRelayPlaygrounds(relayUrl, async (helper) => {158 const destination = {159 V1: {160 parents: 0,161 interior: {X1: {162 Parachain: UNIQUE_CHAIN,163 },164 },165 }};166167 const beneficiary = {168 V1: {169 parents: 0,170 interior: {X1: {171 AccountId32: {172 network: 'Any',173 id: alice.addressRaw,174 },175 }},176 },177 };178179 const assets = {180 V1: [181 {182 id: {183 Concrete: {184 parents: 0,185 interior: 'Here',186 },187 },188 fun: {189 Fungible: TRANSFER_AMOUNT_RELAY,190 },191 },192 ],193 };194195 const feeAssetItem = 0;196197 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');198 });199200 });201202 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {203 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {204 const dest = {205 V1: {206 parents: 1,207 interior: {X1: {208 Parachain: UNIQUE_CHAIN,209 },210 },211 }};212213 const beneficiary = {214 V1: {215 parents: 0,216 interior: {X1: {217 AccountId32: {218 network: 'Any',219 id: alice.addressRaw,220 },221 }},222 },223 };224225 const assets = {226 V1: [227 {228 id: {229 Concrete: {230 parents: 0,231 interior: {232 X2: [233 {234 PalletInstance: STATEMINT_PALLET_INSTANCE,235 },236 {237 GeneralIndex: USDT_ASSET_ID,238 },239 ]},240 },241 },242 fun: {243 Fungible: TRANSFER_AMOUNT,244 },245 },246 ],247 };248249 const feeAssetItem = 0;250251 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);252 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');253254 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);255256 257 console.log(258 '[Statemint -> Unique] transaction fees on Statemint: %s WND',259 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),260 );261 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;262263 });264265266 267 await helper.wait.newBlocks(3);268269 270 const free = await helper.ft.getBalance(1, {Substrate: alice.address});271272 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);273274 console.log(275 '[Statemint -> Unique] transaction fees on Unique: %s USDT',276 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),277 );278 console.log(279 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',280 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),281 );282 283 expect(free).to.be.equal(TRANSFER_AMOUNT);284 285 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;286 });287288 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {289 const destination = {290 V2: {291 parents: 1,292 interior: {X2: [293 {294 Parachain: STATEMINT_CHAIN,295 },296 {297 AccountId32: {298 network: 'Any',299 id: alice.addressRaw,300 },301 },302 ]},303 },304 };305306 const relayFee = 400_000_000_000_000n;307 const currencies: [any, bigint][] = [308 [309 {310 ForeignAssetId: 0,311 },312 TRANSFER_AMOUNT,313 ],314 [315 {316 NativeAssetId: 'Parent',317 },318 relayFee,319 ],320 ];321322 const feeItem = 1;323324 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');325326 327 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);328 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));329 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;330331 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {332 await helper.wait.newBlocks(3);333334 335 336 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;337 });338 });339340 itSub('Should connect and send Relay token to Unique', async ({helper}) => {341 balanceBobBefore = await helper.balance.getSubstrate(bob.address);342 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});343344 await usingRelayPlaygrounds(relayUrl, async (helper) => {345 const destination = {346 V1: {347 parents: 0,348 interior: {X1: {349 Parachain: UNIQUE_CHAIN,350 },351 },352 }};353354 const beneficiary = {355 V1: {356 parents: 0,357 interior: {X1: {358 AccountId32: {359 network: 'Any',360 id: bob.addressRaw,361 },362 }},363 },364 };365366 const assets = {367 V1: [368 {369 id: {370 Concrete: {371 parents: 0,372 interior: 'Here',373 },374 },375 fun: {376 Fungible: TRANSFER_AMOUNT_RELAY,377 },378 },379 ],380 };381382 const feeAssetItem = 0;383384 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');385 });386387 await helper.wait.newBlocks(3);388389 balanceBobAfter = await helper.balance.getSubstrate(bob.address);390 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});391392 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;393 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;394 console.log(395 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',396 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),397 );398 console.log(399 '[Relay (Westend) -> Unique] transaction fees: %s WND',400 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),401 );402 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);403 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;404 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;405 });406407 itSub('Should connect and send Relay token back', async ({helper}) => {408 let relayTokenBalanceBefore: bigint;409 let relayTokenBalanceAfter: bigint;410 await usingRelayPlaygrounds(relayUrl, async (helper) => {411 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);412 });413414 const destination = {415 V2: {416 parents: 1,417 interior: {418 X1:{419 AccountId32: {420 network: 'Any',421 id: bob.addressRaw,422 },423 },424 },425 },426 };427428 const currencies: any = [429 [430 {431 NativeAssetId: 'Parent',432 },433 TRANSFER_AMOUNT_RELAY,434 ],435 ];436437 const feeItem = 0;438439 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');440441 balanceBobFinal = await helper.balance.getSubstrate(bob.address);442 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));443444 await usingRelayPlaygrounds(relayUrl, async (helper) => {445 await helper.wait.newBlocks(10);446 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);447448 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;449 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));450 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;451 });452 });453});454455describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {456 let alice: IKeyringPair;457 let randomAccount: IKeyringPair;458459 let balanceUniqueTokenInit: bigint;460 let balanceUniqueTokenMiddle: bigint;461 let balanceUniqueTokenFinal: bigint;462 let balanceAcalaTokenInit: bigint;463 let balanceAcalaTokenMiddle: bigint;464 let balanceAcalaTokenFinal: bigint;465 let balanceUniqueForeignTokenInit: bigint;466 let balanceUniqueForeignTokenMiddle: bigint;467 let balanceUniqueForeignTokenFinal: bigint;468469 470 471 472 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;473474 const ACALA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedAcalaIncomeFee;475476 before(async () => {477 await usingPlaygrounds(async (helper, privateKey) => {478 alice = await privateKey('//Alice');479 [randomAccount] = await helper.arrange.createAccounts([0n], alice);480481 482 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);483 });484485 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {486 const destination = {487 V1: {488 parents: 1,489 interior: {490 X1: {491 Parachain: UNIQUE_CHAIN,492 },493 },494 },495 };496497 const metadata = {498 name: 'Unique Network',499 symbol: 'UNQ',500 decimals: 18,501 minimalBalance: 1250000000000000000n,502 };503504 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);505 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);506 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);507 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});508 });509510 await usingPlaygrounds(async (helper) => {511 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);512 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);513 });514 });515516 itSub('Should connect and send UNQ to Acala', async ({helper}) => {517518 const destination = {519 V2: {520 parents: 1,521 interior: {522 X1: {523 Parachain: ACALA_CHAIN,524 },525 },526 },527 };528529 const beneficiary = {530 V2: {531 parents: 0,532 interior: {533 X1: {534 AccountId32: {535 network: 'Any',536 id: randomAccount.addressRaw,537 },538 },539 },540 },541 };542543 const assets = {544 V2: [545 {546 id: {547 Concrete: {548 parents: 0,549 interior: 'Here',550 },551 },552 fun: {553 Fungible: TRANSFER_AMOUNT,554 },555 },556 ],557 };558559 const feeAssetItem = 0;560561 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');562 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);563564 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;565 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));566 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;567568 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {569 await helper.wait.newBlocks(3);570571 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});572 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);573574 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;575 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;576 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;577578 console.log(579 '[Unique -> Acala] transaction fees on Acala: %s ACA',580 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),581 );582 console.log(583 '[Unique -> Acala] transaction fees on Acala: %s UNQ',584 helper.util.bigIntToDecimals(acaUnqFees),585 );586 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));587 expect(acaFees == 0n).to.be.true;588 expect(589 acaUnqFees == expectedAcalaIncomeFee,590 'Acala took different income fee, check the Acala foreign asset config',591 ).to.be.true;592 });593 });594595 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {596 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {597 const destination = {598 V1: {599 parents: 1,600 interior: {601 X2: [602 {Parachain: UNIQUE_CHAIN},603 {604 AccountId32: {605 network: 'Any',606 id: randomAccount.addressRaw,607 },608 },609 ],610 },611 },612 };613614 const id = {615 ForeignAsset: 0,616 };617618 await helper.xTokens.transfer(randomAccount, id, ACALA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');619 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);620 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);621622 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;623 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;624625 console.log(626 '[Acala -> Unique] transaction fees on Acala: %s ACA',627 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),628 );629 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));630631 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;632 expect(unqOutcomeTransfer == ACALA_BACKWARD_TRANSFER_AMOUNT).to.be.true;633 });634635 await helper.wait.newBlocks(3);636637 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);638 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;639 expect(actuallyDelivered > 0).to.be.true;640641 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));642643 const unqFees = ACALA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;644 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));645 expect(unqFees == 0n).to.be.true;646 });647648 itSub('Acala can send only up to its balance', async ({helper}) => {649 650 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);651 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);652 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);653654 const moreThanAcalaHas = acalaBalance * 2n;655656 let targetAccountBalance = 0n;657 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);658659 const uniqueMultilocation = {660 V1: {661 parents: 1,662 interior: {663 X1: {Parachain: UNIQUE_CHAIN},664 },665 },666 };667668 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(targetAccount.addressRaw, moreThanAcalaHas);669670 671 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {672 await helper.getSudo().executeExtrinsic(673 alice,674 'api.tx.polkadotXcm.send',675 [676 uniqueMultilocation,677 maliciousXcmProgram,678 ],679 true,680 );681 });682683 const maxWaitBlocks = 3;684685 const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(686 maxWaitBlocks,687 'xcmpQueue',688 'Fail',689 );690691 expect(692 xcmpQueueFailEvent != null,693 `'xcmpQueue.FailEvent' event is expected`,694 ).to.be.true;695696 expect(697 xcmpQueueFailEvent!.isFailedToTransactAsset,698 `The XCM error should be 'FailedToTransactAsset'`,699 ).to.be.true;700701 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);702 expect(targetAccountBalance).to.be.equal(0n);703704 705 const validTransferAmount = acalaBalance / 2n;706 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(targetAccount.addressRaw, validTransferAmount);707708 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {709 await helper.getSudo().executeExtrinsic(710 alice,711 'api.tx.polkadotXcm.send',712 [713 uniqueMultilocation,714 validXcmProgram,715 ],716 true,717 );718 });719720 await helper.wait.newBlocks(maxWaitBlocks);721722 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);723 expect(targetAccountBalance).to.be.equal(validTransferAmount);724 });725});726727728729describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {730 let alice: IKeyringPair;731 let alith: IKeyringPair;732733 const testAmount = 100_000_000_000n;734 let uniqueParachainJunction;735 let uniqueAccountJunction;736737 let uniqueParachainMultilocation: any;738 let uniqueAccountMultilocation: any;739 let uniqueCombinedMultilocation: any;740741 before(async () => {742 await usingPlaygrounds(async (helper, privateKey) => {743 alice = await privateKey('//Alice');744745 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};746 uniqueAccountJunction = {747 AccountId32: {748 network: 'Any',749 id: alice.addressRaw,750 },751 };752753 uniqueParachainMultilocation = {754 V1: {755 parents: 1,756 interior: {757 X1: uniqueParachainJunction,758 },759 },760 };761762 uniqueAccountMultilocation = {763 V1: {764 parents: 0,765 interior: {766 X1: uniqueAccountJunction,767 },768 },769 };770771 uniqueCombinedMultilocation = {772 V1: {773 parents: 1,774 interior: {775 X2: [uniqueParachainJunction, uniqueAccountJunction],776 },777 },778 };779780 781 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);782 });783784 785 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {786 alith = helper.account.alithAccount();787 });788 });789790 const expectFailedToTransact = async (network: string, helper: DevUniqueHelper) => {791 const maxWaitBlocks = 3;792793 const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(794 maxWaitBlocks,795 'xcmpQueue',796 'Fail',797 );798799 expect(800 xcmpQueueFailEvent != null,801 `[reject ${network} tokens] 'xcmpQueue.FailEvent' event is expected`,802 ).to.be.true;803804 expect(805 xcmpQueueFailEvent!.isFailedToTransactAsset,806 `[reject ${network} tokens] The XCM error should be 'FailedToTransactAsset'`,807 ).to.be.true;808 };809810 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {811 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {812 const id = {813 Token: 'ACA',814 };815 const destination = uniqueCombinedMultilocation;816 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');817 });818819 await expectFailedToTransact('ACA', helper);820 });821822 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {823 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {824 const id = 'SelfReserve';825 const destination = uniqueCombinedMultilocation;826 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');827 });828829 await expectFailedToTransact('GLMR', helper);830 });831832 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {833 await usingAstarPlaygrounds(astarUrl, async (helper) => {834 const destinationParachain = uniqueParachainMultilocation;835 const beneficiary = uniqueAccountMultilocation;836 const assets = {837 V1: [{838 id: {839 Concrete: {840 parents: 0,841 interior: 'Here',842 },843 },844 fun: {845 Fungible: testAmount,846 },847 }],848 };849 const feeAssetItem = 0;850851 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [852 destinationParachain,853 beneficiary,854 assets,855 feeAssetItem,856 ]);857 });858859 await expectFailedToTransact('ASTR', helper);860 });861});862863describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {864 865 let uniqueDonor: IKeyringPair;866 let uniqueAssetLocation;867868 let randomAccountUnique: IKeyringPair;869 let randomAccountMoonbeam: IKeyringPair;870871 872 let assetId: string;873874 const councilVotingThreshold = 2;875 const technicalCommitteeThreshold = 2;876 const votingPeriod = 3;877 const delayPeriod = 0;878879 const uniqueAssetMetadata = {880 name: 'xcUnique',881 symbol: 'xcUNQ',882 decimals: 18,883 isFrozen: false,884 minimalBalance: 1n,885 };886887 let balanceUniqueTokenInit: bigint;888 let balanceUniqueTokenMiddle: bigint;889 let balanceUniqueTokenFinal: bigint;890 let balanceForeignUnqTokenInit: bigint;891 let balanceForeignUnqTokenMiddle: bigint;892 let balanceForeignUnqTokenFinal: bigint;893 let balanceGlmrTokenInit: bigint;894 let balanceGlmrTokenMiddle: bigint;895 let balanceGlmrTokenFinal: bigint;896897 before(async () => {898 await usingPlaygrounds(async (helper, privateKey) => {899 uniqueDonor = await privateKey('//Alice');900 [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor);901902 balanceForeignUnqTokenInit = 0n;903904 905 const alice = uniqueDonor;906 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);907 });908909 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {910 const alithAccount = helper.account.alithAccount();911 const baltatharAccount = helper.account.baltatharAccount();912 const dorothyAccount = helper.account.dorothyAccount();913914 randomAccountMoonbeam = helper.account.create();915916 917 console.log('Sponsoring Dorothy.......');918 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);919 console.log('Sponsoring Dorothy.......DONE');920 921922 uniqueAssetLocation = {923 XCM: {924 parents: 1,925 interior: {X1: {Parachain: UNIQUE_CHAIN}},926 },927 };928 const existentialDeposit = 1n;929 const isSufficient = true;930 const unitsPerSecond = 1n;931 const numAssetsWeightHint = 0;932933 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({934 location: uniqueAssetLocation,935 metadata: uniqueAssetMetadata,936 existentialDeposit,937 isSufficient,938 unitsPerSecond,939 numAssetsWeightHint,940 });941 const proposalHash = blake2AsHex(encodedProposal);942943 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);944 console.log('Encoded length %d', encodedProposal.length);945 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);946947 948 console.log('Note motion preimage.......');949 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);950 console.log('Note motion preimage.......DONE');951 952953 954 console.log('Propose external motion through council.......');955 const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});956 const encodedMotion = externalMotion?.method.toHex() || '';957 const motionHash = blake2AsHex(encodedMotion);958 console.log('Motion hash is %s', motionHash);959960 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);961962 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;963 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);964 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);965966 await helper.collective.council.close(967 dorothyAccount,968 motionHash,969 councilProposalIdx,970 {971 refTime: 1_000_000_000,972 proofSize: 1_000_000,973 },974 externalMotion.encodedLength,975 );976 console.log('Propose external motion through council.......DONE');977 978979 980 console.log('Fast track proposal through technical committee.......');981 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);982 const encodedFastTrack = fastTrack?.method.toHex() || '';983 const fastTrackHash = blake2AsHex(encodedFastTrack);984 console.log('FastTrack hash is %s', fastTrackHash);985986 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);987988 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;989 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);990 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);991992 await helper.collective.techCommittee.close(993 baltatharAccount,994 fastTrackHash,995 techProposalIdx,996 {997 refTime: 1_000_000_000,998 proofSize: 1_000_000,999 },1000 fastTrack.encodedLength,1001 );1002 console.log('Fast track proposal through technical committee.......DONE');1003 10041005 1006 console.log('Referendum voting.......');1007 await helper.democracy.referendumVote(dorothyAccount, 0, {1008 balance: 10_000_000_000_000_000_000n,1009 vote: {aye: true, conviction: 1},1010 });1011 console.log('Referendum voting.......DONE');1012 10131014 1015 console.log('Acquire Unique AssetId Info on Moonbeam.......');10161017 1018 await helper.wait.newBlocks(5);10191020 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();10211022 console.log('UNQ asset ID is %s', assetId);1023 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1024 10251026 1027 console.log('Sponsoring random Account.......');1028 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1029 console.log('Sponsoring random Account.......DONE');1030 10311032 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1033 });10341035 await usingPlaygrounds(async (helper) => {1036 await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1037 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1038 });1039 });10401041 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1042 const currencyId = {1043 NativeAssetId: 'Here',1044 };1045 const dest = {1046 V2: {1047 parents: 1,1048 interior: {1049 X2: [1050 {Parachain: MOONBEAM_CHAIN},1051 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1052 ],1053 },1054 },1055 };1056 const amount = TRANSFER_AMOUNT;10571058 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');10591060 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1061 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;10621063 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1064 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1065 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;10661067 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1068 await helper.wait.newBlocks(3);10691070 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);10711072 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1073 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1074 expect(glmrFees == 0n).to.be.true;10751076 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;10771078 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1079 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1080 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1081 });1082 });10831084 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1085 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1086 const asset = {1087 V1: {1088 id: {1089 Concrete: {1090 parents: 1,1091 interior: {1092 X1: {Parachain: UNIQUE_CHAIN},1093 },1094 },1095 },1096 fun: {1097 Fungible: TRANSFER_AMOUNT,1098 },1099 },1100 };1101 const destination = {1102 V1: {1103 parents: 1,1104 interior: {1105 X2: [1106 {Parachain: UNIQUE_CHAIN},1107 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1108 ],1109 },1110 },1111 };11121113 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');11141115 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);11161117 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1118 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1119 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;11201121 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);11221123 expect(unqRandomAccountAsset).to.be.null;11241125 balanceForeignUnqTokenFinal = 0n;11261127 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1128 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1129 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1130 });11311132 await helper.wait.newBlocks(3);11331134 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1135 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1136 expect(actuallyDelivered > 0).to.be.true;11371138 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));11391140 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1141 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1142 expect(unqFees == 0n).to.be.true;1143 });11441145 itSub.skip('Moonbeam can send only up to its balance', async ({helper}) => {1146 throw Error("Not yet implemented");1147 });1148});11491150describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1151 let alice: IKeyringPair;1152 let randomAccount: IKeyringPair;11531154 const UNQ_ASSET_ID_ON_SHIDEN = 1;1155 const UNQ_MINIMAL_BALANCE_ON_SHIDEN = 1n;11561157 1158 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); 1159 const unitsPerSecond = 228_000_000_000n; 1160 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); 1161 const unqToAstarArrived = 9_999_999_999_088_000_000n; 11621163 1164 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); 1165 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; 11661167 let balanceAfterUniqueToAstarXCM: bigint;11681169 before(async () => {1170 await usingPlaygrounds(async (helper, privateKey) => {1171 alice = await privateKey('//Alice');1172 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1173 console.log('randomAccount', randomAccount.address);11741175 1176 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1177 });11781179 await usingAstarPlaygrounds(astarUrl, async (helper) => {1180 console.log('1. Create foreign asset and metadata');1181 1182 await helper.assets.create(1183 alice,1184 UNQ_ASSET_ID_ON_SHIDEN,1185 alice.address,1186 UNQ_MINIMAL_BALANCE_ON_SHIDEN,1187 );11881189 await helper.assets.setMetadata(1190 alice,1191 UNQ_ASSET_ID_ON_SHIDEN,1192 'Cross chain UNQ',1193 'xcUNQ',1194 Number(UNQ_DECIMALS),1195 );11961197 console.log('2. Register asset location on Astar');1198 const assetLocation = {1199 V1: {1200 parents: 1,1201 interior: {1202 X1: {1203 Parachain: UNIQUE_CHAIN,1204 },1205 },1206 },1207 };12081209 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_SHIDEN]);12101211 console.log('3. Set UNQ payment for XCM execution on Astar');1212 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);12131214 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1215 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1216 });1217 });12181219 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1220 const destination = {1221 V2: {1222 parents: 1,1223 interior: {1224 X1: {1225 Parachain: ASTAR_CHAIN,1226 },1227 },1228 },1229 };12301231 const beneficiary = {1232 V2: {1233 parents: 0,1234 interior: {1235 X1: {1236 AccountId32: {1237 network: 'Any',1238 id: randomAccount.addressRaw,1239 },1240 },1241 },1242 },1243 };12441245 const assets = {1246 V2: [1247 {1248 id: {1249 Concrete: {1250 parents: 0,1251 interior: 'Here',1252 },1253 },1254 fun: {1255 Fungible: unqToAstarTransferred,1256 },1257 },1258 ],1259 };12601261 1262 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1263 console.log(`Initial balance is: ${balanceBefore}`);12641265 const feeAssetItem = 0;1266 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');12671268 1269 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1270 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1271 console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1272 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;12731274 await usingAstarPlaygrounds(astarUrl, async (helper) => {1275 await helper.wait.newBlocks(3);1276 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_SHIDEN, randomAccount.address);1277 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);12781279 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1280 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);12811282 expect(xcUNQbalance).to.eq(unqToAstarArrived);1283 1284 expect(astarBalance).to.eq(astarInitialBalance);1285 });1286 });12871288 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1289 await usingAstarPlaygrounds(astarUrl, async (helper) => {1290 const destination = {1291 V1: {1292 parents: 1,1293 interior: {1294 X1: {1295 Parachain: UNIQUE_CHAIN,1296 },1297 },1298 },1299 };13001301 const beneficiary = {1302 V1: {1303 parents: 0,1304 interior: {1305 X1: {1306 AccountId32: {1307 network: 'Any',1308 id: randomAccount.addressRaw,1309 },1310 },1311 },1312 },1313 };13141315 const assets = {1316 V1: [1317 {1318 id: {1319 Concrete: {1320 parents: 1,1321 interior: {1322 X1: {1323 Parachain: UNIQUE_CHAIN,1324 },1325 },1326 },1327 },1328 fun: {1329 Fungible: unqFromAstarTransfered,1330 },1331 },1332 ],1333 };13341335 1336 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1337 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1338 expect(balanceASTRbefore).to.eq(astarInitialBalance);13391340 const feeAssetItem = 0;1341 1342 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);13431344 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_SHIDEN, randomAccount.address);1345 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1346 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);13471348 1349 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1350 1351 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1352 });13531354 await helper.wait.newBlocks(3);1355 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1356 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1357 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1358 });13591360 itSub.skip('Should not accept limitedReserveTransfer of UNQ from ASTAR', async ({helper}) => {1361 await usingAstarPlaygrounds(astarUrl, async (helper) => {1362 const destination = {1363 V1: {1364 parents: 1,1365 interior: {1366 X1: {1367 Parachain: UNIQUE_CHAIN,1368 },1369 },1370 },1371 };13721373 const beneficiary = {1374 V1: {1375 parents: 0,1376 interior: {1377 X1: {1378 AccountId32: {1379 network: 'Any',1380 id: randomAccount.addressRaw,1381 },1382 },1383 },1384 },1385 };13861387 const assets = {1388 V1: [1389 {1390 id: {1391 Concrete: {1392 parents: 1,1393 interior: {1394 X1: {1395 Parachain: UNIQUE_CHAIN,1396 },1397 },1398 },1399 },1400 fun: {1401 Fungible: unqFromAstarTransfered,1402 },1403 },1404 ],1405 };14061407 1408 expect(await helper.balance.getSubstrate(randomAccount.address)).to.eq(astarInitialBalance);14091410 const feeAssetItem = 0;1411 1412 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');1413 });1414 });14151416 itSub('Astar can send only up to its balance', async ({helper}) => {1417 1418 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1419 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1420 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);14211422 const moreThanShidenHas = astarBalance * 2n;14231424 let targetAccountBalance = 0n;1425 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14261427 const uniqueMultilocation = {1428 V1: {1429 parents: 1,1430 interior: {1431 X1: {Parachain: UNIQUE_CHAIN},1432 },1433 },1434 };14351436 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(targetAccount.addressRaw, moreThanShidenHas);14371438 1439 await usingAstarPlaygrounds(astarUrl, async (helper) => {1440 await helper.getSudo().executeExtrinsic(1441 alice,1442 'api.tx.polkadotXcm.send',1443 [1444 uniqueMultilocation,1445 maliciousXcmProgram,1446 ],1447 true,1448 );1449 });14501451 const maxWaitBlocks = 3;14521453 const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(1454 maxWaitBlocks,1455 'xcmpQueue',1456 'Fail',1457 );14581459 expect(1460 xcmpQueueFailEvent != null,1461 `'xcmpQueue.FailEvent' event is expected`,1462 ).to.be.true;14631464 expect(1465 xcmpQueueFailEvent!.isFailedToTransactAsset,1466 `The XCM error should be 'FailedToTransactAsset'`,1467 ).to.be.true;14681469 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1470 expect(targetAccountBalance).to.be.equal(0n);14711472 1473 const validTransferAmount = astarBalance / 2n;1474 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(targetAccount.addressRaw, validTransferAmount);14751476 await usingAstarPlaygrounds(astarUrl, async (helper) => {1477 await helper.getSudo().executeExtrinsic(1478 alice,1479 'api.tx.polkadotXcm.send',1480 [1481 uniqueMultilocation,1482 validXcmProgram,1483 ],1484 true,1485 );1486 });14871488 await helper.wait.newBlocks(maxWaitBlocks);14891490 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1491 expect(targetAccountBalance).to.be.equal(validTransferAmount);1492 });1493});