1234567891011121314151617import type {IKeyringPair} from '@polkadot/types/types';18import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util/index.js';19import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';20import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types.js';21import {hexToString} from '@polkadot/util';22import {XcmTestHelper} from './xcm.types.js';2324const STATEMINE_PALLET_INSTANCE = 50;2526const TRANSFER_AMOUNT = 2000000000000000000000000n;2728const FUNDING_AMOUNT = 3_500_000_0000_000_000n;2930const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;3132const USDT_ASSET_ID = 100;33const USDT_ASSET_METADATA_DECIMALS = 18;34const USDT_ASSET_METADATA_NAME = 'USDT';35const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';36const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;37const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;3839const SAFE_XCM_VERSION = 2;4041const testHelper = new XcmTestHelper('quartz');4243describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {44 let alice: IKeyringPair;45 let bob: IKeyringPair;4647 let balanceStmnBefore: bigint;48 let balanceStmnAfter: bigint;4950 let balanceQuartzBefore: bigint;51 let balanceQuartzAfter: bigint;52 let balanceQuartzFinal: bigint;5354 let balanceBobBefore: bigint;55 let balanceBobAfter: bigint;56 let balanceBobFinal: bigint;5758 let balanceBobRelayTokenBefore: bigint;59 let balanceBobRelayTokenAfter: bigint;6061 let usdtCollectionId: number;62 let relayCollectionId: number;6364 before(async () => {65 await usingPlaygrounds(async (helper, privateKey) => {66 alice = await privateKey('//Alice');67 bob = await privateKey('//Bob'); 6869 70 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);7172 relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);73 });7475 await usingRelayPlaygrounds(relayUrl, async (helper) => {76 77 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);78 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);79 });8081 await usingStateminePlaygrounds(statemineUrl, async (helper) => {82 const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);83 if(assetInfo == null) {84 await helper.assets.create(85 alice,86 USDT_ASSET_ID,87 alice.address,88 USDT_ASSET_METADATA_MINIMAL_BALANCE,89 );90 await helper.assets.setMetadata(91 alice,92 USDT_ASSET_ID,93 USDT_ASSET_METADATA_NAME,94 USDT_ASSET_METADATA_DESCRIPTION,95 USDT_ASSET_METADATA_DECIMALS,96 );97 } else {98 console.log('The USDT asset is already registered on AssetHub');99 }100101 await helper.assets.mint(102 alice,103 USDT_ASSET_ID,104 alice.address,105 USDT_ASSET_AMOUNT,106 );107108 const sovereignFundingAmount = 3_500_000_000n;109110 111 112 113 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);114 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);115 });116117118 await usingPlaygrounds(async (helper) => {119 const location = {120 parents: 1,121 interior: {X3: [122 {123 Parachain: STATEMINE_CHAIN,124 },125 {126 PalletInstance: STATEMINE_PALLET_INSTANCE,127 },128 {129 GeneralIndex: USDT_ASSET_ID,130 },131 ]},132 };133 const assetId = {Concrete: location};134135 if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {136 const tokenPrefix = USDT_ASSET_METADATA_NAME;137 await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});138 } else {139 console.log('Foreign collection is already registered on Quartz');140 }141142 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);143 usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);144 });145146147 148 149 await usingRelayPlaygrounds(relayUrl, async (helper) => {150 const destination = {151 V2: {152 parents: 0,153 interior: {X1: {154 Parachain: QUARTZ_CHAIN,155 },156 },157 }};158159 const beneficiary = {160 V2: {161 parents: 0,162 interior: {X1: {163 AccountId32: {164 network: 'Any',165 id: alice.addressRaw,166 },167 }},168 },169 };170171 const assets = {172 V2: [173 {174 id: {175 Concrete: {176 parents: 0,177 interior: 'Here',178 },179 },180 fun: {181 Fungible: TRANSFER_AMOUNT_RELAY,182 },183 },184 ],185 };186187 const feeAssetItem = 0;188189 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');190 });191192 });193194 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {195 await usingStateminePlaygrounds(statemineUrl, async (helper) => {196 const dest = {197 V2: {198 parents: 1,199 interior: {X1: {200 Parachain: QUARTZ_CHAIN,201 },202 },203 }};204205 const beneficiary = {206 V2: {207 parents: 0,208 interior: {X1: {209 AccountId32: {210 network: 'Any',211 id: alice.addressRaw,212 },213 }},214 },215 };216217 const assets = {218 V2: [219 {220 id: {221 Concrete: {222 parents: 0,223 interior: {224 X2: [225 {226 PalletInstance: STATEMINE_PALLET_INSTANCE,227 },228 {229 GeneralIndex: USDT_ASSET_ID,230 },231 ]},232 },233 },234 fun: {235 Fungible: TRANSFER_AMOUNT,236 },237 },238 ],239 };240241 const feeAssetItem = 0;242243 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);244 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');245246 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);247248 249 console.log(250 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',251 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),252 );253 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;254255 });256257258 259 await helper.wait.newBlocks(3);260261 const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});262263 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);264265 console.log(266 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',267 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),268 );269 console.log(270 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',271 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),272 );273 274 expect(free).to.be.equal(TRANSFER_AMOUNT);275 276 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;277 });278279 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {280 const destination = {281 V2: {282 parents: 1,283 interior: {X2: [284 {285 Parachain: STATEMINE_CHAIN,286 },287 {288 AccountId32: {289 network: 'Any',290 id: alice.addressRaw,291 },292 },293 ]},294 },295 };296297 const relayFee = 400_000_000_000_000n;298 const currencies: [any, bigint][] = [299 [300 usdtCollectionId,301 TRANSFER_AMOUNT,302 ],303 [304 relayCollectionId,305 relayFee,306 ],307 ];308309 const feeItem = 1;310311 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');312313 314 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);315 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));316 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;317318 await usingStateminePlaygrounds(statemineUrl, async (helper) => {319 await helper.wait.newBlocks(3);320321 322 323 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;324 });325 });326327 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {328 balanceBobBefore = await helper.balance.getSubstrate(bob.address);329 balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});330331 await usingRelayPlaygrounds(relayUrl, async (helper) => {332 const destination = {333 V2: {334 parents: 0,335 interior: {X1: {336 Parachain: QUARTZ_CHAIN,337 },338 },339 }};340341 const beneficiary = {342 V2: {343 parents: 0,344 interior: {X1: {345 AccountId32: {346 network: 'Any',347 id: bob.addressRaw,348 },349 }},350 },351 };352353 const assets = {354 V2: [355 {356 id: {357 Concrete: {358 parents: 0,359 interior: 'Here',360 },361 },362 fun: {363 Fungible: TRANSFER_AMOUNT_RELAY,364 },365 },366 ],367 };368369 const feeAssetItem = 0;370371 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');372 });373374 await helper.wait.newBlocks(3);375376 balanceBobAfter = await helper.balance.getSubstrate(bob.address);377 balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});378379 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;380 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;381 console.log(382 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',383 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),384 );385 console.log(386 '[Relay (Westend) -> Quartz] transaction fees: %s WND',387 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),388 );389 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);390 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;391 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;392 });393394 itSub('Should connect and send Relay token back', async ({helper}) => {395 let relayTokenBalanceBefore: bigint;396 let relayTokenBalanceAfter: bigint;397 await usingRelayPlaygrounds(relayUrl, async (helper) => {398 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);399 });400401 const destination = {402 V2: {403 parents: 1,404 interior: {405 X1:{406 AccountId32: {407 network: 'Any',408 id: bob.addressRaw,409 },410 },411 },412 },413 };414415 const currencies: any = [416 [417 relayCollectionId,418 TRANSFER_AMOUNT_RELAY,419 ],420 ];421422 const feeItem = 0;423424 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');425426 balanceBobFinal = await helper.balance.getSubstrate(bob.address);427 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));428429 await usingRelayPlaygrounds(relayUrl, async (helper) => {430 await helper.wait.newBlocks(10);431 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);432433 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;434 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));435 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;436 });437 });438});439440describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {441 let alice: IKeyringPair;442 let randomAccount: IKeyringPair;443444 let balanceQuartzTokenInit: bigint;445 let balanceQuartzTokenMiddle: bigint;446 let balanceQuartzTokenFinal: bigint;447 let balanceKaruraTokenInit: bigint;448 let balanceKaruraTokenMiddle: bigint;449 let balanceKaruraTokenFinal: bigint;450 let balanceQuartzForeignTokenInit: bigint;451 let balanceQuartzForeignTokenMiddle: bigint;452 let balanceQuartzForeignTokenFinal: bigint;453454 455 456 457 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;458 const karuraEps = 8n * 10n ** 16n;459460 let karuraBackwardTransferAmount: bigint;461462 before(async () => {463 await usingPlaygrounds(async (helper, privateKey) => {464 alice = await privateKey('//Alice');465 [randomAccount] = await helper.arrange.createAccounts([0n], alice);466467 468 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);469 });470471 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {472 const destination = {473 V2: {474 parents: 1,475 interior: {476 X1: {477 Parachain: QUARTZ_CHAIN,478 },479 },480 },481 };482483 const metadata = {484 name: 'Quartz',485 symbol: 'QTZ',486 decimals: 18,487 minimalBalance: 1000000000000000000n,488 };489490 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>491 hexToString(v.toJSON()['symbol'])) as string[];492493 if(!assets.includes('QTZ')) {494 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);495 } else {496 console.log('QTZ token already registered on Karura assetRegistry pallet');497 }498 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);499 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);500 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});501 });502503 await usingPlaygrounds(async (helper) => {504 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);505 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);506 });507 });508509 itSub('Should connect and send QTZ to Karura', async ({helper}) => {510 const destination = {511 V2: {512 parents: 1,513 interior: {514 X1: {515 Parachain: KARURA_CHAIN,516 },517 },518 },519 };520521 const beneficiary = {522 V2: {523 parents: 0,524 interior: {525 X1: {526 AccountId32: {527 network: 'Any',528 id: randomAccount.addressRaw,529 },530 },531 },532 },533 };534535 const assets = {536 V2: [537 {538 id: {539 Concrete: {540 parents: 0,541 interior: 'Here',542 },543 },544 fun: {545 Fungible: TRANSFER_AMOUNT,546 },547 },548 ],549 };550551 const feeAssetItem = 0;552553 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');554 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);555556 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;557 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;558 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));559560 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {561 await helper.wait.newBlocks(3);562563 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});564 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);565566 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;567 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;568 karuraBackwardTransferAmount = qtzIncomeTransfer;569570 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;571572 console.log(573 '[Quartz -> Karura] transaction fees on Karura: %s KAR',574 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),575 );576 console.log(577 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',578 helper.util.bigIntToDecimals(karUnqFees),579 );580 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));581 expect(karFees == 0n).to.be.true;582583 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;584585 expect(586 bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,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 V2: {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, karuraBackwardTransferAmount, 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 == karuraBackwardTransferAmount).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 = karuraBackwardTransferAmount - 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 V2: {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 let maliciousXcmProgramSent: any;677 const maxWaitBlocks = 5;678679 680 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {681 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);682683 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);684 });685686 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash687 && event.outcome.isFailedToTransactAsset);688689 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);690 expect(targetAccountBalance).to.be.equal(0n);691692 693 const validTransferAmount = karuraBalance / 2n;694 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(695 targetAccount.addressRaw,696 {697 Concrete: {698 parents: 0,699 interior: 'Here',700 },701 },702 validTransferAmount,703 );704705 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {706 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);707 });708709 await helper.wait.newBlocks(maxWaitBlocks);710711 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);712 expect(targetAccountBalance).to.be.equal(validTransferAmount);713 });714715 itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {716 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);717 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);718719 const quartzMultilocation = {720 V2: {721 parents: 1,722 interior: {723 X1: {724 Parachain: QUARTZ_CHAIN,725 },726 },727 },728 };729730 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(731 targetAccount.addressRaw,732 {733 Concrete: {734 parents: 1,735 interior: {736 X1: {737 Parachain: QUARTZ_CHAIN,738 },739 },740 },741 },742 testAmount,743 );744745 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(746 targetAccount.addressRaw,747 {748 Concrete: {749 parents: 0,750 interior: 'Here',751 },752 },753 testAmount,754 );755756 let maliciousXcmProgramFullIdSent: any;757 let maliciousXcmProgramHereIdSent: any;758 const maxWaitBlocks = 3;759760 761 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {762 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);763764 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);765 });766767 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash768 && event.outcome.isUntrustedReserveLocation);769770 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);771 expect(accountBalance).to.be.equal(0n);772773 774 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {775 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);776777 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);778 });779780 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash781 && event.outcome.isUntrustedReserveLocation);782783 accountBalance = await helper.balance.getSubstrate(targetAccount.address);784 expect(accountBalance).to.be.equal(0n);785 });786});787788789790describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {791 let alice: IKeyringPair;792 let alith: IKeyringPair;793794 const testAmount = 100_000_000_000n;795 let quartzParachainJunction;796 let quartzAccountJunction;797798 let quartzParachainMultilocation: any;799 let quartzAccountMultilocation: any;800 let quartzCombinedMultilocation: any;801802 let messageSent: any;803804 const maxWaitBlocks = 3;805806 before(async () => {807 await usingPlaygrounds(async (helper, privateKey) => {808 alice = await privateKey('//Alice');809810 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};811 quartzAccountJunction = {812 AccountId32: {813 network: 'Any',814 id: alice.addressRaw,815 },816 };817818 quartzParachainMultilocation = {819 V2: {820 parents: 1,821 interior: {822 X1: quartzParachainJunction,823 },824 },825 };826827 quartzAccountMultilocation = {828 V2: {829 parents: 0,830 interior: {831 X1: quartzAccountJunction,832 },833 },834 };835836 quartzCombinedMultilocation = {837 V2: {838 parents: 1,839 interior: {840 X2: [quartzParachainJunction, quartzAccountJunction],841 },842 },843 };844845 846 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);847 });848849 850 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {851 alith = helper.account.alithAccount();852 });853 });854855 const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {856 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash857 && event.outcome.isFailedToTransactAsset);858 };859860 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {861 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {862 const id = {863 Token: 'KAR',864 };865 const destination = quartzCombinedMultilocation;866 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');867868 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);869 });870871 await expectFailedToTransact(helper, messageSent);872 });873874 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {875 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {876 const id = 'SelfReserve';877 const destination = quartzCombinedMultilocation;878 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');879880 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);881 });882883 await expectFailedToTransact(helper, messageSent);884 });885886 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {887 await usingShidenPlaygrounds(shidenUrl, async (helper) => {888 const destinationParachain = quartzParachainMultilocation;889 const beneficiary = quartzAccountMultilocation;890 const assets = {891 V2: [{892 id: {893 Concrete: {894 parents: 0,895 interior: 'Here',896 },897 },898 fun: {899 Fungible: testAmount,900 },901 }],902 };903 const feeAssetItem = 0;904905 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [906 destinationParachain,907 beneficiary,908 assets,909 feeAssetItem,910 ]);911912 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);913 });914915 await expectFailedToTransact(helper, messageSent);916 });917});918919describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {920 921 let alice: IKeyringPair;922 let quartzAssetLocation;923924 let randomAccountQuartz: IKeyringPair;925 let randomAccountMoonriver: IKeyringPair;926927 928 let assetId: string;929930 const quartzAssetMetadata = {931 name: 'xcQuartz',932 symbol: 'xcQTZ',933 decimals: 18,934 isFrozen: false,935 minimalBalance: 1n,936 };937938 let balanceQuartzTokenInit: bigint;939 let balanceQuartzTokenMiddle: bigint;940 let balanceQuartzTokenFinal: bigint;941 let balanceForeignQtzTokenInit: bigint;942 let balanceForeignQtzTokenMiddle: bigint;943 let balanceForeignQtzTokenFinal: bigint;944 let balanceMovrTokenInit: bigint;945 let balanceMovrTokenMiddle: bigint;946 let balanceMovrTokenFinal: bigint;947948 before(async () => {949 await usingPlaygrounds(async (helper, privateKey) => {950 alice = await privateKey('//Alice');951 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);952953 balanceForeignQtzTokenInit = 0n;954955 956 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);957 });958959 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {960 const alithAccount = helper.account.alithAccount();961 const baltatharAccount = helper.account.baltatharAccount();962 const dorothyAccount = helper.account.dorothyAccount();963964 randomAccountMoonriver = helper.account.create();965966 967 console.log('Sponsoring Dorothy.......');968 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);969 console.log('Sponsoring Dorothy.......DONE');970 971972 quartzAssetLocation = {973 XCM: {974 parents: 1,975 interior: {X1: {Parachain: QUARTZ_CHAIN}},976 },977 };978 const existentialDeposit = 1n;979 const isSufficient = true;980 const unitsPerSecond = 1n;981 const numAssetsWeightHint = 0;982983 if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {984 console.log('Quartz asset already registered on Moonriver');985 } else {986 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({987 location: quartzAssetLocation,988 metadata: quartzAssetMetadata,989 existentialDeposit,990 isSufficient,991 unitsPerSecond,992 numAssetsWeightHint,993 });994995 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);996997 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);998 }999 1000 console.log('Acquire Quartz AssetId Info on Moonriver.......');10011002 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10031004 console.log('QTZ asset ID is %s', assetId);1005 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1006 10071008 1009 console.log('Sponsoring random Account.......');1010 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1011 console.log('Sponsoring random Account.......DONE');1012 10131014 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1015 });10161017 await usingPlaygrounds(async (helper) => {1018 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1019 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1020 });1021 });10221023 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1024 const currencyId = 0;1025 const dest = {1026 V2: {1027 parents: 1,1028 interior: {1029 X2: [1030 {Parachain: MOONRIVER_CHAIN},1031 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1032 ],1033 },1034 },1035 };1036 const amount = TRANSFER_AMOUNT;10371038 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10391040 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1041 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10421043 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1044 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1045 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10461047 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1048 await helper.wait.newBlocks(3);10491050 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10511052 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1053 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1054 expect(movrFees == 0n).to.be.true;10551056 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; 1057 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1058 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1059 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1060 });1061 });10621063 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1064 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1065 const asset = {1066 V2: {1067 id: {1068 Concrete: {1069 parents: 1,1070 interior: {1071 X1: {Parachain: QUARTZ_CHAIN},1072 },1073 },1074 },1075 fun: {1076 Fungible: TRANSFER_AMOUNT,1077 },1078 },1079 };1080 const destination = {1081 V2: {1082 parents: 1,1083 interior: {1084 X2: [1085 {Parachain: QUARTZ_CHAIN},1086 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1087 ],1088 },1089 },1090 };10911092 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10931094 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10951096 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1097 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1098 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10991100 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11011102 expect(qtzRandomAccountAsset).to.be.null;11031104 balanceForeignQtzTokenFinal = 0n;11051106 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1107 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1108 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1109 });11101111 await helper.wait.newBlocks(3);11121113 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1114 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1115 expect(actuallyDelivered > 0).to.be.true;11161117 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11181119 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1120 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1121 expect(qtzFees == 0n).to.be.true;1122 });11231124 itSub('Moonriver can send only up to its balance', async ({helper}) => {1125 1126 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1127 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1128 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11291130 const moreThanMoonriverHas = moonriverBalance * 2n;11311132 let targetAccountBalance = 0n;1133 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11341135 const quartzMultilocation = {1136 V2: {1137 parents: 1,1138 interior: {1139 X1: {Parachain: QUARTZ_CHAIN},1140 },1141 },1142 };11431144 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1145 targetAccount.addressRaw,1146 {1147 Concrete: {1148 parents: 0,1149 interior: 'Here',1150 },1151 },1152 moreThanMoonriverHas,1153 );11541155 let maliciousXcmProgramSent: any;1156 const maxWaitBlocks = 3;11571158 1159 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1160 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11611162 1163 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1164 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11651166 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1167 });11681169 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1170 && event.outcome.isFailedToTransactAsset);11711172 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1173 expect(targetAccountBalance).to.be.equal(0n);11741175 1176 const validTransferAmount = moonriverBalance / 2n;1177 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1178 targetAccount.addressRaw,1179 {1180 Concrete: {1181 parents: 0,1182 interior: 'Here',1183 },1184 },1185 validTransferAmount,1186 );11871188 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1189 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11901191 1192 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1193 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1194 });11951196 await helper.wait.newBlocks(maxWaitBlocks);11971198 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1199 expect(targetAccountBalance).to.be.equal(validTransferAmount);1200 });12011202 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1203 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1204 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12051206 const quartzMultilocation = {1207 V2: {1208 parents: 1,1209 interior: {1210 X1: {1211 Parachain: QUARTZ_CHAIN,1212 },1213 },1214 },1215 };12161217 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1218 targetAccount.addressRaw,1219 {1220 Concrete: {1221 parents: 0,1222 interior: {1223 X1: {1224 Parachain: QUARTZ_CHAIN,1225 },1226 },1227 },1228 },1229 testAmount,1230 );12311232 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1233 targetAccount.addressRaw,1234 {1235 Concrete: {1236 parents: 0,1237 interior: 'Here',1238 },1239 },1240 testAmount,1241 );12421243 let maliciousXcmProgramFullIdSent: any;1244 let maliciousXcmProgramHereIdSent: any;1245 const maxWaitBlocks = 3;12461247 1248 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1249 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12501251 1252 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1253 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12541255 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1256 });12571258 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1259 && event.outcome.isUntrustedReserveLocation);12601261 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1262 expect(accountBalance).to.be.equal(0n);12631264 1265 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1266 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12671268 1269 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1270 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12711272 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1273 });12741275 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1276 && event.outcome.isUntrustedReserveLocation);12771278 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1279 expect(accountBalance).to.be.equal(0n);1280 });1281});12821283describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1284 let alice: IKeyringPair;1285 let sender: IKeyringPair;12861287 const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; 1288 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; 12891290 1291 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); 1292 const unitsPerSecond = 500_451_000_000_000_000_000n; 1293 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); 1294 const qtzToShidenArrived = 7_998_196_000_000_000_000n; 12951296 1297 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); 1298 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; 12991300 let balanceAfterQuartzToShidenXCM: bigint;13011302 before(async () => {1303 await usingPlaygrounds(async (helper, privateKey) => {1304 alice = await privateKey('//Alice');1305 [sender] = await helper.arrange.createAccounts([100n], alice);1306 console.log('sender', sender.address);13071308 1309 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1310 });13111312 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1313 if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {1314 console.log('1. Create foreign asset and metadata');1315 await helper.getSudo().assets.forceCreate(1316 alice,1317 QTZ_ASSET_ID_ON_SHIDEN,1318 alice.address,1319 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1320 );13211322 await helper.assets.setMetadata(1323 alice,1324 QTZ_ASSET_ID_ON_SHIDEN,1325 'Quartz',1326 'QTZ',1327 Number(QTZ_DECIMALS),1328 );13291330 console.log('2. Register asset location on Shiden');1331 const assetLocation = {1332 V2: {1333 parents: 1,1334 interior: {1335 X1: {1336 Parachain: QUARTZ_CHAIN,1337 },1338 },1339 },1340 };13411342 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13431344 console.log('3. Set QTZ payment for XCM execution on Shiden');1345 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1346 } else {1347 console.log('QTZ is already registered on Shiden');1348 }1349 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1350 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1351 });1352 });13531354 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1355 const destination = {1356 V2: {1357 parents: 1,1358 interior: {1359 X1: {1360 Parachain: SHIDEN_CHAIN,1361 },1362 },1363 },1364 };13651366 const beneficiary = {1367 V2: {1368 parents: 0,1369 interior: {1370 X1: {1371 AccountId32: {1372 network: 'Any',1373 id: sender.addressRaw,1374 },1375 },1376 },1377 },1378 };13791380 const assets = {1381 V2: [1382 {1383 id: {1384 Concrete: {1385 parents: 0,1386 interior: 'Here',1387 },1388 },1389 fun: {1390 Fungible: qtzToShidenTransferred,1391 },1392 },1393 ],1394 };13951396 1397 const balanceBefore = await helper.balance.getSubstrate(sender.address);1398 console.log(`Initial balance is: ${balanceBefore}`);13991400 const feeAssetItem = 0;1401 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14021403 1404 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1405 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1406 console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1407 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14081409 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1410 await helper.wait.newBlocks(3);1411 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1412 const shidenBalance = await helper.balance.getSubstrate(sender.address);14131414 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1415 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14161417 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1418 1419 expect(shidenBalance).to.eq(shidenInitialBalance);1420 });1421 });14221423 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1424 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1425 const destination = {1426 V2: {1427 parents: 1,1428 interior: {1429 X1: {1430 Parachain: QUARTZ_CHAIN,1431 },1432 },1433 },1434 };14351436 const beneficiary = {1437 V2: {1438 parents: 0,1439 interior: {1440 X1: {1441 AccountId32: {1442 network: 'Any',1443 id: sender.addressRaw,1444 },1445 },1446 },1447 },1448 };14491450 const assets = {1451 V2: [1452 {1453 id: {1454 Concrete: {1455 parents: 1,1456 interior: {1457 X1: {1458 Parachain: QUARTZ_CHAIN,1459 },1460 },1461 },1462 },1463 fun: {1464 Fungible: qtzFromShidenTransfered,1465 },1466 },1467 ],1468 };14691470 1471 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1472 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1473 expect(balanceSDNbefore).to.eq(shidenInitialBalance);14741475 const feeAssetItem = 0;1476 1477 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14781479 1480 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1481 const balanceSDN = await helper.balance.getSubstrate(sender.address);1482 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14831484 1485 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1486 1487 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1488 });14891490 await helper.wait.newBlocks(3);1491 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1492 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1493 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1494 });14951496 itSub('Shiden can send only up to its balance', async ({helper}) => {1497 1498 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1499 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1500 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15011502 const moreThanShidenHas = shidenBalance * 2n;15031504 let targetAccountBalance = 0n;1505 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15061507 const quartzMultilocation = {1508 V2: {1509 parents: 1,1510 interior: {1511 X1: {Parachain: QUARTZ_CHAIN},1512 },1513 },1514 };15151516 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1517 targetAccount.addressRaw,1518 {1519 Concrete: {1520 parents: 0,1521 interior: 'Here',1522 },1523 },1524 moreThanShidenHas,1525 );15261527 let maliciousXcmProgramSent: any;1528 const maxWaitBlocks = 3;15291530 1531 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1532 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15331534 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1535 });15361537 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1538 && event.outcome.isFailedToTransactAsset);15391540 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1541 expect(targetAccountBalance).to.be.equal(0n);15421543 1544 const validTransferAmount = shidenBalance / 2n;1545 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1546 targetAccount.addressRaw,1547 {1548 Concrete: {1549 parents: 0,1550 interior: 'Here',1551 },1552 },1553 validTransferAmount,1554 );15551556 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1557 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1558 });15591560 await helper.wait.newBlocks(maxWaitBlocks);15611562 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1563 expect(targetAccountBalance).to.be.equal(validTransferAmount);1564 });15651566 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1567 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1568 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15691570 const quartzMultilocation = {1571 V2: {1572 parents: 1,1573 interior: {1574 X1: {1575 Parachain: QUARTZ_CHAIN,1576 },1577 },1578 },1579 };15801581 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1582 targetAccount.addressRaw,1583 {1584 Concrete: {1585 parents: 1,1586 interior: {1587 X1: {1588 Parachain: QUARTZ_CHAIN,1589 },1590 },1591 },1592 },1593 testAmount,1594 );15951596 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1597 targetAccount.addressRaw,1598 {1599 Concrete: {1600 parents: 0,1601 interior: 'Here',1602 },1603 },1604 testAmount,1605 );16061607 let maliciousXcmProgramFullIdSent: any;1608 let maliciousXcmProgramHereIdSent: any;1609 const maxWaitBlocks = 3;16101611 1612 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1613 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16141615 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1616 });16171618 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1619 && event.outcome.isUntrustedReserveLocation);16201621 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1622 expect(accountBalance).to.be.equal(0n);16231624 1625 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1626 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16271628 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1629 });16301631 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1632 && event.outcome.isUntrustedReserveLocation);16331634 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1635 expect(accountBalance).to.be.equal(0n);1636 });1637});