1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {XcmV2TraitsError} from '../interfaces';20import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';21import {DevUniqueHelper} from '../util/playgrounds/unique.dev';2223const QUARTZ_CHAIN = 2095;24const STATEMINE_CHAIN = 1000;25const KARURA_CHAIN = 2000;26const MOONRIVER_CHAIN = 2023;27const SHIDEN_CHAIN = 2007;2829const STATEMINE_PALLET_INSTANCE = 50;3031const relayUrl = config.relayUrl;32const statemineUrl = config.statemineUrl;33const karuraUrl = config.karuraUrl;34const moonriverUrl = config.moonriverUrl;35const shidenUrl = config.shidenUrl;3637const RELAY_DECIMALS = 12;38const STATEMINE_DECIMALS = 12;39const KARURA_DECIMALS = 12;40const SHIDEN_DECIMALS = 18n;41const QTZ_DECIMALS = 18n;4243const TRANSFER_AMOUNT = 2000000000000000000000000n;4445const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4647const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4849const USDT_ASSET_ID = 100;50const USDT_ASSET_METADATA_DECIMALS = 18;51const USDT_ASSET_METADATA_NAME = 'USDT';52const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';53const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;54const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5556const SAFE_XCM_VERSION = 2;5758describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {59 let alice: IKeyringPair;60 let bob: IKeyringPair;6162 let balanceStmnBefore: bigint;63 let balanceStmnAfter: bigint;6465 let balanceQuartzBefore: bigint;66 let balanceQuartzAfter: bigint;67 let balanceQuartzFinal: bigint;6869 let balanceBobBefore: bigint;70 let balanceBobAfter: bigint;71 let balanceBobFinal: bigint;7273 let balanceBobRelayTokenBefore: bigint;74 let balanceBobRelayTokenAfter: bigint;757677 before(async () => {78 await usingPlaygrounds(async (helper, privateKey) => {79 alice = await privateKey('//Alice');80 bob = await privateKey('//Bob'); 8182 83 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);84 });8586 await usingRelayPlaygrounds(relayUrl, async (helper) => {87 88 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);89 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);90 });9192 await usingStateminePlaygrounds(statemineUrl, async (helper) => {93 const sovereignFundingAmount = 3_500_000_000n;9495 await helper.assets.create(96 alice,97 USDT_ASSET_ID,98 alice.address,99 USDT_ASSET_METADATA_MINIMAL_BALANCE,100 );101 await helper.assets.setMetadata(102 alice,103 USDT_ASSET_ID,104 USDT_ASSET_METADATA_NAME,105 USDT_ASSET_METADATA_DESCRIPTION,106 USDT_ASSET_METADATA_DECIMALS,107 );108 await helper.assets.mint(109 alice,110 USDT_ASSET_ID,111 alice.address,112 USDT_ASSET_AMOUNT,113 );114115 116 117 118 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);119 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);120 });121122123 await usingPlaygrounds(async (helper) => {124 const location = {125 V2: {126 parents: 1,127 interior: {X3: [128 {129 Parachain: STATEMINE_CHAIN,130 },131 {132 PalletInstance: STATEMINE_PALLET_INSTANCE,133 },134 {135 GeneralIndex: USDT_ASSET_ID,136 },137 ]},138 },139 };140141 const metadata =142 {143 name: USDT_ASSET_ID,144 symbol: USDT_ASSET_METADATA_NAME,145 decimals: USDT_ASSET_METADATA_DECIMALS,146 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,147 };148 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);149 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);150 });151152153 154 155 await usingRelayPlaygrounds(relayUrl, async (helper) => {156 const destination = {157 V2: {158 parents: 0,159 interior: {X1: {160 Parachain: QUARTZ_CHAIN,161 },162 },163 }};164165 const beneficiary = {166 V2: {167 parents: 0,168 interior: {X1: {169 AccountId32: {170 network: 'Any',171 id: alice.addressRaw,172 },173 }},174 },175 };176177 const assets = {178 V2: [179 {180 id: {181 Concrete: {182 parents: 0,183 interior: 'Here',184 },185 },186 fun: {187 Fungible: TRANSFER_AMOUNT_RELAY,188 },189 },190 ],191 };192193 const feeAssetItem = 0;194195 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');196 });197198 });199200 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {201 await usingStateminePlaygrounds(statemineUrl, async (helper) => {202 const dest = {203 V2: {204 parents: 1,205 interior: {X1: {206 Parachain: QUARTZ_CHAIN,207 },208 },209 }};210211 const beneficiary = {212 V2: {213 parents: 0,214 interior: {X1: {215 AccountId32: {216 network: 'Any',217 id: alice.addressRaw,218 },219 }},220 },221 };222223 const assets = {224 V2: [225 {226 id: {227 Concrete: {228 parents: 0,229 interior: {230 X2: [231 {232 PalletInstance: STATEMINE_PALLET_INSTANCE,233 },234 {235 GeneralIndex: USDT_ASSET_ID,236 },237 ]},238 },239 },240 fun: {241 Fungible: TRANSFER_AMOUNT,242 },243 },244 ],245 };246247 const feeAssetItem = 0;248249 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);250 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');251252 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);253254 255 console.log(256 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',257 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),258 );259 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;260261 });262263264 265 await helper.wait.newBlocks(3);266267 268 const free = await helper.ft.getBalance(1, {Substrate: alice.address});269270 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);271272 console.log(273 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',274 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),275 );276 console.log(277 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',278 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),279 );280 281 expect(free).to.be.equal(TRANSFER_AMOUNT);282 283 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;284 });285286 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {287 const destination = {288 V2: {289 parents: 1,290 interior: {X2: [291 {292 Parachain: STATEMINE_CHAIN,293 },294 {295 AccountId32: {296 network: 'Any',297 id: alice.addressRaw,298 },299 },300 ]},301 },302 };303304 const relayFee = 400_000_000_000_000n;305 const currencies: [any, bigint][] = [306 [307 {308 ForeignAssetId: 0,309 },310 TRANSFER_AMOUNT,311 ],312 [313 {314 NativeAssetId: 'Parent',315 },316 relayFee,317 ],318 ];319320 const feeItem = 1;321322 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');323324 325 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);326 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));327 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;328329 await usingStateminePlaygrounds(statemineUrl, async (helper) => {330 await helper.wait.newBlocks(3);331332 333 334 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;335 });336 });337338 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {339 balanceBobBefore = await helper.balance.getSubstrate(bob.address);340 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});341342 await usingRelayPlaygrounds(relayUrl, async (helper) => {343 const destination = {344 V2: {345 parents: 0,346 interior: {X1: {347 Parachain: QUARTZ_CHAIN,348 },349 },350 }};351352 const beneficiary = {353 V2: {354 parents: 0,355 interior: {X1: {356 AccountId32: {357 network: 'Any',358 id: bob.addressRaw,359 },360 }},361 },362 };363364 const assets = {365 V2: [366 {367 id: {368 Concrete: {369 parents: 0,370 interior: 'Here',371 },372 },373 fun: {374 Fungible: TRANSFER_AMOUNT_RELAY,375 },376 },377 ],378 };379380 const feeAssetItem = 0;381382 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');383 });384385 await helper.wait.newBlocks(3);386387 balanceBobAfter = await helper.balance.getSubstrate(bob.address);388 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});389390 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;391 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;392 console.log(393 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',394 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),395 );396 console.log(397 '[Relay (Westend) -> Quartz] transaction fees: %s WND',398 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),399 );400 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);401 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;402 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;403 });404405 itSub('Should connect and send Relay token back', async ({helper}) => {406 let relayTokenBalanceBefore: bigint;407 let relayTokenBalanceAfter: bigint;408 await usingRelayPlaygrounds(relayUrl, async (helper) => {409 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);410 });411412 const destination = {413 V2: {414 parents: 1,415 interior: {416 X1:{417 AccountId32: {418 network: 'Any',419 id: bob.addressRaw,420 },421 },422 },423 },424 };425426 const currencies: any = [427 [428 {429 NativeAssetId: 'Parent',430 },431 TRANSFER_AMOUNT_RELAY,432 ],433 ];434435 const feeItem = 0;436437 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');438439 balanceBobFinal = await helper.balance.getSubstrate(bob.address);440 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));441442 await usingRelayPlaygrounds(relayUrl, async (helper) => {443 await helper.wait.newBlocks(10);444 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);445446 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;447 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));448 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;449 });450 });451});452453describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {454 let alice: IKeyringPair;455 let randomAccount: IKeyringPair;456457 let balanceQuartzTokenInit: bigint;458 let balanceQuartzTokenMiddle: bigint;459 let balanceQuartzTokenFinal: bigint;460 let balanceKaruraTokenInit: bigint;461 let balanceKaruraTokenMiddle: bigint;462 let balanceKaruraTokenFinal: bigint;463 let balanceQuartzForeignTokenInit: bigint;464 let balanceQuartzForeignTokenMiddle: bigint;465 let balanceQuartzForeignTokenFinal: bigint;466467 468 469 470 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;471472 const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;473474 before(async () => {475 await usingPlaygrounds(async (helper, privateKey) => {476 alice = await privateKey('//Alice');477 [randomAccount] = await helper.arrange.createAccounts([0n], alice);478479 480 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481 });482483 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {484 const destination = {485 V1: {486 parents: 1,487 interior: {488 X1: {489 Parachain: QUARTZ_CHAIN,490 },491 },492 },493 };494495 const metadata = {496 name: 'Quartz',497 symbol: 'QTZ',498 decimals: 18,499 minimalBalance: 1000000000000000000n,500 };501502 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);503 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);504 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);505 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});506 });507508 await usingPlaygrounds(async (helper) => {509 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);510 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);511 });512 });513514 itSub('Should connect and send QTZ to Karura', async ({helper}) => {515 const destination = {516 V2: {517 parents: 1,518 interior: {519 X1: {520 Parachain: KARURA_CHAIN,521 },522 },523 },524 };525526 const beneficiary = {527 V2: {528 parents: 0,529 interior: {530 X1: {531 AccountId32: {532 network: 'Any',533 id: randomAccount.addressRaw,534 },535 },536 },537 },538 };539540 const assets = {541 V2: [542 {543 id: {544 Concrete: {545 parents: 0,546 interior: 'Here',547 },548 },549 fun: {550 Fungible: TRANSFER_AMOUNT,551 },552 },553 ],554 };555556 const feeAssetItem = 0;557558 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');559 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);560561 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;562 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;563 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));564565 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {566 await helper.wait.newBlocks(3);567568 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});569 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);570571 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;572 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;573 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;574575 console.log(576 '[Quartz -> Karura] transaction fees on Karura: %s KAR',577 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),578 );579 console.log(580 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',581 helper.util.bigIntToDecimals(karUnqFees),582 );583 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));584 expect(karFees == 0n).to.be.true;585 expect(586 karUnqFees == expectedKaruraIncomeFee,587 'Karura took different income fee, check the Karura foreign asset config',588 ).to.be.true;589 });590 });591592 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {593 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {594 const destination = {595 V1: {596 parents: 1,597 interior: {598 X2: [599 {Parachain: QUARTZ_CHAIN},600 {601 AccountId32: {602 network: 'Any',603 id: randomAccount.addressRaw,604 },605 },606 ],607 },608 },609 };610611 const id = {612 ForeignAsset: 0,613 };614615 await helper.xTokens.transfer(randomAccount, id, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');616 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);617 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);618619 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;620 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;621622 console.log(623 '[Karura -> Quartz] transaction fees on Karura: %s KAR',624 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),625 );626 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));627628 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;629 expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;630 });631632 await helper.wait.newBlocks(3);633634 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);635 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;636 expect(actuallyDelivered > 0).to.be.true;637638 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));639640 const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;641 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));642 expect(qtzFees == 0n).to.be.true;643 });644645 itSub('Karura can send only up to its balance', async ({helper}) => {646 647 const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);648 const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);649 await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);650651 const moreThanKaruraHas = karuraBalance * 2n;652653 let targetAccountBalance = 0n;654 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);655656 const quartzMultilocation = {657 V1: {658 parents: 1,659 interior: {660 X1: {Parachain: QUARTZ_CHAIN},661 },662 },663 };664665 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(666 targetAccount.addressRaw,667 {668 Concrete: {669 parents: 0,670 interior: 'Here',671 },672 },673 moreThanKaruraHas,674 );675676 677 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {678 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);679 });680681 const maxWaitBlocks = 3;682 const outcomeField = 1;683684 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(685 maxWaitBlocks,686 'xcmpQueue',687 'Fail',688 outcomeField,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', got '${xcmpQueueFailEvent!.toHuman()}'`,699 ).to.be.true;700701 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);702 expect(targetAccountBalance).to.be.equal(0n);703704 705 const validTransferAmount = karuraBalance / 2n;706 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(707 targetAccount.addressRaw,708 {709 Concrete: {710 parents: 0,711 interior: 'Here',712 },713 },714 validTransferAmount,715 );716717 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {718 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);719 });720721 await helper.wait.newBlocks(maxWaitBlocks);722723 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);724 expect(targetAccountBalance).to.be.equal(validTransferAmount);725 });726727 itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {728 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);729 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);730731 const quartzMultilocation = {732 V1: {733 parents: 1,734 interior: {735 X1: {736 Parachain: QUARTZ_CHAIN,737 },738 },739 },740 };741742 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(743 targetAccount.addressRaw,744 {745 Concrete: {746 parents: 1,747 interior: {748 X1: {749 Parachain: QUARTZ_CHAIN,750 },751 },752 },753 },754 testAmount,755 );756757 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(758 targetAccount.addressRaw,759 {760 Concrete: {761 parents: 0,762 interior: 'Here',763 },764 },765 testAmount,766 );767768 769 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {770 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);771 });772773 const maxWaitBlocks = 3;774 const outcomeField = 1;775776 let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(777 maxWaitBlocks,778 'xcmpQueue',779 'Fail',780 outcomeField,781 );782783 expect(784 xcmpQueueFailEvent != null,785 '\'xcmpQueue.FailEvent\' event is expected',786 ).to.be.true;787788 expect(789 xcmpQueueFailEvent!.isUntrustedReserveLocation,790 `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,791 ).to.be.true;792793 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);794 expect(accountBalance).to.be.equal(0n);795796 797 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {798 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);799 });800801 xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(802 maxWaitBlocks,803 'xcmpQueue',804 'Fail',805 outcomeField,806 );807808 expect(809 xcmpQueueFailEvent != null,810 '\'xcmpQueue.FailEvent\' event is expected',811 ).to.be.true;812813 expect(814 xcmpQueueFailEvent!.isUntrustedReserveLocation,815 `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,816 ).to.be.true;817818 accountBalance = await helper.balance.getSubstrate(targetAccount.address);819 expect(accountBalance).to.be.equal(0n);820 });821});822823824825describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {826 let alice: IKeyringPair;827 let alith: IKeyringPair;828829 const testAmount = 100_000_000_000n;830 let quartzParachainJunction;831 let quartzAccountJunction;832833 let quartzParachainMultilocation: any;834 let quartzAccountMultilocation: any;835 let quartzCombinedMultilocation: any;836837 before(async () => {838 await usingPlaygrounds(async (helper, privateKey) => {839 alice = await privateKey('//Alice');840841 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};842 quartzAccountJunction = {843 AccountId32: {844 network: 'Any',845 id: alice.addressRaw,846 },847 };848849 quartzParachainMultilocation = {850 V1: {851 parents: 1,852 interior: {853 X1: quartzParachainJunction,854 },855 },856 };857858 quartzAccountMultilocation = {859 V1: {860 parents: 0,861 interior: {862 X1: quartzAccountJunction,863 },864 },865 };866867 quartzCombinedMultilocation = {868 V1: {869 parents: 1,870 interior: {871 X2: [quartzParachainJunction, quartzAccountJunction],872 },873 },874 };875876 877 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);878 });879880 881 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {882 alith = helper.account.alithAccount();883 });884 });885886 const expectFailedToTransact = async (network: string, helper: DevUniqueHelper) => {887 const maxWaitBlocks = 3;888 const outcomeField = 1;889890 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(891 maxWaitBlocks,892 'xcmpQueue',893 'Fail',894 outcomeField,895 );896897 expect(898 xcmpQueueFailEvent != null,899 `[reject ${network} tokens] 'xcmpQueue.FailEvent' event is expected`,900 ).to.be.true;901902 expect(903 xcmpQueueFailEvent!.isFailedToTransactAsset,904 `[reject ${network} tokens] The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,905 ).to.be.true;906 };907908 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {909 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {910 const id = {911 Token: 'KAR',912 };913 const destination = quartzCombinedMultilocation;914 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');915 });916917 await expectFailedToTransact('KAR', helper);918 });919920 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {921 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {922 const id = 'SelfReserve';923 const destination = quartzCombinedMultilocation;924 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');925 });926927 await expectFailedToTransact('MOVR', helper);928 });929930 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {931 await usingShidenPlaygrounds(shidenUrl, async (helper) => {932 const destinationParachain = quartzParachainMultilocation;933 const beneficiary = quartzAccountMultilocation;934 const assets = {935 V1: [{936 id: {937 Concrete: {938 parents: 0,939 interior: 'Here',940 },941 },942 fun: {943 Fungible: testAmount,944 },945 }],946 };947 const feeAssetItem = 0;948949 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [950 destinationParachain,951 beneficiary,952 assets,953 feeAssetItem,954 ]);955 });956957 await expectFailedToTransact('SDN', helper);958 });959});960961describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {962 963 let alice: IKeyringPair;964 let quartzAssetLocation;965966 let randomAccountQuartz: IKeyringPair;967 let randomAccountMoonriver: IKeyringPair;968969 970 let assetId: string;971972 const quartzAssetMetadata = {973 name: 'xcQuartz',974 symbol: 'xcQTZ',975 decimals: 18,976 isFrozen: false,977 minimalBalance: 1n,978 };979980 let balanceQuartzTokenInit: bigint;981 let balanceQuartzTokenMiddle: bigint;982 let balanceQuartzTokenFinal: bigint;983 let balanceForeignQtzTokenInit: bigint;984 let balanceForeignQtzTokenMiddle: bigint;985 let balanceForeignQtzTokenFinal: bigint;986 let balanceMovrTokenInit: bigint;987 let balanceMovrTokenMiddle: bigint;988 let balanceMovrTokenFinal: bigint;989990 before(async () => {991 await usingPlaygrounds(async (helper, privateKey) => {992 alice = await privateKey('//Alice');993 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);994995 balanceForeignQtzTokenInit = 0n;996997 998 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);999 });10001001 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1002 const alithAccount = helper.account.alithAccount();1003 const baltatharAccount = helper.account.baltatharAccount();1004 const dorothyAccount = helper.account.dorothyAccount();10051006 randomAccountMoonriver = helper.account.create();10071008 1009 console.log('Sponsoring Dorothy.......');1010 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1011 console.log('Sponsoring Dorothy.......DONE');1012 10131014 quartzAssetLocation = {1015 XCM: {1016 parents: 1,1017 interior: {X1: {Parachain: QUARTZ_CHAIN}},1018 },1019 };1020 const existentialDeposit = 1n;1021 const isSufficient = true;1022 const unitsPerSecond = 1n;1023 const numAssetsWeightHint = 0;10241025 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1026 location: quartzAssetLocation,1027 metadata: quartzAssetMetadata,1028 existentialDeposit,1029 isSufficient,1030 unitsPerSecond,1031 numAssetsWeightHint,1032 });10331034 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);10351036 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);10371038 1039 console.log('Acquire Quartz AssetId Info on Moonriver.......');10401041 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10421043 console.log('QTZ asset ID is %s', assetId);1044 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1045 10461047 1048 console.log('Sponsoring random Account.......');1049 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1050 console.log('Sponsoring random Account.......DONE');1051 10521053 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1054 });10551056 await usingPlaygrounds(async (helper) => {1057 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1058 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1059 });1060 });10611062 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1063 const currencyId = {1064 NativeAssetId: 'Here',1065 };1066 const dest = {1067 V2: {1068 parents: 1,1069 interior: {1070 X2: [1071 {Parachain: MOONRIVER_CHAIN},1072 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1073 ],1074 },1075 },1076 };1077 const amount = TRANSFER_AMOUNT;10781079 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10801081 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1082 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10831084 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1085 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1086 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10871088 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1089 await helper.wait.newBlocks(3);10901091 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10921093 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1094 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1095 expect(movrFees == 0n).to.be.true;10961097 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; 1098 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1099 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1100 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1101 });1102 });11031104 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1105 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1106 const asset = {1107 V1: {1108 id: {1109 Concrete: {1110 parents: 1,1111 interior: {1112 X1: {Parachain: QUARTZ_CHAIN},1113 },1114 },1115 },1116 fun: {1117 Fungible: TRANSFER_AMOUNT,1118 },1119 },1120 };1121 const destination = {1122 V1: {1123 parents: 1,1124 interior: {1125 X2: [1126 {Parachain: QUARTZ_CHAIN},1127 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1128 ],1129 },1130 },1131 };11321133 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');11341135 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);11361137 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1138 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1139 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;11401141 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11421143 expect(qtzRandomAccountAsset).to.be.null;11441145 balanceForeignQtzTokenFinal = 0n;11461147 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1148 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1149 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1150 });11511152 await helper.wait.newBlocks(3);11531154 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1155 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1156 expect(actuallyDelivered > 0).to.be.true;11571158 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11591160 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1161 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1162 expect(qtzFees == 0n).to.be.true;1163 });11641165 itSub('Moonriver can send only up to its balance', async ({helper}) => {1166 1167 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1168 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1169 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11701171 const moreThanMoonriverHas = moonriverBalance * 2n;11721173 let targetAccountBalance = 0n;1174 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11751176 const quartzMultilocation = {1177 V1: {1178 parents: 1,1179 interior: {1180 X1: {Parachain: QUARTZ_CHAIN},1181 },1182 },1183 };11841185 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1186 targetAccount.addressRaw,1187 {1188 Concrete: {1189 parents: 0,1190 interior: 'Here',1191 },1192 },1193 moreThanMoonriverHas,1194 );11951196 1197 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1198 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11991200 1201 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1202 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);1203 });12041205 const maxWaitBlocks = 3;1206 const outcomeField = 1;12071208 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1209 maxWaitBlocks,1210 'xcmpQueue',1211 'Fail',1212 outcomeField,1213 );12141215 expect(1216 xcmpQueueFailEvent != null,1217 '\'xcmpQueue.FailEvent\' event is expected',1218 ).to.be.true;12191220 expect(1221 xcmpQueueFailEvent!.isFailedToTransactAsset,1222 `The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,1223 ).to.be.true;12241225 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1226 expect(targetAccountBalance).to.be.equal(0n);12271228 1229 const validTransferAmount = moonriverBalance / 2n;1230 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1231 targetAccount.addressRaw,1232 {1233 Concrete: {1234 parents: 0,1235 interior: 'Here',1236 },1237 },1238 validTransferAmount,1239 );12401241 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1242 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);12431244 1245 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1246 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1247 });12481249 await helper.wait.newBlocks(maxWaitBlocks);12501251 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1252 expect(targetAccountBalance).to.be.equal(validTransferAmount);1253 });12541255 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1256 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1257 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12581259 const quartzMultilocation = {1260 V1: {1261 parents: 1,1262 interior: {1263 X1: {1264 Parachain: QUARTZ_CHAIN,1265 },1266 },1267 },1268 };12691270 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1271 targetAccount.addressRaw,1272 {1273 Concrete: {1274 parents: 0,1275 interior: {1276 X1: {1277 Parachain: QUARTZ_CHAIN,1278 },1279 },1280 },1281 },1282 testAmount,1283 );12841285 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1286 targetAccount.addressRaw,1287 {1288 Concrete: {1289 parents: 0,1290 interior: 'Here',1291 },1292 },1293 testAmount,1294 );12951296 1297 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1298 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12991300 1301 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1302 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);1303 });13041305 const maxWaitBlocks = 3;1306 const outcomeField = 1;13071308 let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1309 maxWaitBlocks,1310 'xcmpQueue',1311 'Fail',1312 outcomeField,1313 );13141315 expect(1316 xcmpQueueFailEvent != null,1317 '\'xcmpQueue.FailEvent\' event is expected',1318 ).to.be.true;13191320 expect(1321 xcmpQueueFailEvent!.isUntrustedReserveLocation,1322 `The XCM error should be 'isUntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,1323 ).to.be.true;13241325 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1326 expect(accountBalance).to.be.equal(0n);13271328 1329 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1330 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);13311332 1333 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1334 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);1335 });13361337 xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1338 maxWaitBlocks,1339 'xcmpQueue',1340 'Fail',1341 outcomeField,1342 );13431344 expect(1345 xcmpQueueFailEvent != null,1346 '\'xcmpQueue.FailEvent\' event is expected',1347 ).to.be.true;13481349 expect(1350 xcmpQueueFailEvent!.isUntrustedReserveLocation,1351 `The XCM error should be 'isUntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,1352 ).to.be.true;13531354 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1355 expect(accountBalance).to.be.equal(0n);1356 });1357});13581359describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1360 let alice: IKeyringPair;1361 let sender: IKeyringPair;13621363 const QTZ_ASSET_ID_ON_SHIDEN = 1;1364 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;13651366 1367 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); 1368 const unitsPerSecond = 228_000_000_000n; 1369 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); 1370 const qtzToShidenArrived = 9_999_999_999_088_000_000n; 13711372 1373 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); 1374 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; 13751376 let balanceAfterQuartzToShidenXCM: bigint;13771378 before(async () => {1379 await usingPlaygrounds(async (helper, privateKey) => {1380 alice = await privateKey('//Alice');1381 [sender] = await helper.arrange.createAccounts([100n], alice);1382 console.log('sender', sender.address);13831384 1385 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1386 });13871388 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1389 console.log('1. Create foreign asset and metadata');1390 1391 await helper.assets.create(1392 alice,1393 QTZ_ASSET_ID_ON_SHIDEN,1394 alice.address,1395 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1396 );13971398 await helper.assets.setMetadata(1399 alice,1400 QTZ_ASSET_ID_ON_SHIDEN,1401 'Cross chain QTZ',1402 'xcQTZ',1403 Number(QTZ_DECIMALS),1404 );14051406 console.log('2. Register asset location on Shiden');1407 const assetLocation = {1408 V1: {1409 parents: 1,1410 interior: {1411 X1: {1412 Parachain: QUARTZ_CHAIN,1413 },1414 },1415 },1416 };14171418 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);14191420 console.log('3. Set QTZ payment for XCM execution on Shiden');1421 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);14221423 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1424 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1425 });1426 });14271428 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1429 const destination = {1430 V2: {1431 parents: 1,1432 interior: {1433 X1: {1434 Parachain: SHIDEN_CHAIN,1435 },1436 },1437 },1438 };14391440 const beneficiary = {1441 V2: {1442 parents: 0,1443 interior: {1444 X1: {1445 AccountId32: {1446 network: 'Any',1447 id: sender.addressRaw,1448 },1449 },1450 },1451 },1452 };14531454 const assets = {1455 V2: [1456 {1457 id: {1458 Concrete: {1459 parents: 0,1460 interior: 'Here',1461 },1462 },1463 fun: {1464 Fungible: qtzToShidenTransferred,1465 },1466 },1467 ],1468 };14691470 1471 const balanceBefore = await helper.balance.getSubstrate(sender.address);1472 console.log(`Initial balance is: ${balanceBefore}`);14731474 const feeAssetItem = 0;1475 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14761477 1478 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1479 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1480 console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1481 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14821483 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1484 await helper.wait.newBlocks(3);1485 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1486 const shidenBalance = await helper.balance.getSubstrate(sender.address);14871488 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1489 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14901491 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1492 1493 expect(shidenBalance).to.eq(shidenInitialBalance);1494 });1495 });14961497 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1498 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1499 const destination = {1500 V1: {1501 parents: 1,1502 interior: {1503 X1: {1504 Parachain: QUARTZ_CHAIN,1505 },1506 },1507 },1508 };15091510 const beneficiary = {1511 V1: {1512 parents: 0,1513 interior: {1514 X1: {1515 AccountId32: {1516 network: 'Any',1517 id: sender.addressRaw,1518 },1519 },1520 },1521 },1522 };15231524 const assets = {1525 V1: [1526 {1527 id: {1528 Concrete: {1529 parents: 1,1530 interior: {1531 X1: {1532 Parachain: QUARTZ_CHAIN,1533 },1534 },1535 },1536 },1537 fun: {1538 Fungible: qtzFromShidenTransfered,1539 },1540 },1541 ],1542 };15431544 1545 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1546 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1547 expect(balanceSDNbefore).to.eq(shidenInitialBalance);15481549 const feeAssetItem = 0;1550 1551 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);15521553 1554 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1555 const balanceSDN = await helper.balance.getSubstrate(sender.address);1556 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);15571558 1559 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1560 1561 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1562 });15631564 await helper.wait.newBlocks(3);1565 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1566 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1567 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1568 });15691570 itSub('Shiden can send only up to its balance', async ({helper}) => {1571 1572 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1573 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1574 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15751576 const moreThanShidenHas = shidenBalance * 2n;15771578 let targetAccountBalance = 0n;1579 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15801581 const quartzMultilocation = {1582 V1: {1583 parents: 1,1584 interior: {1585 X1: {Parachain: QUARTZ_CHAIN},1586 },1587 },1588 };15891590 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1591 targetAccount.addressRaw,1592 {1593 Concrete: {1594 parents: 0,1595 interior: 'Here',1596 },1597 },1598 moreThanShidenHas,1599 );16001601 1602 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1603 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);1604 });16051606 const maxWaitBlocks = 3;1607 const outcomeField = 1;16081609 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1610 maxWaitBlocks,1611 'xcmpQueue',1612 'Fail',1613 outcomeField,1614 );16151616 expect(1617 xcmpQueueFailEvent != null,1618 '\'xcmpQueue.FailEvent\' event is expected',1619 ).to.be.true;16201621 expect(1622 xcmpQueueFailEvent!.isFailedToTransactAsset,1623 `The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,1624 ).to.be.true;16251626 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1627 expect(targetAccountBalance).to.be.equal(0n);16281629 1630 const validTransferAmount = shidenBalance / 2n;1631 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1632 targetAccount.addressRaw,1633 {1634 Concrete: {1635 parents: 0,1636 interior: 'Here',1637 },1638 },1639 validTransferAmount,1640 );16411642 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1643 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1644 });16451646 await helper.wait.newBlocks(maxWaitBlocks);16471648 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1649 expect(targetAccountBalance).to.be.equal(validTransferAmount);1650 });16511652 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1653 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1654 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);16551656 const quartzMultilocation = {1657 V1: {1658 parents: 1,1659 interior: {1660 X1: {1661 Parachain: QUARTZ_CHAIN,1662 },1663 },1664 },1665 };16661667 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1668 targetAccount.addressRaw,1669 {1670 Concrete: {1671 parents: 1,1672 interior: {1673 X1: {1674 Parachain: QUARTZ_CHAIN,1675 },1676 },1677 },1678 },1679 testAmount,1680 );16811682 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1683 targetAccount.addressRaw,1684 {1685 Concrete: {1686 parents: 0,1687 interior: 'Here',1688 },1689 },1690 testAmount,1691 );16921693 1694 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1695 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);1696 });16971698 const maxWaitBlocks = 3;1699 const outcomeField = 1;17001701 let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1702 maxWaitBlocks,1703 'xcmpQueue',1704 'Fail',1705 outcomeField,1706 );17071708 expect(1709 xcmpQueueFailEvent != null,1710 '\'xcmpQueue.FailEvent\' event is expected',1711 ).to.be.true;17121713 expect(1714 xcmpQueueFailEvent!.isUntrustedReserveLocation,1715 `The XCM error should be 'isUntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,1716 ).to.be.true;17171718 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1719 expect(accountBalance).to.be.equal(0n);17201721 1722 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1723 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);1724 });17251726 xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1727 maxWaitBlocks,1728 'xcmpQueue',1729 'Fail',1730 outcomeField,1731 );17321733 expect(1734 xcmpQueueFailEvent != null,1735 '\'xcmpQueue.FailEvent\' event is expected',1736 ).to.be.true;17371738 expect(1739 xcmpQueueFailEvent!.isUntrustedReserveLocation,1740 `The XCM error should be 'isUntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,1741 ).to.be.true;17421743 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1744 expect(accountBalance).to.be.equal(0n);1745 });1746});