1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {Event} from '../util/playgrounds/unique.dev';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';232425const 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;5253describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {54 let alice: IKeyringPair;55 let bob: IKeyringPair;5657 let balanceStmnBefore: bigint;58 let balanceStmnAfter: bigint;5960 let balanceUniqueBefore: bigint;61 let balanceUniqueAfter: bigint;62 let balanceUniqueFinal: bigint;6364 let balanceBobBefore: bigint;65 let balanceBobAfter: bigint;66 let balanceBobFinal: bigint;6768 let balanceBobRelayTokenBefore: bigint;69 let balanceBobRelayTokenAfter: bigint;707172 before(async () => {73 await usingPlaygrounds(async (helper, privateKey) => {74 alice = await privateKey('//Alice');75 bob = await privateKey('//Bob'); 7677 78 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);79 });8081 await usingRelayPlaygrounds(relayUrl, async (helper) => {82 83 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);84 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);85 });8687 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {88 const sovereignFundingAmount = 3_500_000_000n;8990 await helper.assets.create(91 alice,92 USDT_ASSET_ID,93 alice.address,94 USDT_ASSET_METADATA_MINIMAL_BALANCE,95 );96 await helper.assets.setMetadata(97 alice,98 USDT_ASSET_ID,99 USDT_ASSET_METADATA_NAME,100 USDT_ASSET_METADATA_DESCRIPTION,101 USDT_ASSET_METADATA_DECIMALS,102 );103 await helper.assets.mint(104 alice,105 USDT_ASSET_ID,106 alice.address,107 USDT_ASSET_AMOUNT,108 );109110 111 112 113 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);114 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);115 });116117118 await usingPlaygrounds(async (helper) => {119 const location = {120 V2: {121 parents: 1,122 interior: {X3: [123 {124 Parachain: STATEMINT_CHAIN,125 },126 {127 PalletInstance: STATEMINT_PALLET_INSTANCE,128 },129 {130 GeneralIndex: USDT_ASSET_ID,131 },132 ]},133 },134 };135136 const metadata =137 {138 name: USDT_ASSET_ID,139 symbol: USDT_ASSET_METADATA_NAME,140 decimals: USDT_ASSET_METADATA_DECIMALS,141 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,142 };143 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);144 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);145 });146147148 149 150 await usingRelayPlaygrounds(relayUrl, async (helper) => {151 const destination = {152 V2: {153 parents: 0,154 interior: {X1: {155 Parachain: UNIQUE_CHAIN,156 },157 },158 }};159160 const beneficiary = {161 V2: {162 parents: 0,163 interior: {X1: {164 AccountId32: {165 network: 'Any',166 id: alice.addressRaw,167 },168 }},169 },170 };171172 const assets = {173 V2: [174 {175 id: {176 Concrete: {177 parents: 0,178 interior: 'Here',179 },180 },181 fun: {182 Fungible: TRANSFER_AMOUNT_RELAY,183 },184 },185 ],186 };187188 const feeAssetItem = 0;189190 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');191 });192193 });194195 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {196 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {197 const dest = {198 V2: {199 parents: 1,200 interior: {X1: {201 Parachain: UNIQUE_CHAIN,202 },203 },204 }};205206 const beneficiary = {207 V2: {208 parents: 0,209 interior: {X1: {210 AccountId32: {211 network: 'Any',212 id: alice.addressRaw,213 },214 }},215 },216 };217218 const assets = {219 V2: [220 {221 id: {222 Concrete: {223 parents: 0,224 interior: {225 X2: [226 {227 PalletInstance: STATEMINT_PALLET_INSTANCE,228 },229 {230 GeneralIndex: USDT_ASSET_ID,231 },232 ]},233 },234 },235 fun: {236 Fungible: TRANSFER_AMOUNT,237 },238 },239 ],240 };241242 const feeAssetItem = 0;243244 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);245 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');246247 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);248249 250 console.log(251 '[Statemint -> Unique] transaction fees on Statemint: %s WND',252 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),253 );254 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;255256 });257258259 260 await helper.wait.newBlocks(3);261262 263 const free = await helper.ft.getBalance(1, {Substrate: alice.address});264265 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);266267 console.log(268 '[Statemint -> Unique] transaction fees on Unique: %s USDT',269 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),270 );271 console.log(272 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',273 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),274 );275 276 expect(free).to.be.equal(TRANSFER_AMOUNT);277 278 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;279 });280281 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {282 const destination = {283 V2: {284 parents: 1,285 interior: {X2: [286 {287 Parachain: STATEMINT_CHAIN,288 },289 {290 AccountId32: {291 network: 'Any',292 id: alice.addressRaw,293 },294 },295 ]},296 },297 };298299 const relayFee = 400_000_000_000_000n;300 const currencies: [any, bigint][] = [301 [302 {303 ForeignAssetId: 0,304 },305 TRANSFER_AMOUNT,306 ],307 [308 {309 NativeAssetId: 'Parent',310 },311 relayFee,312 ],313 ];314315 const feeItem = 1;316317 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');318319 320 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);321 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));322 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;323324 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {325 await helper.wait.newBlocks(3);326327 328 329 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;330 });331 });332333 itSub('Should connect and send Relay token to Unique', async ({helper}) => {334 balanceBobBefore = await helper.balance.getSubstrate(bob.address);335 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});336337 await usingRelayPlaygrounds(relayUrl, async (helper) => {338 const destination = {339 V2: {340 parents: 0,341 interior: {X1: {342 Parachain: UNIQUE_CHAIN,343 },344 },345 }};346347 const beneficiary = {348 V2: {349 parents: 0,350 interior: {X1: {351 AccountId32: {352 network: 'Any',353 id: bob.addressRaw,354 },355 }},356 },357 };358359 const assets = {360 V2: [361 {362 id: {363 Concrete: {364 parents: 0,365 interior: 'Here',366 },367 },368 fun: {369 Fungible: TRANSFER_AMOUNT_RELAY,370 },371 },372 ],373 };374375 const feeAssetItem = 0;376377 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');378 });379380 await helper.wait.newBlocks(3);381382 balanceBobAfter = await helper.balance.getSubstrate(bob.address);383 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});384385 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;386 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;387 console.log(388 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',389 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),390 );391 console.log(392 '[Relay (Westend) -> Unique] transaction fees: %s WND',393 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),394 );395 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);396 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;397 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;398 });399400 itSub('Should connect and send Relay token back', async ({helper}) => {401 let relayTokenBalanceBefore: bigint;402 let relayTokenBalanceAfter: bigint;403 await usingRelayPlaygrounds(relayUrl, async (helper) => {404 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);405 });406407 const destination = {408 V2: {409 parents: 1,410 interior: {411 X1:{412 AccountId32: {413 network: 'Any',414 id: bob.addressRaw,415 },416 },417 },418 },419 };420421 const currencies: any = [422 [423 {424 NativeAssetId: 'Parent',425 },426 TRANSFER_AMOUNT_RELAY,427 ],428 ];429430 const feeItem = 0;431432 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');433434 balanceBobFinal = await helper.balance.getSubstrate(bob.address);435 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));436437 await usingRelayPlaygrounds(relayUrl, async (helper) => {438 await helper.wait.newBlocks(10);439 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);440441 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;442 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));443 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;444 });445 });446});447448describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {449 let alice: IKeyringPair;450 let randomAccount: IKeyringPair;451452 let balanceUniqueTokenInit: bigint;453 let balanceUniqueTokenMiddle: bigint;454 let balanceUniqueTokenFinal: bigint;455 let balanceAcalaTokenInit: bigint;456 let balanceAcalaTokenMiddle: bigint;457 let balanceAcalaTokenFinal: bigint;458 let balanceUniqueForeignTokenInit: bigint;459 let balanceUniqueForeignTokenMiddle: bigint;460 let balanceUniqueForeignTokenFinal: bigint;461462 463 464 465 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;466 const acalaEps = 8n * 10n ** 16n;467468 let acalaBackwardTransferAmount: bigint;469470 before(async () => {471 await usingPlaygrounds(async (helper, privateKey) => {472 alice = await privateKey('//Alice');473 [randomAccount] = await helper.arrange.createAccounts([0n], alice);474475 476 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);477 });478479 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {480 const destination = {481 V2: {482 parents: 1,483 interior: {484 X1: {485 Parachain: UNIQUE_CHAIN,486 },487 },488 },489 };490491 const metadata = {492 name: 'Unique Network',493 symbol: 'UNQ',494 decimals: 18,495 minimalBalance: 1250000000000000000n,496 };497 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>498 hexToString(v.toJSON()['symbol'])) as string[];499500 if(!assets.includes('UNQ')) {501 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);502 } else {503 console.log('UNQ token already registered on Acala assetRegistry pallet');504 }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 acalaBackwardTransferAmount = unqIncomeTransfer;577578 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;579580 console.log(581 '[Unique -> Acala] transaction fees on Acala: %s ACA',582 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),583 );584 console.log(585 '[Unique -> Acala] transaction fees on Acala: %s UNQ',586 helper.util.bigIntToDecimals(acaUnqFees),587 );588 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));589 expect(acaFees == 0n).to.be.true;590591 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;592593 expect(594 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,595 'Acala took different income fee, check the Acala foreign asset config',596 ).to.be.true;597 });598 });599600 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {601 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {602 const destination = {603 V2: {604 parents: 1,605 interior: {606 X2: [607 {Parachain: UNIQUE_CHAIN},608 {609 AccountId32: {610 network: 'Any',611 id: randomAccount.addressRaw,612 },613 },614 ],615 },616 },617 };618619 const id = {620 ForeignAsset: 0,621 };622623 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');624 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);625 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);626627 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;628 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;629630 console.log(631 '[Acala -> Unique] transaction fees on Acala: %s ACA',632 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),633 );634 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));635636 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;637 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;638 });639640 await helper.wait.newBlocks(3);641642 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);643 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;644 expect(actuallyDelivered > 0).to.be.true;645646 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));647648 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;649 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));650 expect(unqFees == 0n).to.be.true;651 });652653 itSub('Acala can send only up to its balance', async ({helper}) => {654 655 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);656 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);657 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);658659 const moreThanAcalaHas = acalaBalance * 2n;660661 let targetAccountBalance = 0n;662 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);663664 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(665 targetAccount.addressRaw,666 {667 Concrete: {668 parents: 0,669 interior: 'Here',670 },671 },672 moreThanAcalaHas,673 );674675 let maliciousXcmProgramSent: any;676 const maxWaitBlocks = 3;677678 679 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {680 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);681682 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);683 });684685 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash686 && event.outcome.isFailedToTransactAsset);687688 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);689 expect(targetAccountBalance).to.be.equal(0n);690691 692 const validTransferAmount = acalaBalance / 2n;693 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(694 targetAccount.addressRaw,695 {696 Concrete: {697 parents: 0,698 interior: 'Here',699 },700 },701 validTransferAmount,702 );703704 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {705 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);706 });707708 await helper.wait.newBlocks(maxWaitBlocks);709710 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);711 expect(targetAccountBalance).to.be.equal(validTransferAmount);712 });713714 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {715 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);716 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);717718 const uniqueMultilocation = {719 V2: {720 parents: 1,721 interior: {722 X1: {723 Parachain: UNIQUE_CHAIN,724 },725 },726 },727 };728729 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(730 targetAccount.addressRaw,731 {732 Concrete: {733 parents: 1,734 interior: {735 X1: {736 Parachain: UNIQUE_CHAIN,737 },738 },739 },740 },741 testAmount,742 );743744 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(745 targetAccount.addressRaw,746 {747 Concrete: {748 parents: 0,749 interior: 'Here',750 },751 },752 testAmount,753 );754755 let maliciousXcmProgramFullIdSent: any;756 let maliciousXcmProgramHereIdSent: any;757 const maxWaitBlocks = 3;758759 760 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {761 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);762763 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);764 });765766 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash767 && event.outcome.isUntrustedReserveLocation);768769 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);770 expect(accountBalance).to.be.equal(0n);771772 773 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {774 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);775776 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);777 });778779 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash780 && event.outcome.isUntrustedReserveLocation);781782 accountBalance = await helper.balance.getSubstrate(targetAccount.address);783 expect(accountBalance).to.be.equal(0n);784 });785});786787describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {788 let alice: IKeyringPair;789 let randomAccount: IKeyringPair;790 let unqFees: bigint;791 let balanceUniqueTokenInit: bigint;792 let balanceUniqueTokenMiddle: bigint;793 let balanceUniqueTokenFinal: bigint;794 const maxWaitBlocks = 6;795796 before(async () => {797 await usingPlaygrounds(async (helper, privateKey) => {798 alice = await privateKey('//Alice');799 [randomAccount] = await helper.arrange.createAccounts([0n], alice);800801 802 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);803 });804805 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {806 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))807 .toJSON() as [])808 .map(nToBigInt).length != 0;809 810811812813814815 if(isWhitelisted) {816 console.log('UNQ token is already whitelisted on Polkadex');817 } else {818 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);819 }820821 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);822 });823824 await usingPlaygrounds(async (helper) => {825 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);826 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);827 });828 });829830 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {831832 const destination = {833 V2: {834 parents: 1,835 interior: {836 X1: {837 Parachain: POLKADEX_CHAIN,838 },839 },840 },841 };842843 const beneficiary = {844 V2: {845 parents: 0,846 interior: {847 X1: {848 AccountId32: {849 network: 'Any',850 id: randomAccount.addressRaw,851 },852 },853 },854 },855 };856857 const assets = {858 V2: [859 {860 id: {861 Concrete: {862 parents: 0,863 interior: 'Here',864 },865 },866 fun: {867 Fungible: TRANSFER_AMOUNT,868 },869 },870 ],871 };872873 const feeAssetItem = 0;874875 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');876 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);877 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);878879 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;880 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));881 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;882883 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {884 885886887888889890891892893894 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);895 });896 });897898899 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {900901 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(902 randomAccount.addressRaw,903 {904 Concrete: {905 parents: 1,906 interior: {907 X1: {Parachain: UNIQUE_CHAIN},908 },909 },910 },911 TRANSFER_AMOUNT,912 );913914 let xcmProgramSent: any;915916917 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {918 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);919920 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);921 });922923 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);924925 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);926927 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);928 });929930 itSub('Polkadex can send only up to its balance', async ({helper}) => {931 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);932 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);933 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);934 const moreThanPolkadexHas = 2n * polkadexBalance;935936 const targetAccount = helper.arrange.createEmptyAccount();937938 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(939 targetAccount.addressRaw,940 {941 Concrete: {942 parents: 0,943 interior: 'Here',944 },945 },946 moreThanPolkadexHas,947 );948949 let maliciousXcmProgramSent: any;950951952 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {953 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);954955 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);956 });957958 await expectFailedToTransact(helper, maliciousXcmProgramSent);959960 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);961 expect(targetAccountBalance).to.be.equal(0n);962 });963964 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {965 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);966 const targetAccount = helper.arrange.createEmptyAccount();967968 const uniqueMultilocation = {969 V2: {970 parents: 1,971 interior: {972 X1: {973 Parachain: UNIQUE_CHAIN,974 },975 },976 },977 };978979 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(980 targetAccount.addressRaw,981 {982 Concrete: {983 parents: 1,984 interior: {985 X1: {986 Parachain: UNIQUE_CHAIN,987 },988 },989 },990 },991 testAmount,992 );993994 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(995 targetAccount.addressRaw,996 {997 Concrete: {998 parents: 0,999 interior: 'Here',1000 },1001 },1002 testAmount,1003 );10041005 let maliciousXcmProgramFullIdSent: any;1006 let maliciousXcmProgramHereIdSent: any;1007 const maxWaitBlocks = 3;10081009 1010 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1011 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10121013 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1014 });10151016 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10171018 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1019 expect(accountBalance).to.be.equal(0n);10201021 1022 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1023 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10241025 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1026 });10271028 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10291030 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1031 expect(accountBalance).to.be.equal(0n);1032 });1033});1034103510361037describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1038 let alice: IKeyringPair;1039 let alith: IKeyringPair;10401041 const testAmount = 100_000_000_000n;1042 let uniqueParachainJunction;1043 let uniqueAccountJunction;10441045 let uniqueParachainMultilocation: any;1046 let uniqueAccountMultilocation: any;1047 let uniqueCombinedMultilocation: any;1048 let uniqueCombinedMultilocationAcala: any; 10491050 let messageSent: any;10511052 const maxWaitBlocks = 3;10531054 before(async () => {1055 await usingPlaygrounds(async (helper, privateKey) => {1056 alice = await privateKey('//Alice');10571058 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1059 uniqueAccountJunction = {1060 AccountId32: {1061 network: 'Any',1062 id: alice.addressRaw,1063 },1064 };10651066 uniqueParachainMultilocation = {1067 V2: {1068 parents: 1,1069 interior: {1070 X1: uniqueParachainJunction,1071 },1072 },1073 };10741075 uniqueAccountMultilocation = {1076 V2: {1077 parents: 0,1078 interior: {1079 X1: uniqueAccountJunction,1080 },1081 },1082 };10831084 uniqueCombinedMultilocation = {1085 V2: {1086 parents: 1,1087 interior: {1088 X2: [uniqueParachainJunction, uniqueAccountJunction],1089 },1090 },1091 };10921093 uniqueCombinedMultilocationAcala = {1094 V2: {1095 parents: 1,1096 interior: {1097 X2: [uniqueParachainJunction, uniqueAccountJunction],1098 },1099 },1100 };11011102 1103 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1104 });11051106 1107 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1108 alith = helper.account.alithAccount();1109 });1110 });1111111211131114 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1115 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1116 const id = {1117 Token: 'ACA',1118 };1119 const destination = uniqueCombinedMultilocationAcala;1120 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11211122 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1123 });11241125 await expectFailedToTransact(helper, messageSent);1126 });11271128 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1129 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1130 const id = 'SelfReserve';1131 const destination = uniqueCombinedMultilocation;1132 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11331134 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1135 });11361137 await expectFailedToTransact(helper, messageSent);1138 });11391140 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1141 await usingAstarPlaygrounds(astarUrl, async (helper) => {1142 const destinationParachain = uniqueParachainMultilocation;1143 const beneficiary = uniqueAccountMultilocation;1144 const assets = {1145 V2: [{1146 id: {1147 Concrete: {1148 parents: 0,1149 interior: 'Here',1150 },1151 },1152 fun: {1153 Fungible: testAmount,1154 },1155 }],1156 };1157 const feeAssetItem = 0;11581159 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1160 destinationParachain,1161 beneficiary,1162 assets,1163 feeAssetItem,1164 ]);11651166 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1167 });11681169 await expectFailedToTransact(helper, messageSent);1170 });11711172 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11731174 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1175 helper.arrange.createEmptyAccount().addressRaw,1176 {1177 Concrete: {1178 parents: 1,1179 interior: {1180 X1: {1181 Parachain: POLKADEX_CHAIN,1182 },1183 },1184 },1185 },1186 testAmount,1187 );11881189 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1190 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1191 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1192 });11931194 await expectFailedToTransact(helper, messageSent);1195 });1196});11971198describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1199 1200 let alice: IKeyringPair;1201 let uniqueAssetLocation;12021203 let randomAccountUnique: IKeyringPair;1204 let randomAccountMoonbeam: IKeyringPair;12051206 1207 let assetId: string;12081209 const uniqueAssetMetadata = {1210 name: 'xcUnique',1211 symbol: 'xcUNQ',1212 decimals: 18,1213 isFrozen: false,1214 minimalBalance: 1n,1215 };12161217 let balanceUniqueTokenInit: bigint;1218 let balanceUniqueTokenMiddle: bigint;1219 let balanceUniqueTokenFinal: bigint;1220 let balanceForeignUnqTokenInit: bigint;1221 let balanceForeignUnqTokenMiddle: bigint;1222 let balanceForeignUnqTokenFinal: bigint;1223 let balanceGlmrTokenInit: bigint;1224 let balanceGlmrTokenMiddle: bigint;1225 let balanceGlmrTokenFinal: bigint;12261227 before(async () => {1228 await usingPlaygrounds(async (helper, privateKey) => {1229 alice = await privateKey('//Alice');1230 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12311232 balanceForeignUnqTokenInit = 0n;12331234 1235 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1236 });12371238 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1239 const alithAccount = helper.account.alithAccount();1240 const baltatharAccount = helper.account.baltatharAccount();1241 const dorothyAccount = helper.account.dorothyAccount();12421243 randomAccountMoonbeam = helper.account.create();12441245 1246 console.log('Sponsoring Dorothy.......');1247 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1248 console.log('Sponsoring Dorothy.......DONE');1249 12501251 uniqueAssetLocation = {1252 XCM: {1253 parents: 1,1254 interior: {X1: {Parachain: UNIQUE_CHAIN}},1255 },1256 };1257 const existentialDeposit = 1n;1258 const isSufficient = true;1259 const unitsPerSecond = 1n;1260 const numAssetsWeightHint = 0;12611262 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1263 console.log('Unique asset is already registered on MoonBeam');1264 } else {1265 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1266 location: uniqueAssetLocation,1267 metadata: uniqueAssetMetadata,1268 existentialDeposit,1269 isSufficient,1270 unitsPerSecond,1271 numAssetsWeightHint,1272 });12731274 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12751276 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1277 }12781279 1280 console.log('Acquire Unique AssetId Info on Moonbeam.......');12811282 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1283 console.log('UNQ asset ID is %s', assetId);1284 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1285 12861287 1288 console.log('Sponsoring random Account.......');1289 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1290 console.log('Sponsoring random Account.......DONE');1291 12921293 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1294 });12951296 await usingPlaygrounds(async (helper) => {1297 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1298 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1299 });1300 });13011302 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1303 const currencyId = {1304 NativeAssetId: 'Here',1305 };1306 const dest = {1307 V2: {1308 parents: 1,1309 interior: {1310 X2: [1311 {Parachain: MOONBEAM_CHAIN},1312 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1313 ],1314 },1315 },1316 };1317 const amount = TRANSFER_AMOUNT;13181319 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13201321 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1322 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13231324 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1325 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1326 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13271328 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1329 await helper.wait.newBlocks(3);1330 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13311332 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1333 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1334 expect(glmrFees == 0n).to.be.true;13351336 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13371338 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1339 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1340 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1341 });1342 });13431344 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1345 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1346 const asset = {1347 V2: {1348 id: {1349 Concrete: {1350 parents: 1,1351 interior: {1352 X1: {Parachain: UNIQUE_CHAIN},1353 },1354 },1355 },1356 fun: {1357 Fungible: TRANSFER_AMOUNT,1358 },1359 },1360 };1361 const destination = {1362 V2: {1363 parents: 1,1364 interior: {1365 X2: [1366 {Parachain: UNIQUE_CHAIN},1367 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1368 ],1369 },1370 },1371 };13721373 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13741375 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13761377 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1378 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1379 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13801381 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13821383 expect(unqRandomAccountAsset).to.be.null;13841385 balanceForeignUnqTokenFinal = 0n;13861387 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1388 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1389 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1390 });13911392 await helper.wait.newBlocks(3);13931394 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1395 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1396 expect(actuallyDelivered > 0).to.be.true;13971398 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13991400 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1401 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1402 expect(unqFees == 0n).to.be.true;1403 });14041405 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1406 1407 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1408 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1409 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14101411 const moreThanMoonbeamHas = moonbeamBalance * 2n;14121413 let targetAccountBalance = 0n;1414 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14151416 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1417 targetAccount.addressRaw,1418 {1419 Concrete: {1420 parents: 0,1421 interior: 'Here',1422 },1423 },1424 moreThanMoonbeamHas,1425 );14261427 let maliciousXcmProgramSent: any;1428 const maxWaitBlocks = 3;14291430 1431 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1432 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14331434 1435 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1436 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14371438 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1439 });14401441 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1442 && event.outcome.isFailedToTransactAsset);14431444 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1445 expect(targetAccountBalance).to.be.equal(0n);14461447 1448 const validTransferAmount = moonbeamBalance / 2n;1449 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1450 targetAccount.addressRaw,1451 {1452 Concrete: {1453 parents: 0,1454 interior: 'Here',1455 },1456 },1457 validTransferAmount,1458 );14591460 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1461 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14621463 1464 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1465 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1466 });14671468 await helper.wait.newBlocks(maxWaitBlocks);14691470 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1471 expect(targetAccountBalance).to.be.equal(validTransferAmount);1472 });14731474 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1475 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1476 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14771478 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1479 targetAccount.addressRaw,1480 {1481 Concrete: {1482 parents: 0,1483 interior: {1484 X1: {1485 Parachain: UNIQUE_CHAIN,1486 },1487 },1488 },1489 },1490 testAmount,1491 );14921493 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1494 targetAccount.addressRaw,1495 {1496 Concrete: {1497 parents: 0,1498 interior: 'Here',1499 },1500 },1501 testAmount,1502 );15031504 let maliciousXcmProgramFullIdSent: any;1505 let maliciousXcmProgramHereIdSent: any;1506 const maxWaitBlocks = 3;15071508 1509 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1510 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);15111512 1513 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1514 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15151516 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1517 });15181519 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1520 && event.outcome.isUntrustedReserveLocation);15211522 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1523 expect(accountBalance).to.be.equal(0n);15241525 1526 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1527 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);15281529 1530 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1531 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15321533 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1534 });15351536 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1537 && event.outcome.isUntrustedReserveLocation);15381539 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1540 expect(accountBalance).to.be.equal(0n);1541 });1542});15431544describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1545 let alice: IKeyringPair;1546 let randomAccount: IKeyringPair;15471548 const UNQ_ASSET_ID_ON_ASTAR = 1;1549 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;15501551 1552 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); 1553 const unitsPerSecond = 228_000_000_000n; 1554 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); 1555 const unqToAstarArrived = 9_999_999_999_088_000_000n; 15561557 1558 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); 1559 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; 15601561 let balanceAfterUniqueToAstarXCM: bigint;15621563 before(async () => {1564 await usingPlaygrounds(async (helper, privateKey) => {1565 alice = await privateKey('//Alice');1566 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1567 console.log('randomAccount', randomAccount.address);15681569 1570 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1571 });15721573 await usingAstarPlaygrounds(astarUrl, async (helper) => {1574 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1575 console.log('1. Create foreign asset and metadata');1576 1577 await helper.assets.create(1578 alice,1579 UNQ_ASSET_ID_ON_ASTAR,1580 alice.address,1581 UNQ_MINIMAL_BALANCE_ON_ASTAR,1582 );15831584 await helper.assets.setMetadata(1585 alice,1586 UNQ_ASSET_ID_ON_ASTAR,1587 'Cross chain UNQ',1588 'xcUNQ',1589 Number(UNQ_DECIMALS),1590 );15911592 console.log('2. Register asset location on Astar');1593 const assetLocation = {1594 V2: {1595 parents: 1,1596 interior: {1597 X1: {1598 Parachain: UNIQUE_CHAIN,1599 },1600 },1601 },1602 };16031604 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16051606 console.log('3. Set UNQ payment for XCM execution on Astar');1607 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1608 } else {1609 console.log('UNQ is already registered on Astar');1610 }1611 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1612 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1613 });1614 });16151616 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1617 const destination = {1618 V2: {1619 parents: 1,1620 interior: {1621 X1: {1622 Parachain: ASTAR_CHAIN,1623 },1624 },1625 },1626 };16271628 const beneficiary = {1629 V2: {1630 parents: 0,1631 interior: {1632 X1: {1633 AccountId32: {1634 network: 'Any',1635 id: randomAccount.addressRaw,1636 },1637 },1638 },1639 },1640 };16411642 const assets = {1643 V2: [1644 {1645 id: {1646 Concrete: {1647 parents: 0,1648 interior: 'Here',1649 },1650 },1651 fun: {1652 Fungible: unqToAstarTransferred,1653 },1654 },1655 ],1656 };16571658 1659 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1660 console.log(`Initial balance is: ${balanceBefore}`);16611662 const feeAssetItem = 0;1663 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16641665 1666 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1667 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1668 console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1669 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16701671 await usingAstarPlaygrounds(astarUrl, async (helper) => {1672 await helper.wait.newBlocks(3);1673 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1674 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16751676 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1677 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);16781679 expect(xcUNQbalance).to.eq(unqToAstarArrived);1680 1681 expect(astarBalance).to.eq(astarInitialBalance);1682 });1683 });16841685 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1686 await usingAstarPlaygrounds(astarUrl, async (helper) => {1687 const destination = {1688 V2: {1689 parents: 1,1690 interior: {1691 X1: {1692 Parachain: UNIQUE_CHAIN,1693 },1694 },1695 },1696 };16971698 const beneficiary = {1699 V2: {1700 parents: 0,1701 interior: {1702 X1: {1703 AccountId32: {1704 network: 'Any',1705 id: randomAccount.addressRaw,1706 },1707 },1708 },1709 },1710 };17111712 const assets = {1713 V2: [1714 {1715 id: {1716 Concrete: {1717 parents: 1,1718 interior: {1719 X1: {1720 Parachain: UNIQUE_CHAIN,1721 },1722 },1723 },1724 },1725 fun: {1726 Fungible: unqFromAstarTransfered,1727 },1728 },1729 ],1730 };17311732 1733 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1734 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1735 expect(balanceASTRbefore).to.eq(astarInitialBalance);17361737 const feeAssetItem = 0;1738 1739 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17401741 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1742 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1743 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17441745 1746 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1747 1748 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1749 });17501751 await helper.wait.newBlocks(3);1752 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1753 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1754 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1755 });17561757 itSub('Astar can send only up to its balance', async ({helper}) => {1758 1759 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1760 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1761 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17621763 const moreThanAstarHas = astarBalance * 2n;17641765 let targetAccountBalance = 0n;1766 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17671768 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1769 targetAccount.addressRaw,1770 {1771 Concrete: {1772 parents: 0,1773 interior: 'Here',1774 },1775 },1776 moreThanAstarHas,1777 );17781779 let maliciousXcmProgramSent: any;1780 const maxWaitBlocks = 3;17811782 1783 await usingAstarPlaygrounds(astarUrl, async (helper) => {1784 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17851786 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1787 });17881789 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1790 && event.outcome.isFailedToTransactAsset);17911792 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1793 expect(targetAccountBalance).to.be.equal(0n);17941795 1796 const validTransferAmount = astarBalance / 2n;1797 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1798 targetAccount.addressRaw,1799 {1800 Concrete: {1801 parents: 0,1802 interior: 'Here',1803 },1804 },1805 validTransferAmount,1806 );18071808 await usingAstarPlaygrounds(astarUrl, async (helper) => {1809 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1810 });18111812 await helper.wait.newBlocks(maxWaitBlocks);18131814 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1815 expect(targetAccountBalance).to.be.equal(validTransferAmount);1816 });18171818 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1819 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1820 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18211822 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1823 targetAccount.addressRaw,1824 {1825 Concrete: {1826 parents: 1,1827 interior: {1828 X1: {1829 Parachain: UNIQUE_CHAIN,1830 },1831 },1832 },1833 },1834 testAmount,1835 );18361837 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1838 targetAccount.addressRaw,1839 {1840 Concrete: {1841 parents: 0,1842 interior: 'Here',1843 },1844 },1845 testAmount,1846 );18471848 let maliciousXcmProgramFullIdSent: any;1849 let maliciousXcmProgramHereIdSent: any;1850 const maxWaitBlocks = 3;18511852 1853 await usingAstarPlaygrounds(astarUrl, async (helper) => {1854 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18551856 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1857 });18581859 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1860 && event.outcome.isUntrustedReserveLocation);18611862 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1863 expect(accountBalance).to.be.equal(0n);18641865 1866 await usingAstarPlaygrounds(astarUrl, async (helper) => {1867 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18681869 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1870 });18711872 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1873 && event.outcome.isUntrustedReserveLocation);18741875 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1876 expect(accountBalance).to.be.equal(0n);1877 });1878});