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';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 };133134 if(await helper.foreignAssets.foreignCollectionId(location) == null) {135 const tokenPrefix = USDT_ASSET_METADATA_NAME;136 await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});137 } else {138 console.log('Foreign collection is already registered on Quartz');139 }140141 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);142 usdtCollectionId = await helper.foreignAssets.foreignCollectionId(location);143 });144145146 147 148 await usingRelayPlaygrounds(relayUrl, async (helper) => {149 const destination = {150 V2: {151 parents: 0,152 interior: {X1: {153 Parachain: QUARTZ_CHAIN,154 },155 },156 }};157158 const beneficiary = {159 V2: {160 parents: 0,161 interior: {X1: {162 AccountId32: {163 network: 'Any',164 id: alice.addressRaw,165 },166 }},167 },168 };169170 const assets = {171 V2: [172 {173 id: {174 Concrete: {175 parents: 0,176 interior: 'Here',177 },178 },179 fun: {180 Fungible: TRANSFER_AMOUNT_RELAY,181 },182 },183 ],184 };185186 const feeAssetItem = 0;187188 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');189 });190191 });192193 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {194 await usingStateminePlaygrounds(statemineUrl, async (helper) => {195 const dest = {196 V2: {197 parents: 1,198 interior: {X1: {199 Parachain: QUARTZ_CHAIN,200 },201 },202 }};203204 const beneficiary = {205 V2: {206 parents: 0,207 interior: {X1: {208 AccountId32: {209 network: 'Any',210 id: alice.addressRaw,211 },212 }},213 },214 };215216 const assets = {217 V2: [218 {219 id: {220 Concrete: {221 parents: 0,222 interior: {223 X2: [224 {225 PalletInstance: STATEMINE_PALLET_INSTANCE,226 },227 {228 GeneralIndex: USDT_ASSET_ID,229 },230 ]},231 },232 },233 fun: {234 Fungible: TRANSFER_AMOUNT,235 },236 },237 ],238 };239240 const feeAssetItem = 0;241242 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);243 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');244245 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);246247 248 console.log(249 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',250 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),251 );252 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;253254 });255256257 258 await helper.wait.newBlocks(3);259260 const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});261262 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);263264 console.log(265 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',266 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),267 );268 console.log(269 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',270 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),271 );272 273 expect(free).to.be.equal(TRANSFER_AMOUNT);274 275 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;276 });277278 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {279 const destination = {280 V2: {281 parents: 1,282 interior: {X2: [283 {284 Parachain: STATEMINE_CHAIN,285 },286 {287 AccountId32: {288 network: 'Any',289 id: alice.addressRaw,290 },291 },292 ]},293 },294 };295296 const relayFee = 400_000_000_000_000n;297 const currencies: [any, bigint][] = [298 [299 usdtCollectionId,300 TRANSFER_AMOUNT,301 ],302 [303 relayCollectionId,304 relayFee,305 ],306 ];307308 const feeItem = 1;309310 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');311312 313 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);314 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));315 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;316317 await usingStateminePlaygrounds(statemineUrl, async (helper) => {318 await helper.wait.newBlocks(3);319320 321 322 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;323 });324 });325326 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {327 balanceBobBefore = await helper.balance.getSubstrate(bob.address);328 balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});329330 await usingRelayPlaygrounds(relayUrl, async (helper) => {331 const destination = {332 V2: {333 parents: 0,334 interior: {X1: {335 Parachain: QUARTZ_CHAIN,336 },337 },338 }};339340 const beneficiary = {341 V2: {342 parents: 0,343 interior: {X1: {344 AccountId32: {345 network: 'Any',346 id: bob.addressRaw,347 },348 }},349 },350 };351352 const assets = {353 V2: [354 {355 id: {356 Concrete: {357 parents: 0,358 interior: 'Here',359 },360 },361 fun: {362 Fungible: TRANSFER_AMOUNT_RELAY,363 },364 },365 ],366 };367368 const feeAssetItem = 0;369370 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');371 });372373 await helper.wait.newBlocks(3);374375 balanceBobAfter = await helper.balance.getSubstrate(bob.address);376 balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});377378 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;379 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;380 console.log(381 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',382 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),383 );384 console.log(385 '[Relay (Westend) -> Quartz] transaction fees: %s WND',386 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),387 );388 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);389 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;390 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;391 });392393 itSub('Should connect and send Relay token back', async ({helper}) => {394 let relayTokenBalanceBefore: bigint;395 let relayTokenBalanceAfter: bigint;396 await usingRelayPlaygrounds(relayUrl, async (helper) => {397 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);398 });399400 const destination = {401 V2: {402 parents: 1,403 interior: {404 X1:{405 AccountId32: {406 network: 'Any',407 id: bob.addressRaw,408 },409 },410 },411 },412 };413414 const currencies: any = [415 [416 relayCollectionId,417 TRANSFER_AMOUNT_RELAY,418 ],419 ];420421 const feeItem = 0;422423 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');424425 balanceBobFinal = await helper.balance.getSubstrate(bob.address);426 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));427428 await usingRelayPlaygrounds(relayUrl, async (helper) => {429 await helper.wait.newBlocks(10);430 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);431432 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;433 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));434 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;435 });436 });437});438439describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {440 let alice: IKeyringPair;441 let randomAccount: IKeyringPair;442443 let balanceQuartzTokenInit: bigint;444 let balanceQuartzTokenMiddle: bigint;445 let balanceQuartzTokenFinal: bigint;446 let balanceKaruraTokenInit: bigint;447 let balanceKaruraTokenMiddle: bigint;448 let balanceKaruraTokenFinal: bigint;449 let balanceQuartzForeignTokenInit: bigint;450 let balanceQuartzForeignTokenMiddle: bigint;451 let balanceQuartzForeignTokenFinal: bigint;452453 454 455 456 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;457 const karuraEps = 8n * 10n ** 16n;458459 let karuraBackwardTransferAmount: bigint;460461 before(async () => {462 await usingPlaygrounds(async (helper, privateKey) => {463 alice = await privateKey('//Alice');464 [randomAccount] = await helper.arrange.createAccounts([0n], alice);465466 467 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);468 });469470 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {471 const destination = {472 V2: {473 parents: 1,474 interior: {475 X1: {476 Parachain: QUARTZ_CHAIN,477 },478 },479 },480 };481482 const metadata = {483 name: 'Quartz',484 symbol: 'QTZ',485 decimals: 18,486 minimalBalance: 1000000000000000000n,487 };488489 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>490 hexToString(v.toJSON()['symbol'])) as string[];491492 if(!assets.includes('QTZ')) {493 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);494 } else {495 console.log('QTZ token already registered on Karura assetRegistry pallet');496 }497 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);498 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);499 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});500 });501502 await usingPlaygrounds(async (helper) => {503 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);504 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);505 });506 });507508 itSub('Should connect and send QTZ to Karura', async ({helper}) => {509 const destination = {510 V2: {511 parents: 1,512 interior: {513 X1: {514 Parachain: KARURA_CHAIN,515 },516 },517 },518 };519520 const beneficiary = {521 V2: {522 parents: 0,523 interior: {524 X1: {525 AccountId32: {526 network: 'Any',527 id: randomAccount.addressRaw,528 },529 },530 },531 },532 };533534 const assets = {535 V2: [536 {537 id: {538 Concrete: {539 parents: 0,540 interior: 'Here',541 },542 },543 fun: {544 Fungible: TRANSFER_AMOUNT,545 },546 },547 ],548 };549550 const feeAssetItem = 0;551552 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');553 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);554555 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;556 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;557 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));558559 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {560 await helper.wait.newBlocks(3);561562 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});563 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);564565 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;566 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;567 karuraBackwardTransferAmount = qtzIncomeTransfer;568569 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;570571 console.log(572 '[Quartz -> Karura] transaction fees on Karura: %s KAR',573 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),574 );575 console.log(576 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',577 helper.util.bigIntToDecimals(karUnqFees),578 );579 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));580 expect(karFees == 0n).to.be.true;581582 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;583584 expect(585 bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,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 V2: {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, karuraBackwardTransferAmount, 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 == karuraBackwardTransferAmount).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 = karuraBackwardTransferAmount - 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 V2: {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 = 5;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 => event.messageHash == maliciousXcmProgramSent.messageHash686 && event.outcome.isFailedToTransactAsset);687688 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);689 expect(targetAccountBalance).to.be.equal(0n);690691 692 const validTransferAmount = karuraBalance / 2n;693 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(694 targetAccount.addressRaw,695 {696 Concrete: {697 parents: 0,698 interior: 'Here',699 },700 },701 validTransferAmount,702 );703704 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {705 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);706 });707708 await helper.wait.newBlocks(maxWaitBlocks);709710 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);711 expect(targetAccountBalance).to.be.equal(validTransferAmount);712 });713714 itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {715 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);716 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);717718 const quartzMultilocation = {719 V2: {720 parents: 1,721 interior: {722 X1: {723 Parachain: QUARTZ_CHAIN,724 },725 },726 },727 };728729 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(730 targetAccount.addressRaw,731 {732 Concrete: {733 parents: 1,734 interior: {735 X1: {736 Parachain: QUARTZ_CHAIN,737 },738 },739 },740 },741 testAmount,742 );743744 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(745 targetAccount.addressRaw,746 {747 Concrete: {748 parents: 0,749 interior: 'Here',750 },751 },752 testAmount,753 );754755 let maliciousXcmProgramFullIdSent: any;756 let maliciousXcmProgramHereIdSent: any;757 const maxWaitBlocks = 3;758759 760 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {761 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);762763 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);764 });765766 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash767 && event.outcome.isUntrustedReserveLocation);768769 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);770 expect(accountBalance).to.be.equal(0n);771772 773 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {774 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);775776 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);777 });778779 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash780 && event.outcome.isUntrustedReserveLocation);781782 accountBalance = await helper.balance.getSubstrate(targetAccount.address);783 expect(accountBalance).to.be.equal(0n);784 });785786 itSub.skip('Transfer NFT from Quartz to Karura', async ({helper}) => {787 const collection = await helper.nft.mintCollection(alice, {788 tokenPrefix: 'xNFT',789 });790791 const collectionId = collection.collectionId;792793 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {794 const uniqueCollectionLocation = {795 parents: 1,796 interior: {797 X2: [798 {799 Parachain: QUARTZ_CHAIN,800 },801 {802 GeneralIndex: collectionId,803 },804 ],805 },806 };807808 await helper.executeExtrinsic(809 alice,810 'api.tx.xnft.registerAsset',811 [{812 Concrete: uniqueCollectionLocation,813 }],814 );815 });816817 const token = await collection.mintToken(alice, {Substrate: alice.address});818 const tokenId = token.tokenId;819820 const destination = {821 V3: {822 parents: 1,823 interior: {824 X1: {825 Parachain: KARURA_CHAIN,826 },827 },828 },829 };830831 const beneficiary = {832 V3: {833 parents: 0,834 interior: {835 X1: {836 AccountId32: {837 network: null,838 id: randomAccount.addressRaw,839 },840 },841 },842 },843 };844845 const buyExecutionFee = 10n * 10n ** QTZ_DECIMALS;846847 const assets = {848 V3: [849 {850 id: {851 Concrete: {852 parents: 0,853 interior: 'Here',854 },855 },856 fun: {857 Fungible: buyExecutionFee,858 },859 },860 {861 id: {862 Concrete: {863 parents: 1,864 interior: {865 X2: [866 {867 Parachain: QUARTZ_CHAIN,868 },869 {870 GeneralIndex: collectionId,871 },872 ],873 },874 },875 },876 fun: {877 NonFungible: {878 Index: tokenId,879 },880 },881 },882 ],883 };884885 const feeAssetItem = 0;886887 await helper.xcm.limitedReserveTransferAssets(888 alice,889 destination,890 beneficiary,891 assets,892 feeAssetItem,893 'Unlimited',894 );895896 const maxWaitBlocks = 3;897 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);898899 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {900 const xcmpSuccess = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success);901 expect(xcmpSuccess.messageHash).to.be.equal(messageSent.messageSent);902 });903 });904});905906907908describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {909 let alice: IKeyringPair;910 let alith: IKeyringPair;911912 const testAmount = 100_000_000_000n;913 let quartzParachainJunction;914 let quartzAccountJunction;915916 let quartzParachainMultilocation: any;917 let quartzAccountMultilocation: any;918 let quartzCombinedMultilocation: any;919920 let messageSent: any;921922 const maxWaitBlocks = 3;923924 before(async () => {925 await usingPlaygrounds(async (helper, privateKey) => {926 alice = await privateKey('//Alice');927928 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};929 quartzAccountJunction = {930 AccountId32: {931 network: 'Any',932 id: alice.addressRaw,933 },934 };935936 quartzParachainMultilocation = {937 V2: {938 parents: 1,939 interior: {940 X1: quartzParachainJunction,941 },942 },943 };944945 quartzAccountMultilocation = {946 V2: {947 parents: 0,948 interior: {949 X1: quartzAccountJunction,950 },951 },952 };953954 quartzCombinedMultilocation = {955 V2: {956 parents: 1,957 interior: {958 X2: [quartzParachainJunction, quartzAccountJunction],959 },960 },961 };962963 964 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);965 });966967 968 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {969 alith = helper.account.alithAccount();970 });971 });972973 const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {974 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash975 && event.outcome.isFailedToTransactAsset);976 };977978 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {979 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {980 const id = {981 Token: 'KAR',982 };983 const destination = quartzCombinedMultilocation;984 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');985986 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);987 });988989 await expectFailedToTransact(helper, messageSent);990 });991992 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {993 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {994 const id = 'SelfReserve';995 const destination = quartzCombinedMultilocation;996 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');997998 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);999 });10001001 await expectFailedToTransact(helper, messageSent);1002 });10031004 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {1005 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1006 const destinationParachain = quartzParachainMultilocation;1007 const beneficiary = quartzAccountMultilocation;1008 const assets = {1009 V2: [{1010 id: {1011 Concrete: {1012 parents: 0,1013 interior: 'Here',1014 },1015 },1016 fun: {1017 Fungible: testAmount,1018 },1019 }],1020 };1021 const feeAssetItem = 0;10221023 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1024 destinationParachain,1025 beneficiary,1026 assets,1027 feeAssetItem,1028 ]);10291030 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1031 });10321033 await expectFailedToTransact(helper, messageSent);1034 });1035});10361037describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {1038 1039 let alice: IKeyringPair;1040 let quartzAssetLocation;10411042 let randomAccountQuartz: IKeyringPair;1043 let randomAccountMoonriver: IKeyringPair;10441045 1046 let assetId: string;10471048 const quartzAssetMetadata = {1049 name: 'xcQuartz',1050 symbol: 'xcQTZ',1051 decimals: 18,1052 isFrozen: false,1053 minimalBalance: 1n,1054 };10551056 let balanceQuartzTokenInit: bigint;1057 let balanceQuartzTokenMiddle: bigint;1058 let balanceQuartzTokenFinal: bigint;1059 let balanceForeignQtzTokenInit: bigint;1060 let balanceForeignQtzTokenMiddle: bigint;1061 let balanceForeignQtzTokenFinal: bigint;1062 let balanceMovrTokenInit: bigint;1063 let balanceMovrTokenMiddle: bigint;1064 let balanceMovrTokenFinal: bigint;10651066 before(async () => {1067 await usingPlaygrounds(async (helper, privateKey) => {1068 alice = await privateKey('//Alice');1069 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);10701071 balanceForeignQtzTokenInit = 0n;10721073 1074 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1075 });10761077 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1078 const alithAccount = helper.account.alithAccount();1079 const baltatharAccount = helper.account.baltatharAccount();1080 const dorothyAccount = helper.account.dorothyAccount();10811082 randomAccountMoonriver = helper.account.create();10831084 1085 console.log('Sponsoring Dorothy.......');1086 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1087 console.log('Sponsoring Dorothy.......DONE');1088 10891090 quartzAssetLocation = {1091 XCM: {1092 parents: 1,1093 interior: {X1: {Parachain: QUARTZ_CHAIN}},1094 },1095 };1096 const existentialDeposit = 1n;1097 const isSufficient = true;1098 const unitsPerSecond = 1n;1099 const numAssetsWeightHint = 0;11001101 if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {1102 console.log('Quartz asset already registered on Moonriver');1103 } else {1104 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1105 location: quartzAssetLocation,1106 metadata: quartzAssetMetadata,1107 existentialDeposit,1108 isSufficient,1109 unitsPerSecond,1110 numAssetsWeightHint,1111 });11121113 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);11141115 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);1116 }1117 1118 console.log('Acquire Quartz AssetId Info on Moonriver.......');11191120 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();11211122 console.log('QTZ asset ID is %s', assetId);1123 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1124 11251126 1127 console.log('Sponsoring random Account.......');1128 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1129 console.log('Sponsoring random Account.......DONE');1130 11311132 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1133 });11341135 await usingPlaygrounds(async (helper) => {1136 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1137 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1138 });1139 });11401141 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1142 const currencyId = 0;1143 const dest = {1144 V2: {1145 parents: 1,1146 interior: {1147 X2: [1148 {Parachain: MOONRIVER_CHAIN},1149 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1150 ],1151 },1152 },1153 };1154 const amount = TRANSFER_AMOUNT;11551156 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');11571158 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1159 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;11601161 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1162 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1163 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;11641165 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1166 await helper.wait.newBlocks(3);11671168 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);11691170 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1171 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1172 expect(movrFees == 0n).to.be.true;11731174 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; 1175 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1176 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1177 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1178 });1179 });11801181 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1182 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1183 const asset = {1184 V2: {1185 id: {1186 Concrete: {1187 parents: 1,1188 interior: {1189 X1: {Parachain: QUARTZ_CHAIN},1190 },1191 },1192 },1193 fun: {1194 Fungible: TRANSFER_AMOUNT,1195 },1196 },1197 };1198 const destination = {1199 V2: {1200 parents: 1,1201 interior: {1202 X2: [1203 {Parachain: QUARTZ_CHAIN},1204 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1205 ],1206 },1207 },1208 };12091210 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');12111212 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);12131214 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1215 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1216 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;12171218 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);12191220 expect(qtzRandomAccountAsset).to.be.null;12211222 balanceForeignQtzTokenFinal = 0n;12231224 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1225 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1226 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1227 });12281229 await helper.wait.newBlocks(3);12301231 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1232 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1233 expect(actuallyDelivered > 0).to.be.true;12341235 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));12361237 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1238 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1239 expect(qtzFees == 0n).to.be.true;1240 });12411242 itSub('Moonriver can send only up to its balance', async ({helper}) => {1243 1244 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1245 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1246 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);12471248 const moreThanMoonriverHas = moonriverBalance * 2n;12491250 let targetAccountBalance = 0n;1251 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);12521253 const quartzMultilocation = {1254 V2: {1255 parents: 1,1256 interior: {1257 X1: {Parachain: QUARTZ_CHAIN},1258 },1259 },1260 };12611262 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1263 targetAccount.addressRaw,1264 {1265 Concrete: {1266 parents: 0,1267 interior: 'Here',1268 },1269 },1270 moreThanMoonriverHas,1271 );12721273 let maliciousXcmProgramSent: any;1274 const maxWaitBlocks = 3;12751276 1277 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1278 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);12791280 1281 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1282 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);12831284 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1285 });12861287 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1288 && event.outcome.isFailedToTransactAsset);12891290 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1291 expect(targetAccountBalance).to.be.equal(0n);12921293 1294 const validTransferAmount = moonriverBalance / 2n;1295 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1296 targetAccount.addressRaw,1297 {1298 Concrete: {1299 parents: 0,1300 interior: 'Here',1301 },1302 },1303 validTransferAmount,1304 );13051306 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1307 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);13081309 1310 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1311 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1312 });13131314 await helper.wait.newBlocks(maxWaitBlocks);13151316 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1317 expect(targetAccountBalance).to.be.equal(validTransferAmount);1318 });13191320 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1321 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1322 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);13231324 const quartzMultilocation = {1325 V2: {1326 parents: 1,1327 interior: {1328 X1: {1329 Parachain: QUARTZ_CHAIN,1330 },1331 },1332 },1333 };13341335 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1336 targetAccount.addressRaw,1337 {1338 Concrete: {1339 parents: 0,1340 interior: {1341 X1: {1342 Parachain: QUARTZ_CHAIN,1343 },1344 },1345 },1346 },1347 testAmount,1348 );13491350 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1351 targetAccount.addressRaw,1352 {1353 Concrete: {1354 parents: 0,1355 interior: 'Here',1356 },1357 },1358 testAmount,1359 );13601361 let maliciousXcmProgramFullIdSent: any;1362 let maliciousXcmProgramHereIdSent: any;1363 const maxWaitBlocks = 3;13641365 1366 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1367 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);13681369 1370 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1371 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);13721373 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1374 });13751376 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1377 && event.outcome.isUntrustedReserveLocation);13781379 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1380 expect(accountBalance).to.be.equal(0n);13811382 1383 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1384 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);13851386 1387 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1388 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);13891390 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1391 });13921393 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1394 && event.outcome.isUntrustedReserveLocation);13951396 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1397 expect(accountBalance).to.be.equal(0n);1398 });1399});14001401describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1402 let alice: IKeyringPair;1403 let sender: IKeyringPair;14041405 const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; 1406 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; 14071408 1409 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); 1410 const unitsPerSecond = 500_451_000_000_000_000_000n; 1411 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); 1412 const qtzToShidenArrived = 7_998_196_000_000_000_000n; 14131414 1415 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); 1416 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; 14171418 let balanceAfterQuartzToShidenXCM: bigint;14191420 before(async () => {1421 await usingPlaygrounds(async (helper, privateKey) => {1422 alice = await privateKey('//Alice');1423 [sender] = await helper.arrange.createAccounts([100n], alice);1424 console.log('sender', sender.address);14251426 1427 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1428 });14291430 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1431 if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {1432 console.log('1. Create foreign asset and metadata');1433 await helper.getSudo().assets.forceCreate(1434 alice,1435 QTZ_ASSET_ID_ON_SHIDEN,1436 alice.address,1437 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1438 );14391440 await helper.assets.setMetadata(1441 alice,1442 QTZ_ASSET_ID_ON_SHIDEN,1443 'Quartz',1444 'QTZ',1445 Number(QTZ_DECIMALS),1446 );14471448 console.log('2. Register asset location on Shiden');1449 const assetLocation = {1450 V2: {1451 parents: 1,1452 interior: {1453 X1: {1454 Parachain: QUARTZ_CHAIN,1455 },1456 },1457 },1458 };14591460 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);14611462 console.log('3. Set QTZ payment for XCM execution on Shiden');1463 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1464 } else {1465 console.log('QTZ is already registered on Shiden');1466 }1467 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1468 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1469 });1470 });14711472 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1473 const destination = {1474 V2: {1475 parents: 1,1476 interior: {1477 X1: {1478 Parachain: SHIDEN_CHAIN,1479 },1480 },1481 },1482 };14831484 const beneficiary = {1485 V2: {1486 parents: 0,1487 interior: {1488 X1: {1489 AccountId32: {1490 network: 'Any',1491 id: sender.addressRaw,1492 },1493 },1494 },1495 },1496 };14971498 const assets = {1499 V2: [1500 {1501 id: {1502 Concrete: {1503 parents: 0,1504 interior: 'Here',1505 },1506 },1507 fun: {1508 Fungible: qtzToShidenTransferred,1509 },1510 },1511 ],1512 };15131514 1515 const balanceBefore = await helper.balance.getSubstrate(sender.address);1516 console.log(`Initial balance is: ${balanceBefore}`);15171518 const feeAssetItem = 0;1519 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');15201521 1522 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1523 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1524 console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1525 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;15261527 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1528 await helper.wait.newBlocks(3);1529 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1530 const shidenBalance = await helper.balance.getSubstrate(sender.address);15311532 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1533 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);15341535 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1536 1537 expect(shidenBalance).to.eq(shidenInitialBalance);1538 });1539 });15401541 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1542 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1543 const destination = {1544 V2: {1545 parents: 1,1546 interior: {1547 X1: {1548 Parachain: QUARTZ_CHAIN,1549 },1550 },1551 },1552 };15531554 const beneficiary = {1555 V2: {1556 parents: 0,1557 interior: {1558 X1: {1559 AccountId32: {1560 network: 'Any',1561 id: sender.addressRaw,1562 },1563 },1564 },1565 },1566 };15671568 const assets = {1569 V2: [1570 {1571 id: {1572 Concrete: {1573 parents: 1,1574 interior: {1575 X1: {1576 Parachain: QUARTZ_CHAIN,1577 },1578 },1579 },1580 },1581 fun: {1582 Fungible: qtzFromShidenTransfered,1583 },1584 },1585 ],1586 };15871588 1589 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1590 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1591 expect(balanceSDNbefore).to.eq(shidenInitialBalance);15921593 const feeAssetItem = 0;1594 1595 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);15961597 1598 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1599 const balanceSDN = await helper.balance.getSubstrate(sender.address);1600 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);16011602 1603 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1604 1605 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1606 });16071608 await helper.wait.newBlocks(3);1609 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1610 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1611 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1612 });16131614 itSub('Shiden can send only up to its balance', async ({helper}) => {1615 1616 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1617 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1618 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);16191620 const moreThanShidenHas = shidenBalance * 2n;16211622 let targetAccountBalance = 0n;1623 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);16241625 const quartzMultilocation = {1626 V2: {1627 parents: 1,1628 interior: {1629 X1: {Parachain: QUARTZ_CHAIN},1630 },1631 },1632 };16331634 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1635 targetAccount.addressRaw,1636 {1637 Concrete: {1638 parents: 0,1639 interior: 'Here',1640 },1641 },1642 moreThanShidenHas,1643 );16441645 let maliciousXcmProgramSent: any;1646 const maxWaitBlocks = 3;16471648 1649 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1650 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);16511652 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1653 });16541655 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1656 && event.outcome.isFailedToTransactAsset);16571658 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1659 expect(targetAccountBalance).to.be.equal(0n);16601661 1662 const validTransferAmount = shidenBalance / 2n;1663 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1664 targetAccount.addressRaw,1665 {1666 Concrete: {1667 parents: 0,1668 interior: 'Here',1669 },1670 },1671 validTransferAmount,1672 );16731674 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1675 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1676 });16771678 await helper.wait.newBlocks(maxWaitBlocks);16791680 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1681 expect(targetAccountBalance).to.be.equal(validTransferAmount);1682 });16831684 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1685 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1686 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);16871688 const quartzMultilocation = {1689 V2: {1690 parents: 1,1691 interior: {1692 X1: {1693 Parachain: QUARTZ_CHAIN,1694 },1695 },1696 },1697 };16981699 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1700 targetAccount.addressRaw,1701 {1702 Concrete: {1703 parents: 1,1704 interior: {1705 X1: {1706 Parachain: QUARTZ_CHAIN,1707 },1708 },1709 },1710 },1711 testAmount,1712 );17131714 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1715 targetAccount.addressRaw,1716 {1717 Concrete: {1718 parents: 0,1719 interior: 'Here',1720 },1721 },1722 testAmount,1723 );17241725 let maliciousXcmProgramFullIdSent: any;1726 let maliciousXcmProgramHereIdSent: any;1727 const maxWaitBlocks = 3;17281729 1730 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1731 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);17321733 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1734 });17351736 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1737 && event.outcome.isUntrustedReserveLocation);17381739 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1740 expect(accountBalance).to.be.equal(0n);17411742 1743 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1744 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);17451746 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1747 });17481749 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1750 && event.outcome.isUntrustedReserveLocation);17511752 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1753 expect(accountBalance).to.be.equal(0n);1754 });1755});