1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';2122const QUARTZ_CHAIN = 2095;23const STATEMINE_CHAIN = 1000;24const KARURA_CHAIN = 2000;25const MOONRIVER_CHAIN = 2023;26const SHIDEN_CHAIN = 2007;2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;34const shidenUrl = config.shidenUrl;3536const RELAY_DECIMALS = 12;37const STATEMINE_DECIMALS = 12;38const KARURA_DECIMALS = 12;39const SHIDEN_DECIMALS = 18n;40const QTZ_DECIMALS = 18n;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4546const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4748const USDT_ASSET_ID = 100;49const USDT_ASSET_METADATA_DECIMALS = 18;50const USDT_ASSET_METADATA_NAME = 'USDT';51const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';52const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;53const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5455const SAFE_XCM_VERSION = 2;5657describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {58 let alice: IKeyringPair;59 let bob: IKeyringPair;6061 let balanceStmnBefore: bigint;62 let balanceStmnAfter: bigint;6364 let balanceQuartzBefore: bigint;65 let balanceQuartzAfter: bigint;66 let balanceQuartzFinal: bigint;6768 let balanceBobBefore: bigint;69 let balanceBobAfter: bigint;70 let balanceBobFinal: bigint;7172 let balanceBobRelayTokenBefore: bigint;73 let balanceBobRelayTokenAfter: bigint;747576 before(async () => {77 await usingPlaygrounds(async (helper, privateKey) => {78 alice = await privateKey('//Alice');79 bob = await privateKey('//Bob'); 8081 82 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);83 });8485 await usingRelayPlaygrounds(relayUrl, async (helper) => {86 87 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);88 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);89 });9091 await usingStateminePlaygrounds(statemineUrl, async (helper) => {92 const sovereignFundingAmount = 3_500_000_000n;9394 await helper.assets.create(95 alice,96 USDT_ASSET_ID,97 alice.address,98 USDT_ASSET_METADATA_MINIMAL_BALANCE,99 );100 await helper.assets.setMetadata(101 alice,102 USDT_ASSET_ID,103 USDT_ASSET_METADATA_NAME,104 USDT_ASSET_METADATA_DESCRIPTION,105 USDT_ASSET_METADATA_DECIMALS,106 );107 await helper.assets.mint(108 alice,109 USDT_ASSET_ID,110 alice.address,111 USDT_ASSET_AMOUNT,112 );113114 115 116 117 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);118 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);119 });120121122 await usingPlaygrounds(async (helper) => {123 const location = {124 V2: {125 parents: 1,126 interior: {X3: [127 {128 Parachain: STATEMINE_CHAIN,129 },130 {131 PalletInstance: STATEMINE_PALLET_INSTANCE,132 },133 {134 GeneralIndex: USDT_ASSET_ID,135 },136 ]},137 },138 };139140 const metadata =141 {142 name: USDT_ASSET_ID,143 symbol: USDT_ASSET_METADATA_NAME,144 decimals: USDT_ASSET_METADATA_DECIMALS,145 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,146 };147 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);148 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);149 });150151152 153 154 await usingRelayPlaygrounds(relayUrl, async (helper) => {155 const destination = {156 V2: {157 parents: 0,158 interior: {X1: {159 Parachain: QUARTZ_CHAIN,160 },161 },162 }};163164 const beneficiary = {165 V2: {166 parents: 0,167 interior: {X1: {168 AccountId32: {169 network: 'Any',170 id: alice.addressRaw,171 },172 }},173 },174 };175176 const assets = {177 V2: [178 {179 id: {180 Concrete: {181 parents: 0,182 interior: 'Here',183 },184 },185 fun: {186 Fungible: TRANSFER_AMOUNT_RELAY,187 },188 },189 ],190 };191192 const feeAssetItem = 0;193194 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');195 });196197 });198199 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {200 await usingStateminePlaygrounds(statemineUrl, async (helper) => {201 const dest = {202 V2: {203 parents: 1,204 interior: {X1: {205 Parachain: QUARTZ_CHAIN,206 },207 },208 }};209210 const beneficiary = {211 V2: {212 parents: 0,213 interior: {X1: {214 AccountId32: {215 network: 'Any',216 id: alice.addressRaw,217 },218 }},219 },220 };221222 const assets = {223 V2: [224 {225 id: {226 Concrete: {227 parents: 0,228 interior: {229 X2: [230 {231 PalletInstance: STATEMINE_PALLET_INSTANCE,232 },233 {234 GeneralIndex: USDT_ASSET_ID,235 },236 ]},237 },238 },239 fun: {240 Fungible: TRANSFER_AMOUNT,241 },242 },243 ],244 };245246 const feeAssetItem = 0;247248 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);249 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');250251 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);252253 254 console.log(255 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',256 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),257 );258 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;259260 });261262263 264 await helper.wait.newBlocks(3);265266 267 const free = await helper.ft.getBalance(1, {Substrate: alice.address});268269 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);270271 console.log(272 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',273 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),274 );275 console.log(276 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',277 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),278 );279 280 expect(free).to.be.equal(TRANSFER_AMOUNT);281 282 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;283 });284285 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {286 const destination = {287 V2: {288 parents: 1,289 interior: {X2: [290 {291 Parachain: STATEMINE_CHAIN,292 },293 {294 AccountId32: {295 network: 'Any',296 id: alice.addressRaw,297 },298 },299 ]},300 },301 };302303 const relayFee = 400_000_000_000_000n;304 const currencies: [any, bigint][] = [305 [306 {307 ForeignAssetId: 0,308 },309 TRANSFER_AMOUNT,310 ],311 [312 {313 NativeAssetId: 'Parent',314 },315 relayFee,316 ],317 ];318319 const feeItem = 1;320321 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');322323 324 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);325 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));326 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;327328 await usingStateminePlaygrounds(statemineUrl, async (helper) => {329 await helper.wait.newBlocks(3);330331 332 333 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;334 });335 });336337 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {338 balanceBobBefore = await helper.balance.getSubstrate(bob.address);339 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});340341 await usingRelayPlaygrounds(relayUrl, async (helper) => {342 const destination = {343 V2: {344 parents: 0,345 interior: {X1: {346 Parachain: QUARTZ_CHAIN,347 },348 },349 }};350351 const beneficiary = {352 V2: {353 parents: 0,354 interior: {X1: {355 AccountId32: {356 network: 'Any',357 id: bob.addressRaw,358 },359 }},360 },361 };362363 const assets = {364 V2: [365 {366 id: {367 Concrete: {368 parents: 0,369 interior: 'Here',370 },371 },372 fun: {373 Fungible: TRANSFER_AMOUNT_RELAY,374 },375 },376 ],377 };378379 const feeAssetItem = 0;380381 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');382 });383384 await helper.wait.newBlocks(3);385386 balanceBobAfter = await helper.balance.getSubstrate(bob.address);387 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});388389 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;390 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;391 console.log(392 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',393 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),394 );395 console.log(396 '[Relay (Westend) -> Quartz] transaction fees: %s WND',397 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),398 );399 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);400 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;401 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;402 });403404 itSub('Should connect and send Relay token back', async ({helper}) => {405 let relayTokenBalanceBefore: bigint;406 let relayTokenBalanceAfter: bigint;407 await usingRelayPlaygrounds(relayUrl, async (helper) => {408 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);409 });410411 const destination = {412 V2: {413 parents: 1,414 interior: {415 X1:{416 AccountId32: {417 network: 'Any',418 id: bob.addressRaw,419 },420 },421 },422 },423 };424425 const currencies: any = [426 [427 {428 NativeAssetId: 'Parent',429 },430 TRANSFER_AMOUNT_RELAY,431 ],432 ];433434 const feeItem = 0;435436 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438 balanceBobFinal = await helper.balance.getSubstrate(bob.address);439 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441 await usingRelayPlaygrounds(relayUrl, async (helper) => {442 await helper.wait.newBlocks(10);443 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448 });449 });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {453 let alice: IKeyringPair;454 let randomAccount: IKeyringPair;455456 let balanceQuartzTokenInit: bigint;457 let balanceQuartzTokenMiddle: bigint;458 let balanceQuartzTokenFinal: bigint;459 let balanceKaruraTokenInit: bigint;460 let balanceKaruraTokenMiddle: bigint;461 let balanceKaruraTokenFinal: bigint;462 let balanceQuartzForeignTokenInit: bigint;463 let balanceQuartzForeignTokenMiddle: bigint;464 let balanceQuartzForeignTokenFinal: bigint;465466 467 468 469 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;470471 const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;472473 before(async () => {474 await usingPlaygrounds(async (helper, privateKey) => {475 alice = await privateKey('//Alice');476 [randomAccount] = await helper.arrange.createAccounts([0n], alice);477478 479 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);480 });481482 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {483 const destination = {484 V1: {485 parents: 1,486 interior: {487 X1: {488 Parachain: QUARTZ_CHAIN,489 },490 },491 },492 };493494 const metadata = {495 name: 'Quartz',496 symbol: 'QTZ',497 decimals: 18,498 minimalBalance: 1000000000000000000n,499 };500501 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);502 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);503 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);504 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});505 });506507 await usingPlaygrounds(async (helper) => {508 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);509 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);510 });511 });512513 itSub('Should connect and send QTZ to Karura', async ({helper}) => {514 const destination = {515 V2: {516 parents: 1,517 interior: {518 X1: {519 Parachain: KARURA_CHAIN,520 },521 },522 },523 };524525 const beneficiary = {526 V2: {527 parents: 0,528 interior: {529 X1: {530 AccountId32: {531 network: 'Any',532 id: randomAccount.addressRaw,533 },534 },535 },536 },537 };538539 const assets = {540 V2: [541 {542 id: {543 Concrete: {544 parents: 0,545 interior: 'Here',546 },547 },548 fun: {549 Fungible: TRANSFER_AMOUNT,550 },551 },552 ],553 };554555 const feeAssetItem = 0;556557 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');558 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);559560 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;561 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;562 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));563564 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {565 await helper.wait.newBlocks(3);566567 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});568 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);569570 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;571 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;572 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;573574 console.log(575 '[Quartz -> Karura] transaction fees on Karura: %s KAR',576 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),577 );578 console.log(579 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',580 helper.util.bigIntToDecimals(karUnqFees),581 );582 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));583 expect(karFees == 0n).to.be.true;584 expect(585 karUnqFees == expectedKaruraIncomeFee,586 'Karura took different income fee, check the Karura foreign asset config',587 ).to.be.true;588 });589 });590591 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {592 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {593 const destination = {594 V1: {595 parents: 1,596 interior: {597 X2: [598 {Parachain: QUARTZ_CHAIN},599 {600 AccountId32: {601 network: 'Any',602 id: randomAccount.addressRaw,603 },604 },605 ],606 },607 },608 };609610 const id = {611 ForeignAsset: 0,612 };613614 await helper.xTokens.transfer(randomAccount, id, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');615 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);616 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);617618 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;619 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;620621 console.log(622 '[Karura -> Quartz] transaction fees on Karura: %s KAR',623 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),624 );625 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));626627 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;628 expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;629 });630631 await helper.wait.newBlocks(3);632633 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);634 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;635 expect(actuallyDelivered > 0).to.be.true;636637 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));638639 const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;640 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));641 expect(qtzFees == 0n).to.be.true;642 });643644 itSub('Karura can send only up to its balance', async ({helper}) => {645 646 const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);647 const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);648 await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);649650 const moreThanKaruraHas = karuraBalance * 2n;651652 let targetAccountBalance = 0n;653 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);654655 const quartzMultilocation = {656 V1: {657 parents: 1,658 interior: {659 X1: {Parachain: QUARTZ_CHAIN},660 },661 },662 };663664 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(665 targetAccount.addressRaw,666 {667 Concrete: {668 parents: 0,669 interior: 'Here',670 },671 },672 moreThanKaruraHas,673 );674675 let maliciousXcmProgramSent: any;676 const maxWaitBlocks = 3;677678 679 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {680 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);681682 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);683 });684685 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {686 return event.messageHash() == maliciousXcmProgramSent.messageHash()687 && event.outcome().isFailedToTransactAsset;688 });689690 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);691 expect(targetAccountBalance).to.be.equal(0n);692693 694 const validTransferAmount = karuraBalance / 2n;695 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(696 targetAccount.addressRaw,697 {698 Concrete: {699 parents: 0,700 interior: 'Here',701 },702 },703 validTransferAmount,704 );705706 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {707 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);708 });709710 await helper.wait.newBlocks(maxWaitBlocks);711712 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);713 expect(targetAccountBalance).to.be.equal(validTransferAmount);714 });715716 itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {717 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);718 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);719720 const quartzMultilocation = {721 V1: {722 parents: 1,723 interior: {724 X1: {725 Parachain: QUARTZ_CHAIN,726 },727 },728 },729 };730731 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(732 targetAccount.addressRaw,733 {734 Concrete: {735 parents: 1,736 interior: {737 X1: {738 Parachain: QUARTZ_CHAIN,739 },740 },741 },742 },743 testAmount,744 );745746 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(747 targetAccount.addressRaw,748 {749 Concrete: {750 parents: 0,751 interior: 'Here',752 },753 },754 testAmount,755 );756757 let maliciousXcmProgramFullIdSent: any;758 let maliciousXcmProgramHereIdSent: any;759 const maxWaitBlocks = 3;760761 762 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {763 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);764765 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);766 });767768 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {769 return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()770 && event.outcome().isUntrustedReserveLocation;771 });772773 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);774 expect(accountBalance).to.be.equal(0n);775776 777 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {778 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);779780 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);781 });782783 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {784 return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()785 && event.outcome().isUntrustedReserveLocation;786 });787788 accountBalance = await helper.balance.getSubstrate(targetAccount.address);789 expect(accountBalance).to.be.equal(0n);790 });791});792793794795describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {796 let alice: IKeyringPair;797 let alith: IKeyringPair;798799 const testAmount = 100_000_000_000n;800 let quartzParachainJunction;801 let quartzAccountJunction;802803 let quartzParachainMultilocation: any;804 let quartzAccountMultilocation: any;805 let quartzCombinedMultilocation: any;806 let quartzCombinedMultilocationKarura: any; 807808 let messageSent: any;809810 const maxWaitBlocks = 3;811812 before(async () => {813 await usingPlaygrounds(async (helper, privateKey) => {814 alice = await privateKey('//Alice');815816 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};817 quartzAccountJunction = {818 AccountId32: {819 network: 'Any',820 id: alice.addressRaw,821 },822 };823824 quartzParachainMultilocation = {825 V2: {826 parents: 1,827 interior: {828 X1: quartzParachainJunction,829 },830 },831 };832833 quartzAccountMultilocation = {834 V2: {835 parents: 0,836 interior: {837 X1: quartzAccountJunction,838 },839 },840 };841842 quartzCombinedMultilocation = {843 V2: {844 parents: 1,845 interior: {846 X2: [quartzParachainJunction, quartzAccountJunction],847 },848 },849 };850851 quartzCombinedMultilocationKarura = {852 V1: {853 parents: 1,854 interior: {855 X2: [quartzParachainJunction, quartzAccountJunction],856 },857 },858 };859860 861 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);862 });863864 865 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {866 alith = helper.account.alithAccount();867 });868 });869870 const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {871 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {872 return event.messageHash() == messageSent.messageHash()873 && event.outcome().isFailedToTransactAsset;874 });875 };876877 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {878 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {879 const id = {880 Token: 'KAR',881 };882 const destination = quartzCombinedMultilocationKarura;883 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');884885 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);886 });887888 await expectFailedToTransact(helper, messageSent);889 });890891 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {892 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {893 const id = 'SelfReserve';894 const destination = quartzCombinedMultilocation;895 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');896897 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);898 });899900 await expectFailedToTransact(helper, messageSent);901 });902903 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {904 await usingShidenPlaygrounds(shidenUrl, async (helper) => {905 const destinationParachain = quartzParachainMultilocation;906 const beneficiary = quartzAccountMultilocation;907 const assets = {908 V2: [{909 id: {910 Concrete: {911 parents: 0,912 interior: 'Here',913 },914 },915 fun: {916 Fungible: testAmount,917 },918 }],919 };920 const feeAssetItem = 0;921922 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [923 destinationParachain,924 beneficiary,925 assets,926 feeAssetItem,927 ]);928929 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);930 });931932 await expectFailedToTransact(helper, messageSent);933 });934});935936describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {937 938 let alice: IKeyringPair;939 let quartzAssetLocation;940941 let randomAccountQuartz: IKeyringPair;942 let randomAccountMoonriver: IKeyringPair;943944 945 let assetId: string;946947 const quartzAssetMetadata = {948 name: 'xcQuartz',949 symbol: 'xcQTZ',950 decimals: 18,951 isFrozen: false,952 minimalBalance: 1n,953 };954955 let balanceQuartzTokenInit: bigint;956 let balanceQuartzTokenMiddle: bigint;957 let balanceQuartzTokenFinal: bigint;958 let balanceForeignQtzTokenInit: bigint;959 let balanceForeignQtzTokenMiddle: bigint;960 let balanceForeignQtzTokenFinal: bigint;961 let balanceMovrTokenInit: bigint;962 let balanceMovrTokenMiddle: bigint;963 let balanceMovrTokenFinal: bigint;964965 before(async () => {966 await usingPlaygrounds(async (helper, privateKey) => {967 alice = await privateKey('//Alice');968 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);969970 balanceForeignQtzTokenInit = 0n;971972 973 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);974 });975976 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {977 const alithAccount = helper.account.alithAccount();978 const baltatharAccount = helper.account.baltatharAccount();979 const dorothyAccount = helper.account.dorothyAccount();980981 randomAccountMoonriver = helper.account.create();982983 984 console.log('Sponsoring Dorothy.......');985 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);986 console.log('Sponsoring Dorothy.......DONE');987 988989 quartzAssetLocation = {990 XCM: {991 parents: 1,992 interior: {X1: {Parachain: QUARTZ_CHAIN}},993 },994 };995 const existentialDeposit = 1n;996 const isSufficient = true;997 const unitsPerSecond = 1n;998 const numAssetsWeightHint = 0;9991000 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1001 location: quartzAssetLocation,1002 metadata: quartzAssetMetadata,1003 existentialDeposit,1004 isSufficient,1005 unitsPerSecond,1006 numAssetsWeightHint,1007 });10081009 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);10101011 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);10121013 1014 console.log('Acquire Quartz AssetId Info on Moonriver.......');10151016 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10171018 console.log('QTZ asset ID is %s', assetId);1019 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1020 10211022 1023 console.log('Sponsoring random Account.......');1024 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1025 console.log('Sponsoring random Account.......DONE');1026 10271028 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1029 });10301031 await usingPlaygrounds(async (helper) => {1032 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1033 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1034 });1035 });10361037 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1038 const currencyId = {1039 NativeAssetId: 'Here',1040 };1041 const dest = {1042 V2: {1043 parents: 1,1044 interior: {1045 X2: [1046 {Parachain: MOONRIVER_CHAIN},1047 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1048 ],1049 },1050 },1051 };1052 const amount = TRANSFER_AMOUNT;10531054 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10551056 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1057 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10581059 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1060 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1061 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10621063 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1064 await helper.wait.newBlocks(3);10651066 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10671068 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1069 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1070 expect(movrFees == 0n).to.be.true;10711072 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; 1073 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1074 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1075 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1076 });1077 });10781079 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1080 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1081 const asset = {1082 V2: {1083 id: {1084 Concrete: {1085 parents: 1,1086 interior: {1087 X1: {Parachain: QUARTZ_CHAIN},1088 },1089 },1090 },1091 fun: {1092 Fungible: TRANSFER_AMOUNT,1093 },1094 },1095 };1096 const destination = {1097 V2: {1098 parents: 1,1099 interior: {1100 X2: [1101 {Parachain: QUARTZ_CHAIN},1102 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1103 ],1104 },1105 },1106 };11071108 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');11091110 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);11111112 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1113 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1114 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;11151116 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11171118 expect(qtzRandomAccountAsset).to.be.null;11191120 balanceForeignQtzTokenFinal = 0n;11211122 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1123 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1124 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1125 });11261127 await helper.wait.newBlocks(3);11281129 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1130 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1131 expect(actuallyDelivered > 0).to.be.true;11321133 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11341135 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1136 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1137 expect(qtzFees == 0n).to.be.true;1138 });11391140 itSub('Moonriver can send only up to its balance', async ({helper}) => {1141 1142 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1143 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1144 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11451146 const moreThanMoonriverHas = moonriverBalance * 2n;11471148 let targetAccountBalance = 0n;1149 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11501151 const quartzMultilocation = {1152 V2: {1153 parents: 1,1154 interior: {1155 X1: {Parachain: QUARTZ_CHAIN},1156 },1157 },1158 };11591160 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1161 targetAccount.addressRaw,1162 {1163 Concrete: {1164 parents: 0,1165 interior: 'Here',1166 },1167 },1168 moreThanMoonriverHas,1169 );11701171 let maliciousXcmProgramSent: any;1172 const maxWaitBlocks = 3;11731174 1175 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1176 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11771178 1179 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1180 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11811182 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1183 });11841185 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1186 return event.messageHash() == maliciousXcmProgramSent.messageHash()1187 && event.outcome().isFailedToTransactAsset;1188 });11891190 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1191 expect(targetAccountBalance).to.be.equal(0n);11921193 1194 const validTransferAmount = moonriverBalance / 2n;1195 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1196 targetAccount.addressRaw,1197 {1198 Concrete: {1199 parents: 0,1200 interior: 'Here',1201 },1202 },1203 validTransferAmount,1204 );12051206 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1207 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);12081209 1210 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1211 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1212 });12131214 await helper.wait.newBlocks(maxWaitBlocks);12151216 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1217 expect(targetAccountBalance).to.be.equal(validTransferAmount);1218 });12191220 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1221 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1222 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12231224 const quartzMultilocation = {1225 V2: {1226 parents: 1,1227 interior: {1228 X1: {1229 Parachain: QUARTZ_CHAIN,1230 },1231 },1232 },1233 };12341235 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1236 targetAccount.addressRaw,1237 {1238 Concrete: {1239 parents: 0,1240 interior: {1241 X1: {1242 Parachain: QUARTZ_CHAIN,1243 },1244 },1245 },1246 },1247 testAmount,1248 );12491250 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1251 targetAccount.addressRaw,1252 {1253 Concrete: {1254 parents: 0,1255 interior: 'Here',1256 },1257 },1258 testAmount,1259 );12601261 let maliciousXcmProgramFullIdSent: any;1262 let maliciousXcmProgramHereIdSent: any;1263 const maxWaitBlocks = 3;12641265 1266 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1267 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12681269 1270 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1271 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12721273 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1274 });12751276 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1277 return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()1278 && event.outcome().isUntrustedReserveLocation;1279 });12801281 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1282 expect(accountBalance).to.be.equal(0n);12831284 1285 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1286 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12871288 1289 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1290 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12911292 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1293 });12941295 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1296 return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()1297 && event.outcome().isUntrustedReserveLocation;1298 });12991300 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1301 expect(accountBalance).to.be.equal(0n);1302 });1303});13041305describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1306 let alice: IKeyringPair;1307 let sender: IKeyringPair;13081309 const QTZ_ASSET_ID_ON_SHIDEN = 1;1310 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;13111312 1313 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); 1314 const unitsPerSecond = 228_000_000_000n; 1315 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); 1316 const qtzToShidenArrived = 9_999_999_999_088_000_000n; 13171318 1319 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); 1320 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; 13211322 let balanceAfterQuartzToShidenXCM: bigint;13231324 before(async () => {1325 await usingPlaygrounds(async (helper, privateKey) => {1326 alice = await privateKey('//Alice');1327 [sender] = await helper.arrange.createAccounts([100n], alice);1328 console.log('sender', sender.address);13291330 1331 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1332 });13331334 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1335 console.log('1. Create foreign asset and metadata');1336 1337 await helper.assets.create(1338 alice,1339 QTZ_ASSET_ID_ON_SHIDEN,1340 alice.address,1341 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1342 );13431344 await helper.assets.setMetadata(1345 alice,1346 QTZ_ASSET_ID_ON_SHIDEN,1347 'Cross chain QTZ',1348 'xcQTZ',1349 Number(QTZ_DECIMALS),1350 );13511352 console.log('2. Register asset location on Shiden');1353 const assetLocation = {1354 V2: {1355 parents: 1,1356 interior: {1357 X1: {1358 Parachain: QUARTZ_CHAIN,1359 },1360 },1361 },1362 };13631364 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13651366 console.log('3. Set QTZ payment for XCM execution on Shiden');1367 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13681369 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1370 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1371 });1372 });13731374 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1375 const destination = {1376 V2: {1377 parents: 1,1378 interior: {1379 X1: {1380 Parachain: SHIDEN_CHAIN,1381 },1382 },1383 },1384 };13851386 const beneficiary = {1387 V2: {1388 parents: 0,1389 interior: {1390 X1: {1391 AccountId32: {1392 network: 'Any',1393 id: sender.addressRaw,1394 },1395 },1396 },1397 },1398 };13991400 const assets = {1401 V2: [1402 {1403 id: {1404 Concrete: {1405 parents: 0,1406 interior: 'Here',1407 },1408 },1409 fun: {1410 Fungible: qtzToShidenTransferred,1411 },1412 },1413 ],1414 };14151416 1417 const balanceBefore = await helper.balance.getSubstrate(sender.address);1418 console.log(`Initial balance is: ${balanceBefore}`);14191420 const feeAssetItem = 0;1421 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14221423 1424 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1425 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1426 console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1427 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14281429 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1430 await helper.wait.newBlocks(3);1431 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1432 const shidenBalance = await helper.balance.getSubstrate(sender.address);14331434 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1435 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14361437 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1438 1439 expect(shidenBalance).to.eq(shidenInitialBalance);1440 });1441 });14421443 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1444 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1445 const destination = {1446 V2: {1447 parents: 1,1448 interior: {1449 X1: {1450 Parachain: QUARTZ_CHAIN,1451 },1452 },1453 },1454 };14551456 const beneficiary = {1457 V2: {1458 parents: 0,1459 interior: {1460 X1: {1461 AccountId32: {1462 network: 'Any',1463 id: sender.addressRaw,1464 },1465 },1466 },1467 },1468 };14691470 const assets = {1471 V2: [1472 {1473 id: {1474 Concrete: {1475 parents: 1,1476 interior: {1477 X1: {1478 Parachain: QUARTZ_CHAIN,1479 },1480 },1481 },1482 },1483 fun: {1484 Fungible: qtzFromShidenTransfered,1485 },1486 },1487 ],1488 };14891490 1491 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1492 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1493 expect(balanceSDNbefore).to.eq(shidenInitialBalance);14941495 const feeAssetItem = 0;1496 1497 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14981499 1500 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1501 const balanceSDN = await helper.balance.getSubstrate(sender.address);1502 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);15031504 1505 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1506 1507 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1508 });15091510 await helper.wait.newBlocks(3);1511 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1512 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1513 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1514 });15151516 itSub('Shiden can send only up to its balance', async ({helper}) => {1517 1518 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1519 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1520 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15211522 const moreThanShidenHas = shidenBalance * 2n;15231524 let targetAccountBalance = 0n;1525 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15261527 const quartzMultilocation = {1528 V2: {1529 parents: 1,1530 interior: {1531 X1: {Parachain: QUARTZ_CHAIN},1532 },1533 },1534 };15351536 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1537 targetAccount.addressRaw,1538 {1539 Concrete: {1540 parents: 0,1541 interior: 'Here',1542 },1543 },1544 moreThanShidenHas,1545 );15461547 let maliciousXcmProgramSent: any;1548 const maxWaitBlocks = 3;15491550 1551 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1552 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15531554 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1555 });15561557 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1558 return event.messageHash() == maliciousXcmProgramSent.messageHash()1559 && event.outcome().isFailedToTransactAsset;1560 });15611562 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1563 expect(targetAccountBalance).to.be.equal(0n);15641565 1566 const validTransferAmount = shidenBalance / 2n;1567 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1568 targetAccount.addressRaw,1569 {1570 Concrete: {1571 parents: 0,1572 interior: 'Here',1573 },1574 },1575 validTransferAmount,1576 );15771578 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1579 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1580 });15811582 await helper.wait.newBlocks(maxWaitBlocks);15831584 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1585 expect(targetAccountBalance).to.be.equal(validTransferAmount);1586 });15871588 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1589 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1590 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15911592 const quartzMultilocation = {1593 V2: {1594 parents: 1,1595 interior: {1596 X1: {1597 Parachain: QUARTZ_CHAIN,1598 },1599 },1600 },1601 };16021603 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1604 targetAccount.addressRaw,1605 {1606 Concrete: {1607 parents: 1,1608 interior: {1609 X1: {1610 Parachain: QUARTZ_CHAIN,1611 },1612 },1613 },1614 },1615 testAmount,1616 );16171618 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1619 targetAccount.addressRaw,1620 {1621 Concrete: {1622 parents: 0,1623 interior: 'Here',1624 },1625 },1626 testAmount,1627 );16281629 let maliciousXcmProgramFullIdSent: any;1630 let maliciousXcmProgramHereIdSent: any;1631 const maxWaitBlocks = 3;16321633 1634 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1635 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16361637 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1638 });16391640 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1641 return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()1642 && event.outcome().isUntrustedReserveLocation;1643 });16441645 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1646 expect(accountBalance).to.be.equal(0n);16471648 1649 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1650 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16511652 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1653 });16541655 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1656 return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()1657 && event.outcome().isUntrustedReserveLocation;1658 });16591660 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1661 expect(accountBalance).to.be.equal(0n);1662 });1663});