difftreelog
Merge pull request #985 from UniqueNetwork/fix/xcm-release-60
in: master
4 files changed
.baedeker/xcm-quartz.jsonnetdiffbeforeafterboth--- a/.baedeker/xcm-quartz.jsonnet
+++ b/.baedeker/xcm-quartz.jsonnet
@@ -82,7 +82,7 @@
wantedKeys: 'para-nimbus',
legacyRpc: true,
},
- for name in ['alice', 'bob']
+ for name in ['alith', 'baltathar']
},
};
.baedeker/xcm-unique.jsonnetdiffbeforeafterboth--- a/.baedeker/xcm-unique.jsonnet
+++ b/.baedeker/xcm-unique.jsonnet
@@ -83,7 +83,7 @@
wantedKeys: 'para-nimbus',
legacyRpc: true,
},
- for name in ['alice', 'bob']
+ for name in ['alith', 'baltathar']
},
};
tests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';2122const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);23const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);24const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);25const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);26const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;34const shidenUrl = config.shidenUrl;3536const RELAY_DECIMALS = 12;37const STATEMINE_DECIMALS = 12;38const KARURA_DECIMALS = 12;39const SHIDEN_DECIMALS = 18n;40const QTZ_DECIMALS = 18n;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4546const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4748const USDT_ASSET_ID = 100;49const USDT_ASSET_METADATA_DECIMALS = 18;50const USDT_ASSET_METADATA_NAME = 'USDT';51const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';52const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;53const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5455const SAFE_XCM_VERSION = 2;5657describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {58 let alice: IKeyringPair;59 let bob: IKeyringPair;6061 let balanceStmnBefore: bigint;62 let balanceStmnAfter: bigint;6364 let balanceQuartzBefore: bigint;65 let balanceQuartzAfter: bigint;66 let balanceQuartzFinal: bigint;6768 let balanceBobBefore: bigint;69 let balanceBobAfter: bigint;70 let balanceBobFinal: bigint;7172 let balanceBobRelayTokenBefore: bigint;73 let balanceBobRelayTokenAfter: bigint;747576 before(async () => {77 await usingPlaygrounds(async (helper, privateKey) => {78 alice = await privateKey('//Alice');79 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor8081 // Set the default version to wrap the first message to other chains.82 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);83 });8485 await usingRelayPlaygrounds(relayUrl, async (helper) => {86 // Fund accounts on Statemine(t)87 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);88 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);89 });9091 await usingStateminePlaygrounds(statemineUrl, async (helper) => {92 const sovereignFundingAmount = 3_500_000_000n;9394 await helper.assets.create(95 alice,96 USDT_ASSET_ID,97 alice.address,98 USDT_ASSET_METADATA_MINIMAL_BALANCE,99 );100 await helper.assets.setMetadata(101 alice,102 USDT_ASSET_ID,103 USDT_ASSET_METADATA_NAME,104 USDT_ASSET_METADATA_DESCRIPTION,105 USDT_ASSET_METADATA_DECIMALS,106 );107 await helper.assets.mint(108 alice,109 USDT_ASSET_ID,110 alice.address,111 USDT_ASSET_AMOUNT,112 );113114 // funding parachain sovereing account on Statemine(t).115 // The sovereign account should be created before any action116 // (the assets pallet on Statemine(t) check if the sovereign account exists)117 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);118 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);119 });120121122 await usingPlaygrounds(async (helper) => {123 const location = {124 V2: {125 parents: 1,126 interior: {X3: [127 {128 Parachain: STATEMINE_CHAIN,129 },130 {131 PalletInstance: STATEMINE_PALLET_INSTANCE,132 },133 {134 GeneralIndex: USDT_ASSET_ID,135 },136 ]},137 },138 };139140 const metadata =141 {142 name: USDT_ASSET_ID,143 symbol: USDT_ASSET_METADATA_NAME,144 decimals: USDT_ASSET_METADATA_DECIMALS,145 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,146 };147 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);148 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);149 });150151152 // Providing the relay currency to the quartz sender account153 // (fee for USDT XCM are paid in relay tokens)154 await usingRelayPlaygrounds(relayUrl, async (helper) => {155 const destination = {156 V2: {157 parents: 0,158 interior: {X1: {159 Parachain: QUARTZ_CHAIN,160 },161 },162 }};163164 const beneficiary = {165 V2: {166 parents: 0,167 interior: {X1: {168 AccountId32: {169 network: 'Any',170 id: alice.addressRaw,171 },172 }},173 },174 };175176 const assets = {177 V2: [178 {179 id: {180 Concrete: {181 parents: 0,182 interior: 'Here',183 },184 },185 fun: {186 Fungible: TRANSFER_AMOUNT_RELAY,187 },188 },189 ],190 };191192 const feeAssetItem = 0;193194 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');195 });196197 });198199 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {200 await usingStateminePlaygrounds(statemineUrl, async (helper) => {201 const dest = {202 V2: {203 parents: 1,204 interior: {X1: {205 Parachain: QUARTZ_CHAIN,206 },207 },208 }};209210 const beneficiary = {211 V2: {212 parents: 0,213 interior: {X1: {214 AccountId32: {215 network: 'Any',216 id: alice.addressRaw,217 },218 }},219 },220 };221222 const assets = {223 V2: [224 {225 id: {226 Concrete: {227 parents: 0,228 interior: {229 X2: [230 {231 PalletInstance: STATEMINE_PALLET_INSTANCE,232 },233 {234 GeneralIndex: USDT_ASSET_ID,235 },236 ]},237 },238 },239 fun: {240 Fungible: TRANSFER_AMOUNT,241 },242 },243 ],244 };245246 const feeAssetItem = 0;247248 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);249 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');250251 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);252253 // common good parachain take commission in it native token254 console.log(255 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',256 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),257 );258 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;259260 });261262263 // ensure that asset has been delivered264 await helper.wait.newBlocks(3);265266 // expext collection id will be with id 1267 const free = await helper.ft.getBalance(1, {Substrate: alice.address});268269 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);270271 console.log(272 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',273 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),274 );275 console.log(276 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',277 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),278 );279 // commission has not paid in USDT token280 expect(free).to.be.equal(TRANSFER_AMOUNT);281 // ... and parachain native token282 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;283 });284285 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {286 const destination = {287 V2: {288 parents: 1,289 interior: {X2: [290 {291 Parachain: STATEMINE_CHAIN,292 },293 {294 AccountId32: {295 network: 'Any',296 id: alice.addressRaw,297 },298 },299 ]},300 },301 };302303 const relayFee = 400_000_000_000_000n;304 const currencies: [any, bigint][] = [305 [306 {307 ForeignAssetId: 0,308 },309 TRANSFER_AMOUNT,310 ],311 [312 {313 NativeAssetId: 'Parent',314 },315 relayFee,316 ],317 ];318319 const feeItem = 1;320321 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');322323 // the commission has been paid in parachain native token324 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);325 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));326 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;327328 await usingStateminePlaygrounds(statemineUrl, async (helper) => {329 await helper.wait.newBlocks(3);330331 // The USDT token never paid fees. Its amount not changed from begin value.332 // Also check that xcm transfer has been succeeded333 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;334 });335 });336337 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {338 balanceBobBefore = await helper.balance.getSubstrate(bob.address);339 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});340341 await usingRelayPlaygrounds(relayUrl, async (helper) => {342 const destination = {343 V2: {344 parents: 0,345 interior: {X1: {346 Parachain: QUARTZ_CHAIN,347 },348 },349 }};350351 const beneficiary = {352 V2: {353 parents: 0,354 interior: {X1: {355 AccountId32: {356 network: 'Any',357 id: bob.addressRaw,358 },359 }},360 },361 };362363 const assets = {364 V2: [365 {366 id: {367 Concrete: {368 parents: 0,369 interior: 'Here',370 },371 },372 fun: {373 Fungible: TRANSFER_AMOUNT_RELAY,374 },375 },376 ],377 };378379 const feeAssetItem = 0;380381 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');382 });383384 await helper.wait.newBlocks(3);385386 balanceBobAfter = await helper.balance.getSubstrate(bob.address);387 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});388389 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;390 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;391 console.log(392 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',393 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),394 );395 console.log(396 '[Relay (Westend) -> Quartz] transaction fees: %s WND',397 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),398 );399 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);400 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;401 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;402 });403404 itSub('Should connect and send Relay token back', async ({helper}) => {405 let relayTokenBalanceBefore: bigint;406 let relayTokenBalanceAfter: bigint;407 await usingRelayPlaygrounds(relayUrl, async (helper) => {408 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);409 });410411 const destination = {412 V2: {413 parents: 1,414 interior: {415 X1:{416 AccountId32: {417 network: 'Any',418 id: bob.addressRaw,419 },420 },421 },422 },423 };424425 const currencies: any = [426 [427 {428 NativeAssetId: 'Parent',429 },430 TRANSFER_AMOUNT_RELAY,431 ],432 ];433434 const feeItem = 0;435436 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438 balanceBobFinal = await helper.balance.getSubstrate(bob.address);439 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441 await usingRelayPlaygrounds(relayUrl, async (helper) => {442 await helper.wait.newBlocks(10);443 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448 });449 });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {453 let alice: IKeyringPair;454 let randomAccount: IKeyringPair;455456 let balanceQuartzTokenInit: bigint;457 let balanceQuartzTokenMiddle: bigint;458 let balanceQuartzTokenFinal: bigint;459 let balanceKaruraTokenInit: bigint;460 let balanceKaruraTokenMiddle: bigint;461 let balanceKaruraTokenFinal: bigint;462 let balanceQuartzForeignTokenInit: bigint;463 let balanceQuartzForeignTokenMiddle: bigint;464 let balanceQuartzForeignTokenFinal: bigint;465466 // computed by a test transfer from prod Quartz to prod Karura.467 // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9468 // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)469 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;470471 const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;472473 before(async () => {474 await usingPlaygrounds(async (helper, privateKey) => {475 alice = await privateKey('//Alice');476 [randomAccount] = await helper.arrange.createAccounts([0n], alice);477478 // Set the default version to wrap the first message to other chains.479 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);480 });481482 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {483 const destination = {484 V2: {485 parents: 1,486 interior: {487 X1: {488 Parachain: QUARTZ_CHAIN,489 },490 },491 },492 };493494 const metadata = {495 name: 'Quartz',496 symbol: 'QTZ',497 decimals: 18,498 minimalBalance: 1000000000000000000n,499 };500501 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);502 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);503 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);504 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});505 });506507 await usingPlaygrounds(async (helper) => {508 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);509 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);510 });511 });512513 itSub('Should connect and send QTZ to Karura', async ({helper}) => {514 const destination = {515 V2: {516 parents: 1,517 interior: {518 X1: {519 Parachain: KARURA_CHAIN,520 },521 },522 },523 };524525 const beneficiary = {526 V2: {527 parents: 0,528 interior: {529 X1: {530 AccountId32: {531 network: 'Any',532 id: randomAccount.addressRaw,533 },534 },535 },536 },537 };538539 const assets = {540 V2: [541 {542 id: {543 Concrete: {544 parents: 0,545 interior: 'Here',546 },547 },548 fun: {549 Fungible: TRANSFER_AMOUNT,550 },551 },552 ],553 };554555 const feeAssetItem = 0;556557 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');558 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);559560 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;561 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;562 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));563564 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {565 await helper.wait.newBlocks(3);566567 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});568 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);569570 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;571 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;572 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;573574 console.log(575 '[Quartz -> Karura] transaction fees on Karura: %s KAR',576 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),577 );578 console.log(579 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',580 helper.util.bigIntToDecimals(karUnqFees),581 );582 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));583 expect(karFees == 0n).to.be.true;584 expect(585 karUnqFees == expectedKaruraIncomeFee,586 'Karura took different income fee, check the Karura foreign asset config',587 ).to.be.true;588 });589 });590591 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {592 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {593 const destination = {594 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, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');615 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);616 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);617618 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;619 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;620621 console.log(622 '[Karura -> Quartz] transaction fees on Karura: %s KAR',623 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),624 );625 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));626627 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;628 expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;629 });630631 await helper.wait.newBlocks(3);632633 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);634 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;635 expect(actuallyDelivered > 0).to.be.true;636637 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));638639 const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;640 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));641 expect(qtzFees == 0n).to.be.true;642 });643644 itSub('Karura can send only up to its balance', async ({helper}) => {645 // set Karura's sovereign account's balance646 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 = 3;677678 // Try to trick Quartz679 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 // But Karura still can send the correct amount692 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 // Try to trick Quartz using full QTZ identification760 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 // Try to trick Quartz using shortened QTZ identification773 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 });785});786787// These tests are relevant only when788// the the corresponding foreign assets are not registered789describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {790 let alice: IKeyringPair;791 let alith: IKeyringPair;792793 const testAmount = 100_000_000_000n;794 let quartzParachainJunction;795 let quartzAccountJunction;796797 let quartzParachainMultilocation: any;798 let quartzAccountMultilocation: any;799 let quartzCombinedMultilocation: any;800801 let messageSent: any;802803 const maxWaitBlocks = 3;804805 before(async () => {806 await usingPlaygrounds(async (helper, privateKey) => {807 alice = await privateKey('//Alice');808809 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};810 quartzAccountJunction = {811 AccountId32: {812 network: 'Any',813 id: alice.addressRaw,814 },815 };816817 quartzParachainMultilocation = {818 V2: {819 parents: 1,820 interior: {821 X1: quartzParachainJunction,822 },823 },824 };825826 quartzAccountMultilocation = {827 V2: {828 parents: 0,829 interior: {830 X1: quartzAccountJunction,831 },832 },833 };834835 quartzCombinedMultilocation = {836 V2: {837 parents: 1,838 interior: {839 X2: [quartzParachainJunction, quartzAccountJunction],840 },841 },842 };843844 // Set the default version to wrap the first message to other chains.845 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);846 });847848 // eslint-disable-next-line require-await849 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {850 alith = helper.account.alithAccount();851 });852 });853854 const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {855 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash856 && event.outcome().isFailedToTransactAsset);857 };858859 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {860 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {861 const id = {862 Token: 'KAR',863 };864 const destination = quartzCombinedMultilocation;865 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');866867 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);868 });869870 await expectFailedToTransact(helper, messageSent);871 });872873 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {874 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {875 const id = 'SelfReserve';876 const destination = quartzCombinedMultilocation;877 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');878879 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);880 });881882 await expectFailedToTransact(helper, messageSent);883 });884885 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {886 await usingShidenPlaygrounds(shidenUrl, async (helper) => {887 const destinationParachain = quartzParachainMultilocation;888 const beneficiary = quartzAccountMultilocation;889 const assets = {890 V2: [{891 id: {892 Concrete: {893 parents: 0,894 interior: 'Here',895 },896 },897 fun: {898 Fungible: testAmount,899 },900 }],901 };902 const feeAssetItem = 0;903904 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [905 destinationParachain,906 beneficiary,907 assets,908 feeAssetItem,909 ]);910911 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);912 });913914 await expectFailedToTransact(helper, messageSent);915 });916});917918describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {919 // Quartz constants920 let alice: IKeyringPair;921 let quartzAssetLocation;922923 let randomAccountQuartz: IKeyringPair;924 let randomAccountMoonriver: IKeyringPair;925926 // Moonriver constants927 let assetId: string;928929 const quartzAssetMetadata = {930 name: 'xcQuartz',931 symbol: 'xcQTZ',932 decimals: 18,933 isFrozen: false,934 minimalBalance: 1n,935 };936937 let balanceQuartzTokenInit: bigint;938 let balanceQuartzTokenMiddle: bigint;939 let balanceQuartzTokenFinal: bigint;940 let balanceForeignQtzTokenInit: bigint;941 let balanceForeignQtzTokenMiddle: bigint;942 let balanceForeignQtzTokenFinal: bigint;943 let balanceMovrTokenInit: bigint;944 let balanceMovrTokenMiddle: bigint;945 let balanceMovrTokenFinal: bigint;946947 before(async () => {948 await usingPlaygrounds(async (helper, privateKey) => {949 alice = await privateKey('//Alice');950 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);951952 balanceForeignQtzTokenInit = 0n;953954 // Set the default version to wrap the first message to other chains.955 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);956 });957958 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {959 const alithAccount = helper.account.alithAccount();960 const baltatharAccount = helper.account.baltatharAccount();961 const dorothyAccount = helper.account.dorothyAccount();962963 randomAccountMoonriver = helper.account.create();964965 // >>> Sponsoring Dorothy >>>966 console.log('Sponsoring Dorothy.......');967 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);968 console.log('Sponsoring Dorothy.......DONE');969 // <<< Sponsoring Dorothy <<<970971 quartzAssetLocation = {972 XCM: {973 parents: 1,974 interior: {X1: {Parachain: QUARTZ_CHAIN}},975 },976 };977 const existentialDeposit = 1n;978 const isSufficient = true;979 const unitsPerSecond = 1n;980 const numAssetsWeightHint = 0;981982 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({983 location: quartzAssetLocation,984 metadata: quartzAssetMetadata,985 existentialDeposit,986 isSufficient,987 unitsPerSecond,988 numAssetsWeightHint,989 });990991 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);992993 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);994995 // >>> Acquire Quartz AssetId Info on Moonriver >>>996 console.log('Acquire Quartz AssetId Info on Moonriver.......');997998 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();9991000 console.log('QTZ asset ID is %s', assetId);1001 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1002 // >>> Acquire Quartz AssetId Info on Moonriver >>>10031004 // >>> Sponsoring random Account >>>1005 console.log('Sponsoring random Account.......');1006 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1007 console.log('Sponsoring random Account.......DONE');1008 // <<< Sponsoring random Account <<<10091010 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1011 });10121013 await usingPlaygrounds(async (helper) => {1014 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1015 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1016 });1017 });10181019 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1020 const currencyId = {1021 NativeAssetId: 'Here',1022 };1023 const dest = {1024 V2: {1025 parents: 1,1026 interior: {1027 X2: [1028 {Parachain: MOONRIVER_CHAIN},1029 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1030 ],1031 },1032 },1033 };1034 const amount = TRANSFER_AMOUNT;10351036 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10371038 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1039 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10401041 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1042 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1043 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10441045 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1046 await helper.wait.newBlocks(3);10471048 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10491050 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1051 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1052 expect(movrFees == 0n).to.be.true;10531054 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1055 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1056 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1057 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1058 });1059 });10601061 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1062 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1063 const asset = {1064 V2: {1065 id: {1066 Concrete: {1067 parents: 1,1068 interior: {1069 X1: {Parachain: QUARTZ_CHAIN},1070 },1071 },1072 },1073 fun: {1074 Fungible: TRANSFER_AMOUNT,1075 },1076 },1077 };1078 const destination = {1079 V2: {1080 parents: 1,1081 interior: {1082 X2: [1083 {Parachain: QUARTZ_CHAIN},1084 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1085 ],1086 },1087 },1088 };10891090 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10911092 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10931094 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1095 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1096 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10971098 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);10991100 expect(qtzRandomAccountAsset).to.be.null;11011102 balanceForeignQtzTokenFinal = 0n;11031104 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1105 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1106 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1107 });11081109 await helper.wait.newBlocks(3);11101111 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1112 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1113 expect(actuallyDelivered > 0).to.be.true;11141115 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11161117 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1118 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1119 expect(qtzFees == 0n).to.be.true;1120 });11211122 itSub('Moonriver can send only up to its balance', async ({helper}) => {1123 // set Moonriver's sovereign account's balance1124 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1125 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1126 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11271128 const moreThanMoonriverHas = moonriverBalance * 2n;11291130 let targetAccountBalance = 0n;1131 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11321133 const quartzMultilocation = {1134 V2: {1135 parents: 1,1136 interior: {1137 X1: {Parachain: QUARTZ_CHAIN},1138 },1139 },1140 };11411142 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1143 targetAccount.addressRaw,1144 {1145 Concrete: {1146 parents: 0,1147 interior: 'Here',1148 },1149 },1150 moreThanMoonriverHas,1151 );11521153 let maliciousXcmProgramSent: any;1154 const maxWaitBlocks = 3;11551156 // Try to trick Quartz1157 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1158 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11591160 // Needed to bypass the call filter.1161 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1162 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11631164 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1165 });11661167 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1168 && event.outcome().isFailedToTransactAsset);11691170 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1171 expect(targetAccountBalance).to.be.equal(0n);11721173 // But Moonriver still can send the correct amount1174 const validTransferAmount = moonriverBalance / 2n;1175 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1176 targetAccount.addressRaw,1177 {1178 Concrete: {1179 parents: 0,1180 interior: 'Here',1181 },1182 },1183 validTransferAmount,1184 );11851186 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1187 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11881189 // Needed to bypass the call filter.1190 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1191 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1192 });11931194 await helper.wait.newBlocks(maxWaitBlocks);11951196 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1197 expect(targetAccountBalance).to.be.equal(validTransferAmount);1198 });11991200 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1201 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1202 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12031204 const quartzMultilocation = {1205 V2: {1206 parents: 1,1207 interior: {1208 X1: {1209 Parachain: QUARTZ_CHAIN,1210 },1211 },1212 },1213 };12141215 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1216 targetAccount.addressRaw,1217 {1218 Concrete: {1219 parents: 0,1220 interior: {1221 X1: {1222 Parachain: QUARTZ_CHAIN,1223 },1224 },1225 },1226 },1227 testAmount,1228 );12291230 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1231 targetAccount.addressRaw,1232 {1233 Concrete: {1234 parents: 0,1235 interior: 'Here',1236 },1237 },1238 testAmount,1239 );12401241 let maliciousXcmProgramFullIdSent: any;1242 let maliciousXcmProgramHereIdSent: any;1243 const maxWaitBlocks = 3;12441245 // Try to trick Quartz using full QTZ identification1246 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1247 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12481249 // Needed to bypass the call filter.1250 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1251 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12521253 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1254 });12551256 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1257 && event.outcome().isUntrustedReserveLocation);12581259 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1260 expect(accountBalance).to.be.equal(0n);12611262 // Try to trick Quartz using shortened QTZ identification1263 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1264 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12651266 // Needed to bypass the call filter.1267 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1268 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12691270 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1271 });12721273 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1274 && event.outcome().isUntrustedReserveLocation);12751276 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1277 expect(accountBalance).to.be.equal(0n);1278 });1279});12801281describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1282 let alice: IKeyringPair;1283 let sender: IKeyringPair;12841285 const QTZ_ASSET_ID_ON_SHIDEN = 1;1286 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12871288 // Quartz -> Shiden1289 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1290 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1291 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1292 const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens12931294 // Shiden -> Quartz1295 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1296 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ12971298 let balanceAfterQuartzToShidenXCM: bigint;12991300 before(async () => {1301 await usingPlaygrounds(async (helper, privateKey) => {1302 alice = await privateKey('//Alice');1303 [sender] = await helper.arrange.createAccounts([100n], alice);1304 console.log('sender', sender.address);13051306 // Set the default version to wrap the first message to other chains.1307 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1308 });13091310 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1311 console.log('1. Create foreign asset and metadata');1312 // TODO update metadata with values from production1313 await helper.assets.create(1314 alice,1315 QTZ_ASSET_ID_ON_SHIDEN,1316 alice.address,1317 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1318 );13191320 await helper.assets.setMetadata(1321 alice,1322 QTZ_ASSET_ID_ON_SHIDEN,1323 'Cross chain QTZ',1324 'xcQTZ',1325 Number(QTZ_DECIMALS),1326 );13271328 console.log('2. Register asset location on Shiden');1329 const assetLocation = {1330 V2: {1331 parents: 1,1332 interior: {1333 X1: {1334 Parachain: QUARTZ_CHAIN,1335 },1336 },1337 },1338 };13391340 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13411342 console.log('3. Set QTZ payment for XCM execution on Shiden');1343 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13441345 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1346 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1347 });1348 });13491350 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1351 const destination = {1352 V2: {1353 parents: 1,1354 interior: {1355 X1: {1356 Parachain: SHIDEN_CHAIN,1357 },1358 },1359 },1360 };13611362 const beneficiary = {1363 V2: {1364 parents: 0,1365 interior: {1366 X1: {1367 AccountId32: {1368 network: 'Any',1369 id: sender.addressRaw,1370 },1371 },1372 },1373 },1374 };13751376 const assets = {1377 V2: [1378 {1379 id: {1380 Concrete: {1381 parents: 0,1382 interior: 'Here',1383 },1384 },1385 fun: {1386 Fungible: qtzToShidenTransferred,1387 },1388 },1389 ],1390 };13911392 // Initial balance is 100 QTZ1393 const balanceBefore = await helper.balance.getSubstrate(sender.address);1394 console.log(`Initial balance is: ${balanceBefore}`);13951396 const feeAssetItem = 0;1397 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');13981399 // Balance after reserve transfer is less than 901400 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1401 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1402 console.log(`Quartz's QTZ commission is: ${balanceBefore-balanceAfterQuartzToShidenXCM}`);1403 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14041405 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1406 await helper.wait.newBlocks(3);1407 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1408 const shidenBalance = await helper.balance.getSubstrate(sender.address);14091410 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1411 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred-xcQTZbalance!}`);14121413 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1414 // SHD balance does not changed:1415 expect(shidenBalance).to.eq(shidenInitialBalance);1416 });1417 });14181419 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1420 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1421 const destination = {1422 V2: {1423 parents: 1,1424 interior: {1425 X1: {1426 Parachain: QUARTZ_CHAIN,1427 },1428 },1429 },1430 };14311432 const beneficiary = {1433 V2: {1434 parents: 0,1435 interior: {1436 X1: {1437 AccountId32: {1438 network: 'Any',1439 id: sender.addressRaw,1440 },1441 },1442 },1443 },1444 };14451446 const assets = {1447 V2: [1448 {1449 id: {1450 Concrete: {1451 parents: 1,1452 interior: {1453 X1: {1454 Parachain: QUARTZ_CHAIN,1455 },1456 },1457 },1458 },1459 fun: {1460 Fungible: qtzFromShidenTransfered,1461 },1462 },1463 ],1464 };14651466 // Initial balance is 1 SDN1467 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1468 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1469 expect(balanceSDNbefore).to.eq(shidenInitialBalance);14701471 const feeAssetItem = 0;1472 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1473 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14741475 // Balance after reserve transfer is less than 1 SDN1476 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1477 const balanceSDN = await helper.balance.getSubstrate(sender.address);1478 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14791480 // Assert: xcQTZ balance correctly decreased1481 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1482 // Assert: SDN balance is 0.996...1483 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1484 });14851486 await helper.wait.newBlocks(3);1487 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1488 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1489 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1490 });14911492 itSub('Shiden can send only up to its balance', async ({helper}) => {1493 // set Shiden's sovereign account's balance1494 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1495 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1496 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);14971498 const moreThanShidenHas = shidenBalance * 2n;14991500 let targetAccountBalance = 0n;1501 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15021503 const quartzMultilocation = {1504 V2: {1505 parents: 1,1506 interior: {1507 X1: {Parachain: QUARTZ_CHAIN},1508 },1509 },1510 };15111512 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1513 targetAccount.addressRaw,1514 {1515 Concrete: {1516 parents: 0,1517 interior: 'Here',1518 },1519 },1520 moreThanShidenHas,1521 );15221523 let maliciousXcmProgramSent: any;1524 const maxWaitBlocks = 3;15251526 // Try to trick Quartz1527 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1528 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15291530 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1531 });15321533 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1534 && event.outcome().isFailedToTransactAsset);15351536 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1537 expect(targetAccountBalance).to.be.equal(0n);15381539 // But Shiden still can send the correct amount1540 const validTransferAmount = shidenBalance / 2n;1541 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1542 targetAccount.addressRaw,1543 {1544 Concrete: {1545 parents: 0,1546 interior: 'Here',1547 },1548 },1549 validTransferAmount,1550 );15511552 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1553 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1554 });15551556 await helper.wait.newBlocks(maxWaitBlocks);15571558 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1559 expect(targetAccountBalance).to.be.equal(validTransferAmount);1560 });15611562 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1563 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1564 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15651566 const quartzMultilocation = {1567 V2: {1568 parents: 1,1569 interior: {1570 X1: {1571 Parachain: QUARTZ_CHAIN,1572 },1573 },1574 },1575 };15761577 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1578 targetAccount.addressRaw,1579 {1580 Concrete: {1581 parents: 1,1582 interior: {1583 X1: {1584 Parachain: QUARTZ_CHAIN,1585 },1586 },1587 },1588 },1589 testAmount,1590 );15911592 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1593 targetAccount.addressRaw,1594 {1595 Concrete: {1596 parents: 0,1597 interior: 'Here',1598 },1599 },1600 testAmount,1601 );16021603 let maliciousXcmProgramFullIdSent: any;1604 let maliciousXcmProgramHereIdSent: any;1605 const maxWaitBlocks = 3;16061607 // Try to trick Quartz using full QTZ identification1608 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1609 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16101611 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1612 });16131614 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1615 && event.outcome().isUntrustedReserveLocation);16161617 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1618 expect(accountBalance).to.be.equal(0n);16191620 // Try to trick Quartz using shortened QTZ identification1621 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1622 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16231624 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1625 });16261627 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1628 && event.outcome().isUntrustedReserveLocation);16291630 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1631 expect(accountBalance).to.be.equal(0n);1632 });1633});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';2122const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);23const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);24const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);25const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);26const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;34const shidenUrl = config.shidenUrl;3536const RELAY_DECIMALS = 12;37const STATEMINE_DECIMALS = 12;38const KARURA_DECIMALS = 12;39const SHIDEN_DECIMALS = 18n;40const QTZ_DECIMALS = 18n;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4546const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4748const USDT_ASSET_ID = 100;49const USDT_ASSET_METADATA_DECIMALS = 18;50const USDT_ASSET_METADATA_NAME = 'USDT';51const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';52const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;53const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5455const SAFE_XCM_VERSION = 2;5657describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {58 let alice: IKeyringPair;59 let bob: IKeyringPair;6061 let balanceStmnBefore: bigint;62 let balanceStmnAfter: bigint;6364 let balanceQuartzBefore: bigint;65 let balanceQuartzAfter: bigint;66 let balanceQuartzFinal: bigint;6768 let balanceBobBefore: bigint;69 let balanceBobAfter: bigint;70 let balanceBobFinal: bigint;7172 let balanceBobRelayTokenBefore: bigint;73 let balanceBobRelayTokenAfter: bigint;747576 before(async () => {77 await usingPlaygrounds(async (helper, privateKey) => {78 alice = await privateKey('//Alice');79 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor8081 // Set the default version to wrap the first message to other chains.82 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);83 });8485 await usingRelayPlaygrounds(relayUrl, async (helper) => {86 // Fund accounts on Statemine(t)87 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);88 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);89 });9091 await usingStateminePlaygrounds(statemineUrl, async (helper) => {92 const sovereignFundingAmount = 3_500_000_000n;9394 await helper.assets.create(95 alice,96 USDT_ASSET_ID,97 alice.address,98 USDT_ASSET_METADATA_MINIMAL_BALANCE,99 );100 await helper.assets.setMetadata(101 alice,102 USDT_ASSET_ID,103 USDT_ASSET_METADATA_NAME,104 USDT_ASSET_METADATA_DESCRIPTION,105 USDT_ASSET_METADATA_DECIMALS,106 );107 await helper.assets.mint(108 alice,109 USDT_ASSET_ID,110 alice.address,111 USDT_ASSET_AMOUNT,112 );113114 // funding parachain sovereing account on Statemine(t).115 // The sovereign account should be created before any action116 // (the assets pallet on Statemine(t) check if the sovereign account exists)117 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);118 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);119 });120121122 await usingPlaygrounds(async (helper) => {123 const location = {124 V2: {125 parents: 1,126 interior: {X3: [127 {128 Parachain: STATEMINE_CHAIN,129 },130 {131 PalletInstance: STATEMINE_PALLET_INSTANCE,132 },133 {134 GeneralIndex: USDT_ASSET_ID,135 },136 ]},137 },138 };139140 const metadata =141 {142 name: USDT_ASSET_ID,143 symbol: USDT_ASSET_METADATA_NAME,144 decimals: USDT_ASSET_METADATA_DECIMALS,145 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,146 };147 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);148 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);149 });150151152 // Providing the relay currency to the quartz sender account153 // (fee for USDT XCM are paid in relay tokens)154 await usingRelayPlaygrounds(relayUrl, async (helper) => {155 const destination = {156 V2: {157 parents: 0,158 interior: {X1: {159 Parachain: QUARTZ_CHAIN,160 },161 },162 }};163164 const beneficiary = {165 V2: {166 parents: 0,167 interior: {X1: {168 AccountId32: {169 network: 'Any',170 id: alice.addressRaw,171 },172 }},173 },174 };175176 const assets = {177 V2: [178 {179 id: {180 Concrete: {181 parents: 0,182 interior: 'Here',183 },184 },185 fun: {186 Fungible: TRANSFER_AMOUNT_RELAY,187 },188 },189 ],190 };191192 const feeAssetItem = 0;193194 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');195 });196197 });198199 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {200 await usingStateminePlaygrounds(statemineUrl, async (helper) => {201 const dest = {202 V2: {203 parents: 1,204 interior: {X1: {205 Parachain: QUARTZ_CHAIN,206 },207 },208 }};209210 const beneficiary = {211 V2: {212 parents: 0,213 interior: {X1: {214 AccountId32: {215 network: 'Any',216 id: alice.addressRaw,217 },218 }},219 },220 };221222 const assets = {223 V2: [224 {225 id: {226 Concrete: {227 parents: 0,228 interior: {229 X2: [230 {231 PalletInstance: STATEMINE_PALLET_INSTANCE,232 },233 {234 GeneralIndex: USDT_ASSET_ID,235 },236 ]},237 },238 },239 fun: {240 Fungible: TRANSFER_AMOUNT,241 },242 },243 ],244 };245246 const feeAssetItem = 0;247248 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);249 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');250251 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);252253 // common good parachain take commission in it native token254 console.log(255 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',256 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),257 );258 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;259260 });261262263 // ensure that asset has been delivered264 await helper.wait.newBlocks(3);265266 // expext collection id will be with id 1267 const free = await helper.ft.getBalance(1, {Substrate: alice.address});268269 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);270271 console.log(272 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',273 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),274 );275 console.log(276 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',277 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),278 );279 // commission has not paid in USDT token280 expect(free).to.be.equal(TRANSFER_AMOUNT);281 // ... and parachain native token282 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;283 });284285 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {286 const destination = {287 V2: {288 parents: 1,289 interior: {X2: [290 {291 Parachain: STATEMINE_CHAIN,292 },293 {294 AccountId32: {295 network: 'Any',296 id: alice.addressRaw,297 },298 },299 ]},300 },301 };302303 const relayFee = 400_000_000_000_000n;304 const currencies: [any, bigint][] = [305 [306 {307 ForeignAssetId: 0,308 },309 TRANSFER_AMOUNT,310 ],311 [312 {313 NativeAssetId: 'Parent',314 },315 relayFee,316 ],317 ];318319 const feeItem = 1;320321 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');322323 // the commission has been paid in parachain native token324 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);325 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));326 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;327328 await usingStateminePlaygrounds(statemineUrl, async (helper) => {329 await helper.wait.newBlocks(3);330331 // The USDT token never paid fees. Its amount not changed from begin value.332 // Also check that xcm transfer has been succeeded333 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;334 });335 });336337 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {338 balanceBobBefore = await helper.balance.getSubstrate(bob.address);339 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});340341 await usingRelayPlaygrounds(relayUrl, async (helper) => {342 const destination = {343 V2: {344 parents: 0,345 interior: {X1: {346 Parachain: QUARTZ_CHAIN,347 },348 },349 }};350351 const beneficiary = {352 V2: {353 parents: 0,354 interior: {X1: {355 AccountId32: {356 network: 'Any',357 id: bob.addressRaw,358 },359 }},360 },361 };362363 const assets = {364 V2: [365 {366 id: {367 Concrete: {368 parents: 0,369 interior: 'Here',370 },371 },372 fun: {373 Fungible: TRANSFER_AMOUNT_RELAY,374 },375 },376 ],377 };378379 const feeAssetItem = 0;380381 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');382 });383384 await helper.wait.newBlocks(3);385386 balanceBobAfter = await helper.balance.getSubstrate(bob.address);387 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});388389 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;390 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;391 console.log(392 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',393 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),394 );395 console.log(396 '[Relay (Westend) -> Quartz] transaction fees: %s WND',397 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),398 );399 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);400 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;401 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;402 });403404 itSub('Should connect and send Relay token back', async ({helper}) => {405 let relayTokenBalanceBefore: bigint;406 let relayTokenBalanceAfter: bigint;407 await usingRelayPlaygrounds(relayUrl, async (helper) => {408 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);409 });410411 const destination = {412 V2: {413 parents: 1,414 interior: {415 X1:{416 AccountId32: {417 network: 'Any',418 id: bob.addressRaw,419 },420 },421 },422 },423 };424425 const currencies: any = [426 [427 {428 NativeAssetId: 'Parent',429 },430 TRANSFER_AMOUNT_RELAY,431 ],432 ];433434 const feeItem = 0;435436 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438 balanceBobFinal = await helper.balance.getSubstrate(bob.address);439 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441 await usingRelayPlaygrounds(relayUrl, async (helper) => {442 await helper.wait.newBlocks(10);443 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448 });449 });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {453 let alice: IKeyringPair;454 let randomAccount: IKeyringPair;455456 let balanceQuartzTokenInit: bigint;457 let balanceQuartzTokenMiddle: bigint;458 let balanceQuartzTokenFinal: bigint;459 let balanceKaruraTokenInit: bigint;460 let balanceKaruraTokenMiddle: bigint;461 let balanceKaruraTokenFinal: bigint;462 let balanceQuartzForeignTokenInit: bigint;463 let balanceQuartzForeignTokenMiddle: bigint;464 let balanceQuartzForeignTokenFinal: bigint;465466 // computed by a test transfer from prod Quartz to prod Karura.467 // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9468 // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)469 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;470 const karuraEps = 8n * 10n ** 16n;471472 let karuraBackwardTransferAmount: bigint;473474 before(async () => {475 await usingPlaygrounds(async (helper, privateKey) => {476 alice = await privateKey('//Alice');477 [randomAccount] = await helper.arrange.createAccounts([0n], alice);478479 // Set the default version to wrap the first message to other chains.480 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481 });482483 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {484 const destination = {485 V2: {486 parents: 1,487 interior: {488 X1: {489 Parachain: QUARTZ_CHAIN,490 },491 },492 },493 };494495 const metadata = {496 name: 'Quartz',497 symbol: 'QTZ',498 decimals: 18,499 minimalBalance: 1000000000000000000n,500 };501502 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);503 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);504 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);505 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});506 });507508 await usingPlaygrounds(async (helper) => {509 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);510 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);511 });512 });513514 itSub('Should connect and send QTZ to Karura', async ({helper}) => {515 const destination = {516 V2: {517 parents: 1,518 interior: {519 X1: {520 Parachain: KARURA_CHAIN,521 },522 },523 },524 };525526 const beneficiary = {527 V2: {528 parents: 0,529 interior: {530 X1: {531 AccountId32: {532 network: 'Any',533 id: randomAccount.addressRaw,534 },535 },536 },537 },538 };539540 const assets = {541 V2: [542 {543 id: {544 Concrete: {545 parents: 0,546 interior: 'Here',547 },548 },549 fun: {550 Fungible: TRANSFER_AMOUNT,551 },552 },553 ],554 };555556 const feeAssetItem = 0;557558 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');559 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);560561 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;562 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;563 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));564565 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {566 await helper.wait.newBlocks(3);567568 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});569 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);570571 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;572 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;573 karuraBackwardTransferAmount = qtzIncomeTransfer;574575 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;576577 console.log(578 '[Quartz -> Karura] transaction fees on Karura: %s KAR',579 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),580 );581 console.log(582 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',583 helper.util.bigIntToDecimals(karUnqFees),584 );585 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));586 expect(karFees == 0n).to.be.true;587588 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;589590 expect(591 bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,592 'Karura took different income fee, check the Karura foreign asset config',593 ).to.be.true;594 });595 });596597 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {598 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {599 const destination = {600 V2: {601 parents: 1,602 interior: {603 X2: [604 {Parachain: QUARTZ_CHAIN},605 {606 AccountId32: {607 network: 'Any',608 id: randomAccount.addressRaw,609 },610 },611 ],612 },613 },614 };615616 const id = {617 ForeignAsset: 0,618 };619620 await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited');621 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);622 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);623624 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;625 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;626627 console.log(628 '[Karura -> Quartz] transaction fees on Karura: %s KAR',629 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),630 );631 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));632633 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;634 expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true;635 });636637 await helper.wait.newBlocks(3);638639 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);640 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;641 expect(actuallyDelivered > 0).to.be.true;642643 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));644645 const qtzFees = karuraBackwardTransferAmount - actuallyDelivered;646 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));647 expect(qtzFees == 0n).to.be.true;648 });649650 itSub('Karura can send only up to its balance', async ({helper}) => {651 // set Karura's sovereign account's balance652 const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);653 const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);654 await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);655656 const moreThanKaruraHas = karuraBalance * 2n;657658 let targetAccountBalance = 0n;659 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);660661 const quartzMultilocation = {662 V2: {663 parents: 1,664 interior: {665 X1: {Parachain: QUARTZ_CHAIN},666 },667 },668 };669670 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(671 targetAccount.addressRaw,672 {673 Concrete: {674 parents: 0,675 interior: 'Here',676 },677 },678 moreThanKaruraHas,679 );680681 let maliciousXcmProgramSent: any;682 const maxWaitBlocks = 5;683684 // Try to trick Quartz685 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {686 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);687688 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);689 });690691 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {692 return event.messageHash == maliciousXcmProgramSent.messageHash693 && event.outcome.isFailedToTransactAsset;694 });695696 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);697 expect(targetAccountBalance).to.be.equal(0n);698699 // But Karura still can send the correct amount700 const validTransferAmount = karuraBalance / 2n;701 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(702 targetAccount.addressRaw,703 {704 Concrete: {705 parents: 0,706 interior: 'Here',707 },708 },709 validTransferAmount,710 );711712 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {713 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);714 });715716 await helper.wait.newBlocks(maxWaitBlocks);717718 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);719 expect(targetAccountBalance).to.be.equal(validTransferAmount);720 });721722 itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {723 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);724 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);725726 const quartzMultilocation = {727 V2: {728 parents: 1,729 interior: {730 X1: {731 Parachain: QUARTZ_CHAIN,732 },733 },734 },735 };736737 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(738 targetAccount.addressRaw,739 {740 Concrete: {741 parents: 1,742 interior: {743 X1: {744 Parachain: QUARTZ_CHAIN,745 },746 },747 },748 },749 testAmount,750 );751752 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(753 targetAccount.addressRaw,754 {755 Concrete: {756 parents: 0,757 interior: 'Here',758 },759 },760 testAmount,761 );762763 let maliciousXcmProgramFullIdSent: any;764 let maliciousXcmProgramHereIdSent: any;765 const maxWaitBlocks = 3;766767 // Try to trick Quartz using full QTZ identification768 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {769 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);770771 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);772 });773774 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash775 && event.outcome.isUntrustedReserveLocation);776777 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);778 expect(accountBalance).to.be.equal(0n);779780 // Try to trick Quartz using shortened QTZ identification781 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {782 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);783784 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);785 });786787 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash788 && event.outcome.isUntrustedReserveLocation);789790 accountBalance = await helper.balance.getSubstrate(targetAccount.address);791 expect(accountBalance).to.be.equal(0n);792 });793});794795// These tests are relevant only when796// the the corresponding foreign assets are not registered797describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {798 let alice: IKeyringPair;799 let alith: IKeyringPair;800801 const testAmount = 100_000_000_000n;802 let quartzParachainJunction;803 let quartzAccountJunction;804805 let quartzParachainMultilocation: any;806 let quartzAccountMultilocation: any;807 let quartzCombinedMultilocation: any;808809 let messageSent: any;810811 const maxWaitBlocks = 3;812813 before(async () => {814 await usingPlaygrounds(async (helper, privateKey) => {815 alice = await privateKey('//Alice');816817 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};818 quartzAccountJunction = {819 AccountId32: {820 network: 'Any',821 id: alice.addressRaw,822 },823 };824825 quartzParachainMultilocation = {826 V2: {827 parents: 1,828 interior: {829 X1: quartzParachainJunction,830 },831 },832 };833834 quartzAccountMultilocation = {835 V2: {836 parents: 0,837 interior: {838 X1: quartzAccountJunction,839 },840 },841 };842843 quartzCombinedMultilocation = {844 V2: {845 parents: 1,846 interior: {847 X2: [quartzParachainJunction, quartzAccountJunction],848 },849 },850 };851852 // Set the default version to wrap the first message to other chains.853 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);854 });855856 // eslint-disable-next-line require-await857 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {858 alith = helper.account.alithAccount();859 });860 });861862 const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {863 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash864 && event.outcome.isFailedToTransactAsset);865 };866867 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {868 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {869 const id = {870 Token: 'KAR',871 };872 const destination = quartzCombinedMultilocation;873 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');874875 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);876 });877878 await expectFailedToTransact(helper, messageSent);879 });880881 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {882 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {883 const id = 'SelfReserve';884 const destination = quartzCombinedMultilocation;885 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');886887 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);888 });889890 await expectFailedToTransact(helper, messageSent);891 });892893 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {894 await usingShidenPlaygrounds(shidenUrl, async (helper) => {895 const destinationParachain = quartzParachainMultilocation;896 const beneficiary = quartzAccountMultilocation;897 const assets = {898 V2: [{899 id: {900 Concrete: {901 parents: 0,902 interior: 'Here',903 },904 },905 fun: {906 Fungible: testAmount,907 },908 }],909 };910 const feeAssetItem = 0;911912 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [913 destinationParachain,914 beneficiary,915 assets,916 feeAssetItem,917 ]);918919 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);920 });921922 await expectFailedToTransact(helper, messageSent);923 });924});925926describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {927 // Quartz constants928 let alice: IKeyringPair;929 let quartzAssetLocation;930931 let randomAccountQuartz: IKeyringPair;932 let randomAccountMoonriver: IKeyringPair;933934 // Moonriver constants935 let assetId: string;936937 const quartzAssetMetadata = {938 name: 'xcQuartz',939 symbol: 'xcQTZ',940 decimals: 18,941 isFrozen: false,942 minimalBalance: 1n,943 };944945 let balanceQuartzTokenInit: bigint;946 let balanceQuartzTokenMiddle: bigint;947 let balanceQuartzTokenFinal: bigint;948 let balanceForeignQtzTokenInit: bigint;949 let balanceForeignQtzTokenMiddle: bigint;950 let balanceForeignQtzTokenFinal: bigint;951 let balanceMovrTokenInit: bigint;952 let balanceMovrTokenMiddle: bigint;953 let balanceMovrTokenFinal: bigint;954955 before(async () => {956 await usingPlaygrounds(async (helper, privateKey) => {957 alice = await privateKey('//Alice');958 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);959960 balanceForeignQtzTokenInit = 0n;961962 // Set the default version to wrap the first message to other chains.963 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);964 });965966 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {967 const alithAccount = helper.account.alithAccount();968 const baltatharAccount = helper.account.baltatharAccount();969 const dorothyAccount = helper.account.dorothyAccount();970971 randomAccountMoonriver = helper.account.create();972973 // >>> Sponsoring Dorothy >>>974 console.log('Sponsoring Dorothy.......');975 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);976 console.log('Sponsoring Dorothy.......DONE');977 // <<< Sponsoring Dorothy <<<978979 quartzAssetLocation = {980 XCM: {981 parents: 1,982 interior: {X1: {Parachain: QUARTZ_CHAIN}},983 },984 };985 const existentialDeposit = 1n;986 const isSufficient = true;987 const unitsPerSecond = 1n;988 const numAssetsWeightHint = 0;989990 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({991 location: quartzAssetLocation,992 metadata: quartzAssetMetadata,993 existentialDeposit,994 isSufficient,995 unitsPerSecond,996 numAssetsWeightHint,997 });998999 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);10001001 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);10021003 // >>> Acquire Quartz AssetId Info on Moonriver >>>1004 console.log('Acquire Quartz AssetId Info on Moonriver.......');10051006 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10071008 console.log('QTZ asset ID is %s', assetId);1009 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1010 // >>> Acquire Quartz AssetId Info on Moonriver >>>10111012 // >>> Sponsoring random Account >>>1013 console.log('Sponsoring random Account.......');1014 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1015 console.log('Sponsoring random Account.......DONE');1016 // <<< Sponsoring random Account <<<10171018 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1019 });10201021 await usingPlaygrounds(async (helper) => {1022 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1023 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1024 });1025 });10261027 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1028 const currencyId = {1029 NativeAssetId: 'Here',1030 };1031 const dest = {1032 V2: {1033 parents: 1,1034 interior: {1035 X2: [1036 {Parachain: MOONRIVER_CHAIN},1037 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1038 ],1039 },1040 },1041 };1042 const amount = TRANSFER_AMOUNT;10431044 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10451046 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1047 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10481049 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1050 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1051 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10521053 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1054 await helper.wait.newBlocks(3);10551056 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10571058 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1059 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1060 expect(movrFees == 0n).to.be.true;10611062 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1063 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1064 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1065 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1066 });1067 });10681069 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1070 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1071 const asset = {1072 V2: {1073 id: {1074 Concrete: {1075 parents: 1,1076 interior: {1077 X1: {Parachain: QUARTZ_CHAIN},1078 },1079 },1080 },1081 fun: {1082 Fungible: TRANSFER_AMOUNT,1083 },1084 },1085 };1086 const destination = {1087 V2: {1088 parents: 1,1089 interior: {1090 X2: [1091 {Parachain: QUARTZ_CHAIN},1092 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1093 ],1094 },1095 },1096 };10971098 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10991100 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);11011102 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1103 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1104 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;11051106 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11071108 expect(qtzRandomAccountAsset).to.be.null;11091110 balanceForeignQtzTokenFinal = 0n;11111112 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1113 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1114 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1115 });11161117 await helper.wait.newBlocks(3);11181119 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1120 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1121 expect(actuallyDelivered > 0).to.be.true;11221123 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11241125 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1126 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1127 expect(qtzFees == 0n).to.be.true;1128 });11291130 itSub('Moonriver can send only up to its balance', async ({helper}) => {1131 // set Moonriver's sovereign account's balance1132 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1133 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1134 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11351136 const moreThanMoonriverHas = moonriverBalance * 2n;11371138 let targetAccountBalance = 0n;1139 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11401141 const quartzMultilocation = {1142 V2: {1143 parents: 1,1144 interior: {1145 X1: {Parachain: QUARTZ_CHAIN},1146 },1147 },1148 };11491150 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1151 targetAccount.addressRaw,1152 {1153 Concrete: {1154 parents: 0,1155 interior: 'Here',1156 },1157 },1158 moreThanMoonriverHas,1159 );11601161 let maliciousXcmProgramSent: any;1162 const maxWaitBlocks = 3;11631164 // Try to trick Quartz1165 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1166 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11671168 // Needed to bypass the call filter.1169 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1170 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11711172 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1173 });11741175 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1176 && event.outcome.isFailedToTransactAsset);11771178 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1179 expect(targetAccountBalance).to.be.equal(0n);11801181 // But Moonriver still can send the correct amount1182 const validTransferAmount = moonriverBalance / 2n;1183 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1184 targetAccount.addressRaw,1185 {1186 Concrete: {1187 parents: 0,1188 interior: 'Here',1189 },1190 },1191 validTransferAmount,1192 );11931194 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1195 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11961197 // Needed to bypass the call filter.1198 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1199 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1200 });12011202 await helper.wait.newBlocks(maxWaitBlocks);12031204 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1205 expect(targetAccountBalance).to.be.equal(validTransferAmount);1206 });12071208 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1209 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1210 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12111212 const quartzMultilocation = {1213 V2: {1214 parents: 1,1215 interior: {1216 X1: {1217 Parachain: QUARTZ_CHAIN,1218 },1219 },1220 },1221 };12221223 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1224 targetAccount.addressRaw,1225 {1226 Concrete: {1227 parents: 0,1228 interior: {1229 X1: {1230 Parachain: QUARTZ_CHAIN,1231 },1232 },1233 },1234 },1235 testAmount,1236 );12371238 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1239 targetAccount.addressRaw,1240 {1241 Concrete: {1242 parents: 0,1243 interior: 'Here',1244 },1245 },1246 testAmount,1247 );12481249 let maliciousXcmProgramFullIdSent: any;1250 let maliciousXcmProgramHereIdSent: any;1251 const maxWaitBlocks = 3;12521253 // Try to trick Quartz using full QTZ identification1254 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1255 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12561257 // Needed to bypass the call filter.1258 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1259 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12601261 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1262 });12631264 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1265 && event.outcome.isUntrustedReserveLocation);12661267 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1268 expect(accountBalance).to.be.equal(0n);12691270 // Try to trick Quartz using shortened QTZ identification1271 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1272 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12731274 // Needed to bypass the call filter.1275 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1276 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12771278 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1279 });12801281 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1282 && event.outcome.isUntrustedReserveLocation);12831284 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1285 expect(accountBalance).to.be.equal(0n);1286 });1287});12881289describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1290 let alice: IKeyringPair;1291 let sender: IKeyringPair;12921293 const QTZ_ASSET_ID_ON_SHIDEN = 1;1294 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12951296 // Quartz -> Shiden1297 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1298 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1299 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1300 const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens13011302 // Shiden -> Quartz1303 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1304 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ13051306 let balanceAfterQuartzToShidenXCM: bigint;13071308 before(async () => {1309 await usingPlaygrounds(async (helper, privateKey) => {1310 alice = await privateKey('//Alice');1311 [sender] = await helper.arrange.createAccounts([100n], alice);1312 console.log('sender', sender.address);13131314 // Set the default version to wrap the first message to other chains.1315 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1316 });13171318 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1319 console.log('1. Create foreign asset and metadata');1320 // TODO update metadata with values from production1321 await helper.assets.create(1322 alice,1323 QTZ_ASSET_ID_ON_SHIDEN,1324 alice.address,1325 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1326 );13271328 await helper.assets.setMetadata(1329 alice,1330 QTZ_ASSET_ID_ON_SHIDEN,1331 'Cross chain QTZ',1332 'xcQTZ',1333 Number(QTZ_DECIMALS),1334 );13351336 console.log('2. Register asset location on Shiden');1337 const assetLocation = {1338 V2: {1339 parents: 1,1340 interior: {1341 X1: {1342 Parachain: QUARTZ_CHAIN,1343 },1344 },1345 },1346 };13471348 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13491350 console.log('3. Set QTZ payment for XCM execution on Shiden');1351 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13521353 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1354 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1355 });1356 });13571358 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1359 const destination = {1360 V2: {1361 parents: 1,1362 interior: {1363 X1: {1364 Parachain: SHIDEN_CHAIN,1365 },1366 },1367 },1368 };13691370 const beneficiary = {1371 V2: {1372 parents: 0,1373 interior: {1374 X1: {1375 AccountId32: {1376 network: 'Any',1377 id: sender.addressRaw,1378 },1379 },1380 },1381 },1382 };13831384 const assets = {1385 V2: [1386 {1387 id: {1388 Concrete: {1389 parents: 0,1390 interior: 'Here',1391 },1392 },1393 fun: {1394 Fungible: qtzToShidenTransferred,1395 },1396 },1397 ],1398 };13991400 // Initial balance is 100 QTZ1401 const balanceBefore = await helper.balance.getSubstrate(sender.address);1402 console.log(`Initial balance is: ${balanceBefore}`);14031404 const feeAssetItem = 0;1405 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14061407 // Balance after reserve transfer is less than 901408 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1409 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1410 console.log(`Quartz's QTZ commission is: ${balanceBefore-balanceAfterQuartzToShidenXCM}`);1411 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14121413 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1414 await helper.wait.newBlocks(3);1415 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1416 const shidenBalance = await helper.balance.getSubstrate(sender.address);14171418 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1419 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred-xcQTZbalance!}`);14201421 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1422 // SHD balance does not changed:1423 expect(shidenBalance).to.eq(shidenInitialBalance);1424 });1425 });14261427 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1428 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1429 const destination = {1430 V2: {1431 parents: 1,1432 interior: {1433 X1: {1434 Parachain: QUARTZ_CHAIN,1435 },1436 },1437 },1438 };14391440 const beneficiary = {1441 V2: {1442 parents: 0,1443 interior: {1444 X1: {1445 AccountId32: {1446 network: 'Any',1447 id: sender.addressRaw,1448 },1449 },1450 },1451 },1452 };14531454 const assets = {1455 V2: [1456 {1457 id: {1458 Concrete: {1459 parents: 1,1460 interior: {1461 X1: {1462 Parachain: QUARTZ_CHAIN,1463 },1464 },1465 },1466 },1467 fun: {1468 Fungible: qtzFromShidenTransfered,1469 },1470 },1471 ],1472 };14731474 // Initial balance is 1 SDN1475 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1476 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1477 expect(balanceSDNbefore).to.eq(shidenInitialBalance);14781479 const feeAssetItem = 0;1480 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1481 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14821483 // Balance after reserve transfer is less than 1 SDN1484 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1485 const balanceSDN = await helper.balance.getSubstrate(sender.address);1486 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14871488 // Assert: xcQTZ balance correctly decreased1489 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1490 // Assert: SDN balance is 0.996...1491 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1492 });14931494 await helper.wait.newBlocks(3);1495 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1496 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1497 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1498 });14991500 itSub('Shiden can send only up to its balance', async ({helper}) => {1501 // set Shiden's sovereign account's balance1502 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1503 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1504 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15051506 const moreThanShidenHas = shidenBalance * 2n;15071508 let targetAccountBalance = 0n;1509 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15101511 const quartzMultilocation = {1512 V2: {1513 parents: 1,1514 interior: {1515 X1: {Parachain: QUARTZ_CHAIN},1516 },1517 },1518 };15191520 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1521 targetAccount.addressRaw,1522 {1523 Concrete: {1524 parents: 0,1525 interior: 'Here',1526 },1527 },1528 moreThanShidenHas,1529 );15301531 let maliciousXcmProgramSent: any;1532 const maxWaitBlocks = 3;15331534 // Try to trick Quartz1535 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1536 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15371538 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1539 });15401541 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1542 && event.outcome.isFailedToTransactAsset);15431544 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1545 expect(targetAccountBalance).to.be.equal(0n);15461547 // But Shiden still can send the correct amount1548 const validTransferAmount = shidenBalance / 2n;1549 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1550 targetAccount.addressRaw,1551 {1552 Concrete: {1553 parents: 0,1554 interior: 'Here',1555 },1556 },1557 validTransferAmount,1558 );15591560 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1561 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1562 });15631564 await helper.wait.newBlocks(maxWaitBlocks);15651566 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1567 expect(targetAccountBalance).to.be.equal(validTransferAmount);1568 });15691570 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1571 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1572 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15731574 const quartzMultilocation = {1575 V2: {1576 parents: 1,1577 interior: {1578 X1: {1579 Parachain: QUARTZ_CHAIN,1580 },1581 },1582 },1583 };15841585 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1586 targetAccount.addressRaw,1587 {1588 Concrete: {1589 parents: 1,1590 interior: {1591 X1: {1592 Parachain: QUARTZ_CHAIN,1593 },1594 },1595 },1596 },1597 testAmount,1598 );15991600 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1601 targetAccount.addressRaw,1602 {1603 Concrete: {1604 parents: 0,1605 interior: 'Here',1606 },1607 },1608 testAmount,1609 );16101611 let maliciousXcmProgramFullIdSent: any;1612 let maliciousXcmProgramHereIdSent: any;1613 const maxWaitBlocks = 3;16141615 // Try to trick Quartz using full QTZ identification1616 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1617 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16181619 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1620 });16211622 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1623 && event.outcome.isUntrustedReserveLocation);16241625 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1626 expect(accountBalance).to.be.equal(0n);16271628 // Try to trick Quartz using shortened QTZ identification1629 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1630 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16311632 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1633 });16341635 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1636 && event.outcome.isUntrustedReserveLocation);16371638 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1639 expect(accountBalance).to.be.equal(0n);1640 });1641});tests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -84,9 +84,8 @@
await usingRelayPlaygrounds(relayUrl, async (helper) => {
// Fund accounts on Statemint
- const relayXcmVersion = 2;
- await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT, relayXcmVersion);
- await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT, relayXcmVersion);
+ await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);
+ await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);
});
await usingStatemintPlaygrounds(statemintUrl, async (helper) => {
@@ -154,7 +153,7 @@
// (fee for USDT XCM are paid in relay tokens)
await usingRelayPlaygrounds(relayUrl, async (helper) => {
const destination = {
- V1: {
+ V2: {
parents: 0,
interior: {X1: {
Parachain: UNIQUE_CHAIN,
@@ -163,7 +162,7 @@
}};
const beneficiary = {
- V1: {
+ V2: {
parents: 0,
interior: {X1: {
AccountId32: {
@@ -175,7 +174,7 @@
};
const assets = {
- V1: [
+ V2: [
{
id: {
Concrete: {
@@ -200,7 +199,7 @@
itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {
await usingStatemintPlaygrounds(statemintUrl, async (helper) => {
const dest = {
- V1: {
+ V2: {
parents: 1,
interior: {X1: {
Parachain: UNIQUE_CHAIN,
@@ -209,7 +208,7 @@
}};
const beneficiary = {
- V1: {
+ V2: {
parents: 0,
interior: {X1: {
AccountId32: {
@@ -221,7 +220,7 @@
};
const assets = {
- V1: [
+ V2: [
{
id: {
Concrete: {
@@ -341,7 +340,7 @@
await usingRelayPlaygrounds(relayUrl, async (helper) => {
const destination = {
- V1: {
+ V2: {
parents: 0,
interior: {X1: {
Parachain: UNIQUE_CHAIN,
@@ -350,7 +349,7 @@
}};
const beneficiary = {
- V1: {
+ V2: {
parents: 0,
interior: {X1: {
AccountId32: {
@@ -362,7 +361,7 @@
};
const assets = {
- V1: [
+ V2: [
{
id: {
Concrete: {
@@ -468,8 +467,9 @@
// 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48
// 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)
const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;
+ const acalaEps = 8n * 10n ** 16n;
- const ACALA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedAcalaIncomeFee;
+ let acalaBackwardTransferAmount: bigint;
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
@@ -482,7 +482,7 @@
await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
const destination = {
- V1: {
+ V2: {
parents: 1,
interior: {
X1: {
@@ -571,6 +571,8 @@
const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;
const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;
+ acalaBackwardTransferAmount = unqIncomeTransfer;
+
const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;
console.log(
@@ -583,8 +585,11 @@
);
console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));
expect(acaFees == 0n).to.be.true;
+
+ const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;
+
expect(
- acaUnqFees == expectedAcalaIncomeFee,
+ bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,
'Acala took different income fee, check the Acala foreign asset config',
).to.be.true;
});
@@ -593,7 +598,7 @@
itSub('Should connect to Acala and send UNQ back', async ({helper}) => {
await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
const destination = {
- V1: {
+ V2: {
parents: 1,
interior: {
X2: [
@@ -613,7 +618,7 @@
ForeignAsset: 0,
};
- await helper.xTokens.transfer(randomAccount, id, ACALA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');
+ await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');
balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);
@@ -627,7 +632,7 @@
console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));
expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;
- expect(unqOutcomeTransfer == ACALA_BACKWARD_TRANSFER_AMOUNT).to.be.true;
+ expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;
});
await helper.wait.newBlocks(3);
@@ -638,7 +643,7 @@
console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));
- const unqFees = ACALA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;
+ const unqFees = acalaBackwardTransferAmount - actuallyDelivered;
console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));
expect(unqFees == 0n).to.be.true;
});
@@ -655,7 +660,7 @@
const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
const uniqueMultilocation = {
- V1: {
+ V2: {
parents: 1,
interior: {
X1: {Parachain: UNIQUE_CHAIN},
@@ -685,7 +690,7 @@
});
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash
- && event.outcome().isFailedToTransactAsset);
+ && event.outcome.isFailedToTransactAsset);
targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(targetAccountBalance).to.be.equal(0n);
@@ -718,7 +723,7 @@
const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
const uniqueMultilocation = {
- V1: {
+ V2: {
parents: 1,
interior: {
X1: {
@@ -766,7 +771,7 @@
});
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash
- && event.outcome().isUntrustedReserveLocation);
+ && event.outcome.isUntrustedReserveLocation);
let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -779,7 +784,7 @@
});
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash
- && event.outcome().isUntrustedReserveLocation);
+ && event.outcome.isUntrustedReserveLocation);
accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -845,7 +850,7 @@
};
uniqueCombinedMultilocationAcala = {
- V1: {
+ V2: {
parents: 1,
interior: {
X2: [uniqueParachainJunction, uniqueAccountJunction],
@@ -865,7 +870,7 @@
const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
- && event.outcome().isFailedToTransactAsset);
+ && event.outcome.isFailedToTransactAsset);
};
itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {
@@ -1178,7 +1183,7 @@
});
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash
- && event.outcome().isFailedToTransactAsset);
+ && event.outcome.isFailedToTransactAsset);
targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(targetAccountBalance).to.be.equal(0n);
@@ -1267,7 +1272,7 @@
});
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash
- && event.outcome().isUntrustedReserveLocation);
+ && event.outcome.isUntrustedReserveLocation);
let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -1284,7 +1289,7 @@
});
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash
- && event.outcome().isUntrustedReserveLocation);
+ && event.outcome.isUntrustedReserveLocation);
accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -1543,7 +1548,7 @@
});
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash
- && event.outcome().isFailedToTransactAsset);
+ && event.outcome.isFailedToTransactAsset);
targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(targetAccountBalance).to.be.equal(0n);
@@ -1624,7 +1629,7 @@
});
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash
- && event.outcome().isUntrustedReserveLocation);
+ && event.outcome.isUntrustedReserveLocation);
let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -1637,7 +1642,7 @@
});
await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash
- && event.outcome().isUntrustedReserveLocation);
+ && event.outcome.isUntrustedReserveLocation);
accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);