difftreelog
fix(unq xcm tests) restore state for the Acala
in: master
1 file changed
tests/src/xcm/xcmUnique.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, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';21import {hexToString, nToBigInt} from '@polkadot/util';2223const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);24const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);25const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);26const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);27const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);28const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);2930const STATEMINT_PALLET_INSTANCE = 50;3132const relayUrl = config.relayUrl;33const statemintUrl = config.statemintUrl;34const acalaUrl = config.acalaUrl;35const moonbeamUrl = config.moonbeamUrl;36const astarUrl = config.astarUrl;37const polkadexUrl = config.polkadexUrl;3839const RELAY_DECIMALS = 12;40const STATEMINT_DECIMALS = 12;41const ACALA_DECIMALS = 12;42const ASTAR_DECIMALS = 18n;43const UNQ_DECIMALS = 18n;4445const TRANSFER_AMOUNT = 2000000000000000000000000n;4647const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4849const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;5051const USDT_ASSET_ID = 100;52const USDT_ASSET_METADATA_DECIMALS = 18;53const USDT_ASSET_METADATA_NAME = 'USDT';54const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';55const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;56const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5758const SAFE_XCM_VERSION = 2;59const maxWaitBlocks = 6;6061const uniqueMultilocation = {62 V2: {63 parents: 1,64 interior: {65 X1: {66 Parachain: UNIQUE_CHAIN,67 },68 },69 },70};7172const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {73 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash74 && event.outcome.isFailedToTransactAsset);75};76const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {77 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash78 && event.outcome.isUntrustedReserveLocation);79};80describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {81 let alice: IKeyringPair;82 let bob: IKeyringPair;8384 let balanceStmnBefore: bigint;85 let balanceStmnAfter: bigint;8687 let balanceUniqueBefore: bigint;88 let balanceUniqueAfter: bigint;89 let balanceUniqueFinal: bigint;9091 let balanceBobBefore: bigint;92 let balanceBobAfter: bigint;93 let balanceBobFinal: bigint;9495 let balanceBobRelayTokenBefore: bigint;96 let balanceBobRelayTokenAfter: bigint;979899 before(async () => {100 await usingPlaygrounds(async (helper, privateKey) => {101 alice = await privateKey('//Alice');102 bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor103104 // Set the default version to wrap the first message to other chains.105 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);106 });107108 await usingRelayPlaygrounds(relayUrl, async (helper) => {109 // Fund accounts on Statemint110 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);111 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);112 });113114 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {115 const sovereignFundingAmount = 3_500_000_000n;116117 await helper.assets.create(118 alice,119 USDT_ASSET_ID,120 alice.address,121 USDT_ASSET_METADATA_MINIMAL_BALANCE,122 );123 await helper.assets.setMetadata(124 alice,125 USDT_ASSET_ID,126 USDT_ASSET_METADATA_NAME,127 USDT_ASSET_METADATA_DESCRIPTION,128 USDT_ASSET_METADATA_DECIMALS,129 );130 await helper.assets.mint(131 alice,132 USDT_ASSET_ID,133 alice.address,134 USDT_ASSET_AMOUNT,135 );136137 // funding parachain sovereing account on Statemint.138 // The sovereign account should be created before any action139 // (the assets pallet on Statemint check if the sovereign account exists)140 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);141 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);142 });143144145 await usingPlaygrounds(async (helper) => {146 const location = {147 V2: {148 parents: 1,149 interior: {X3: [150 {151 Parachain: STATEMINT_CHAIN,152 },153 {154 PalletInstance: STATEMINT_PALLET_INSTANCE,155 },156 {157 GeneralIndex: USDT_ASSET_ID,158 },159 ]},160 },161 };162163 const metadata =164 {165 name: USDT_ASSET_ID,166 symbol: USDT_ASSET_METADATA_NAME,167 decimals: USDT_ASSET_METADATA_DECIMALS,168 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,169 };170 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);171 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);172 });173174175 // Providing the relay currency to the unique sender account176 // (fee for USDT XCM are paid in relay tokens)177 await usingRelayPlaygrounds(relayUrl, async (helper) => {178 const destination = {179 V2: {180 parents: 0,181 interior: {X1: {182 Parachain: UNIQUE_CHAIN,183 },184 },185 }};186187 const beneficiary = {188 V2: {189 parents: 0,190 interior: {X1: {191 AccountId32: {192 network: 'Any',193 id: alice.addressRaw,194 },195 }},196 },197 };198199 const assets = {200 V2: [201 {202 id: {203 Concrete: {204 parents: 0,205 interior: 'Here',206 },207 },208 fun: {209 Fungible: TRANSFER_AMOUNT_RELAY,210 },211 },212 ],213 };214215 const feeAssetItem = 0;216217 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');218 });219220 });221222 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {223 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {224 const dest = {225 V2: {226 parents: 1,227 interior: {X1: {228 Parachain: UNIQUE_CHAIN,229 },230 },231 }};232233 const beneficiary = {234 V2: {235 parents: 0,236 interior: {X1: {237 AccountId32: {238 network: 'Any',239 id: alice.addressRaw,240 },241 }},242 },243 };244245 const assets = {246 V2: [247 {248 id: {249 Concrete: {250 parents: 0,251 interior: {252 X2: [253 {254 PalletInstance: STATEMINT_PALLET_INSTANCE,255 },256 {257 GeneralIndex: USDT_ASSET_ID,258 },259 ]},260 },261 },262 fun: {263 Fungible: TRANSFER_AMOUNT,264 },265 },266 ],267 };268269 const feeAssetItem = 0;270271 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);272 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');273274 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);275276 // common good parachain take commission in it native token277 console.log(278 '[Statemint -> Unique] transaction fees on Statemint: %s WND',279 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),280 );281 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;282283 });284285286 // ensure that asset has been delivered287 await helper.wait.newBlocks(3);288289 // expext collection id will be with id 1290 const free = await helper.ft.getBalance(1, {Substrate: alice.address});291292 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);293294 console.log(295 '[Statemint -> Unique] transaction fees on Unique: %s USDT',296 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),297 );298 console.log(299 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',300 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),301 );302 // commission has not paid in USDT token303 expect(free).to.be.equal(TRANSFER_AMOUNT);304 // ... and parachain native token305 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;306 });307308 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {309 const destination = {310 V2: {311 parents: 1,312 interior: {X2: [313 {314 Parachain: STATEMINT_CHAIN,315 },316 {317 AccountId32: {318 network: 'Any',319 id: alice.addressRaw,320 },321 },322 ]},323 },324 };325326 const relayFee = 400_000_000_000_000n;327 const currencies: [any, bigint][] = [328 [329 {330 ForeignAssetId: 0,331 },332 TRANSFER_AMOUNT,333 ],334 [335 {336 NativeAssetId: 'Parent',337 },338 relayFee,339 ],340 ];341342 const feeItem = 1;343344 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');345346 // the commission has been paid in parachain native token347 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);348 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));349 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;350351 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {352 await helper.wait.newBlocks(3);353354 // The USDT token never paid fees. Its amount not changed from begin value.355 // Also check that xcm transfer has been succeeded356 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;357 });358 });359360 itSub('Should connect and send Relay token to Unique', async ({helper}) => {361 balanceBobBefore = await helper.balance.getSubstrate(bob.address);362 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});363364 await usingRelayPlaygrounds(relayUrl, async (helper) => {365 const destination = {366 V2: {367 parents: 0,368 interior: {X1: {369 Parachain: UNIQUE_CHAIN,370 },371 },372 }};373374 const beneficiary = {375 V2: {376 parents: 0,377 interior: {X1: {378 AccountId32: {379 network: 'Any',380 id: bob.addressRaw,381 },382 }},383 },384 };385386 const assets = {387 V2: [388 {389 id: {390 Concrete: {391 parents: 0,392 interior: 'Here',393 },394 },395 fun: {396 Fungible: TRANSFER_AMOUNT_RELAY,397 },398 },399 ],400 };401402 const feeAssetItem = 0;403404 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');405 });406407 await helper.wait.newBlocks(3);408409 balanceBobAfter = await helper.balance.getSubstrate(bob.address);410 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});411412 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;413 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;414 console.log(415 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',416 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),417 );418 console.log(419 '[Relay (Westend) -> Unique] transaction fees: %s WND',420 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),421 );422 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);423 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;424 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;425 });426427 itSub('Should connect and send Relay token back', async ({helper}) => {428 let relayTokenBalanceBefore: bigint;429 let relayTokenBalanceAfter: bigint;430 await usingRelayPlaygrounds(relayUrl, async (helper) => {431 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);432 });433434 const destination = {435 V2: {436 parents: 1,437 interior: {438 X1:{439 AccountId32: {440 network: 'Any',441 id: bob.addressRaw,442 },443 },444 },445 },446 };447448 const currencies: any = [449 [450 {451 NativeAssetId: 'Parent',452 },453 TRANSFER_AMOUNT_RELAY,454 ],455 ];456457 const feeItem = 0;458459 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');460461 balanceBobFinal = await helper.balance.getSubstrate(bob.address);462 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));463464 await usingRelayPlaygrounds(relayUrl, async (helper) => {465 await helper.wait.newBlocks(10);466 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);467468 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;469 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));470 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;471 });472 });473});474475describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {476 let alice: IKeyringPair;477 let randomAccount: IKeyringPair;478479 let balanceUniqueTokenInit: bigint;480 let balanceUniqueTokenMiddle: bigint;481 let balanceUniqueTokenFinal: bigint;482 let balanceAcalaTokenInit: bigint;483 let balanceAcalaTokenMiddle: bigint;484 let balanceAcalaTokenFinal: bigint;485 let balanceUniqueForeignTokenInit: bigint;486 let balanceUniqueForeignTokenMiddle: bigint;487 let balanceUniqueForeignTokenFinal: bigint;488489 // computed by a test transfer from prod Unique to prod Acala.490 // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48491 // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)492 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;493 const acalaEps = 8n * 10n ** 16n;494495 let acalaBackwardTransferAmount: bigint;496497 before(async () => {498 await usingPlaygrounds(async (helper, privateKey) => {499 alice = await privateKey('//Alice');500 [randomAccount] = await helper.arrange.createAccounts([0n], alice);501502 // Set the default version to wrap the first message to other chains.503 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);504 });505506 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {507 const destination = {508 V2: {509 parents: 1,510 interior: {511 X1: {512 Parachain: UNIQUE_CHAIN,513 },514 },515 },516 };517518 const metadata = {519 name: 'Unique Network',520 symbol: 'UNQ',521 decimals: 18,522 minimalBalance: 1250000000000000000n,523 };524 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>525 hexToString(v.toJSON()['symbol'])) as string[];526527 if(!assets.includes('UNQ')) {528 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);529 } else {530 console.log('UNQ token already registered on Acala assetRegistry pallet');531 }532 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);533 });534535 await usingPlaygrounds(async (helper) => {536 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);537 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);538 });539 });540541 itSub('Should connect and send UNQ to Acala', async ({helper}) => {542543 const destination = {544 V2: {545 parents: 1,546 interior: {547 X1: {548 Parachain: ACALA_CHAIN,549 },550 },551 },552 };553554 const beneficiary = {555 V2: {556 parents: 0,557 interior: {558 X1: {559 AccountId32: {560 network: 'Any',561 id: randomAccount.addressRaw,562 },563 },564 },565 },566 };567568 const assets = {569 V2: [570 {571 id: {572 Concrete: {573 parents: 0,574 interior: 'Here',575 },576 },577 fun: {578 Fungible: TRANSFER_AMOUNT,579 },580 },581 ],582 };583584 const feeAssetItem = 0;585586 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');587 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);588589 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;590 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));591 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;592593 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {594 await helper.wait.newBlocks(3);595596 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});597 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);598599 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;600 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;601 acalaBackwardTransferAmount = unqIncomeTransfer;602603 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;604605 console.log(606 '[Unique -> Acala] transaction fees on Acala: %s ACA',607 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),608 );609 console.log(610 '[Unique -> Acala] transaction fees on Acala: %s UNQ',611 helper.util.bigIntToDecimals(acaUnqFees),612 );613 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));614 expect(acaFees == 0n).to.be.true;615616 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;617618 expect(619 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,620 'Acala took different income fee, check the Acala foreign asset config',621 ).to.be.true;622 });623 });624625 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {626 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {627 const destination = {628 V2: {629 parents: 1,630 interior: {631 X2: [632 {Parachain: UNIQUE_CHAIN},633 {634 AccountId32: {635 network: 'Any',636 id: randomAccount.addressRaw,637 },638 },639 ],640 },641 },642 };643644 const id = {645 ForeignAsset: 0,646 };647648 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');649 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);650 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);651652 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;653 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;654655 console.log(656 '[Acala -> Unique] transaction fees on Acala: %s ACA',657 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),658 );659 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));660661 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;662 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;663 });664665 await helper.wait.newBlocks(3);666667 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);668 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;669 expect(actuallyDelivered > 0).to.be.true;670671 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));672673 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;674 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));675 expect(unqFees == 0n).to.be.true;676 });677678 itSub('Acala can send only up to its balance', async ({helper}) => {679 // set Acala's sovereign account's balance680 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);681 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);682 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);683684 const moreThanAcalaHas = acalaBalance * 2n;685686 let targetAccountBalance = 0n;687 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);688689 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(690 targetAccount.addressRaw,691 {692 Concrete: {693 parents: 0,694 interior: 'Here',695 },696 },697 moreThanAcalaHas,698 );699700 let maliciousXcmProgramSent: any;701 const maxWaitBlocks = 3;702703 // Try to trick Unique704 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {705 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);706707 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);708 });709710 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash711 && event.outcome.isFailedToTransactAsset);712713 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);714 expect(targetAccountBalance).to.be.equal(0n);715716 // But Acala still can send the correct amount717 const validTransferAmount = acalaBalance / 2n;718 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(719 targetAccount.addressRaw,720 {721 Concrete: {722 parents: 0,723 interior: 'Here',724 },725 },726 validTransferAmount,727 );728729 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {730 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);731 });732733 await helper.wait.newBlocks(maxWaitBlocks);734735 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);736 expect(targetAccountBalance).to.be.equal(validTransferAmount);737 });738739 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {740 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);741 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);742743 const uniqueMultilocation = {744 V2: {745 parents: 1,746 interior: {747 X1: {748 Parachain: UNIQUE_CHAIN,749 },750 },751 },752 };753754 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(755 targetAccount.addressRaw,756 {757 Concrete: {758 parents: 1,759 interior: {760 X1: {761 Parachain: UNIQUE_CHAIN,762 },763 },764 },765 },766 testAmount,767 );768769 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(770 targetAccount.addressRaw,771 {772 Concrete: {773 parents: 0,774 interior: 'Here',775 },776 },777 testAmount,778 );779780 let maliciousXcmProgramFullIdSent: any;781 let maliciousXcmProgramHereIdSent: any;782 const maxWaitBlocks = 3;783784 // Try to trick Unique using full UNQ identification785 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {786 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);787788 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);789 });790791 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash792 && event.outcome.isUntrustedReserveLocation);793794 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);795 expect(accountBalance).to.be.equal(0n);796797 // Try to trick Unique using shortened UNQ identification798 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {799 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);800801 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);802 });803804 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash805 && event.outcome.isUntrustedReserveLocation);806807 accountBalance = await helper.balance.getSubstrate(targetAccount.address);808 expect(accountBalance).to.be.equal(0n);809 });810});811812describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {813 let alice: IKeyringPair;814 let randomAccount: IKeyringPair;815 let unqFees: bigint;816 let balanceUniqueTokenInit: bigint;817 let balanceUniqueTokenMiddle: bigint;818 let balanceUniqueTokenFinal: bigint;819 const maxWaitBlocks = 6;820821 const uniqueAssetId = {822 Concrete: {823 parents: 1,824 interior: {825 X1: {826 Parachain: UNIQUE_CHAIN,827 },828 },829 },830 };831832 before(async () => {833 await usingPlaygrounds(async (helper, privateKey) => {834 alice = await privateKey('//Alice');835 [randomAccount] = await helper.arrange.createAccounts([0n], alice);836837 // Set the default version to wrap the first message to other chains.838 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);839 });840841 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {842 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))843 .toJSON() as [])844 .map(nToBigInt).length != 0;845 /*846 Check whether the Unique token has been added847 to the whitelist, since an error will occur848 if it is added again. Needed for debugging849 when this test is run multiple times.850 */851 if(!isWhitelisted) {852 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);853 }854855 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);856 });857858 await usingPlaygrounds(async (helper) => {859 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);860 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);861 });862 });863864 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {865866 const destination = {867 V2: {868 parents: 1,869 interior: {870 X1: {871 Parachain: POLKADEX_CHAIN,872 },873 },874 },875 };876877 const beneficiary = {878 V2: {879 parents: 0,880 interior: {881 X1: {882 AccountId32: {883 network: 'Any',884 id: randomAccount.addressRaw,885 },886 },887 },888 },889 };890891 const assets = {892 V2: [893 {894 id: {895 Concrete: {896 parents: 0,897 interior: 'Here',898 },899 },900 fun: {901 Fungible: TRANSFER_AMOUNT,902 },903 },904 ],905 };906907 const feeAssetItem = 0;908909 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');910 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);911 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);912913 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;914 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));915 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;916917 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {918 /*919 Since only the parachain part of the Polkadex920 infrastructure is launched (without their921 solochain validators), processing incoming922 assets will lead to an error.923 This error indicates that the Polkadex chain924 received a message from the Unique network,925 since the hash is being checked to ensure926 it matches what was sent.927 */928 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);929 });930 });931932933 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {934935 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(936 randomAccount.addressRaw,937 {938 Concrete: {939 parents: 1,940 interior: {941 X1: {Parachain: UNIQUE_CHAIN},942 },943 },944 },945 TRANSFER_AMOUNT,946 );947948 let xcmProgramSent: any;949950951 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {952 await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);953954 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);955 });956957 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);958959 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);960961 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);962 });963964 itSub('Polkadex can send only up to its balance', async ({helper}) => {965 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);966 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);967 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);968 const moreThanPolkadexHas = 2n * polkadexBalance;969970 const targetAccount = helper.arrange.createEmptyAccount();971972 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(973 targetAccount.addressRaw,974 {975 Concrete: {976 parents: 0,977 interior: 'Here',978 },979 },980 moreThanPolkadexHas,981 );982983 let maliciousXcmProgramSent: any;984985986 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {987 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);988989 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);990 });991992 await expectFailedToTransact(helper, maliciousXcmProgramSent);993994 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);995 expect(targetAccountBalance).to.be.equal(0n);996 });997998 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {999 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1000 const targetAccount = helper.arrange.createEmptyAccount();10011002 const uniqueMultilocation = {1003 V2: {1004 parents: 1,1005 interior: {1006 X1: {1007 Parachain: UNIQUE_CHAIN,1008 },1009 },1010 },1011 };10121013 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1014 targetAccount.addressRaw,1015 {1016 Concrete: {1017 parents: 1,1018 interior: {1019 X1: {1020 Parachain: UNIQUE_CHAIN,1021 },1022 },1023 },1024 },1025 testAmount,1026 );10271028 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1029 targetAccount.addressRaw,1030 {1031 Concrete: {1032 parents: 0,1033 interior: 'Here',1034 },1035 },1036 testAmount,1037 );10381039 let maliciousXcmProgramFullIdSent: any;1040 let maliciousXcmProgramHereIdSent: any;1041 const maxWaitBlocks = 3;10421043 // Try to trick Unique using full UNQ identification1044 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1045 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10461047 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1048 });10491050 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10511052 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1053 expect(accountBalance).to.be.equal(0n);10541055 // Try to trick Unique using shortened UNQ identification1056 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1057 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10581059 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1060 });10611062 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10631064 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1065 expect(accountBalance).to.be.equal(0n);1066 });1067});10681069// These tests are relevant only when1070// the the corresponding foreign assets are not registered1071describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1072 let alice: IKeyringPair;1073 let alith: IKeyringPair;10741075 const testAmount = 100_000_000_000n;1076 let uniqueParachainJunction;1077 let uniqueAccountJunction;10781079 let uniqueParachainMultilocation: any;1080 let uniqueAccountMultilocation: any;1081 let uniqueCombinedMultilocation: any;1082 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210831084 let messageSent: any;10851086 const maxWaitBlocks = 3;10871088 before(async () => {1089 await usingPlaygrounds(async (helper, privateKey) => {1090 alice = await privateKey('//Alice');10911092 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1093 uniqueAccountJunction = {1094 AccountId32: {1095 network: 'Any',1096 id: alice.addressRaw,1097 },1098 };10991100 uniqueParachainMultilocation = {1101 V2: {1102 parents: 1,1103 interior: {1104 X1: uniqueParachainJunction,1105 },1106 },1107 };11081109 uniqueAccountMultilocation = {1110 V2: {1111 parents: 0,1112 interior: {1113 X1: uniqueAccountJunction,1114 },1115 },1116 };11171118 uniqueCombinedMultilocation = {1119 V2: {1120 parents: 1,1121 interior: {1122 X2: [uniqueParachainJunction, uniqueAccountJunction],1123 },1124 },1125 };11261127 uniqueCombinedMultilocationAcala = {1128 V2: {1129 parents: 1,1130 interior: {1131 X2: [uniqueParachainJunction, uniqueAccountJunction],1132 },1133 },1134 };11351136 // Set the default version to wrap the first message to other chains.1137 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1138 });11391140 // eslint-disable-next-line require-await1141 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1142 alith = helper.account.alithAccount();1143 });1144 });1145114611471148 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1149 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1150 const id = {1151 Token: 'ACA',1152 };1153 const destination = uniqueCombinedMultilocationAcala;1154 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11551156 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1157 });11581159 await expectFailedToTransact(helper, messageSent);1160 });11611162 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1163 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1164 const id = 'SelfReserve';1165 const destination = uniqueCombinedMultilocation;1166 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11671168 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1169 });11701171 await expectFailedToTransact(helper, messageSent);1172 });11731174 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1175 await usingAstarPlaygrounds(astarUrl, async (helper) => {1176 const destinationParachain = uniqueParachainMultilocation;1177 const beneficiary = uniqueAccountMultilocation;1178 const assets = {1179 V2: [{1180 id: {1181 Concrete: {1182 parents: 0,1183 interior: 'Here',1184 },1185 },1186 fun: {1187 Fungible: testAmount,1188 },1189 }],1190 };1191 const feeAssetItem = 0;11921193 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1194 destinationParachain,1195 beneficiary,1196 assets,1197 feeAssetItem,1198 ]);11991200 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1201 });12021203 await expectFailedToTransact(helper, messageSent);1204 });12051206 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12071208 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1209 helper.arrange.createEmptyAccount().addressRaw,1210 {1211 Concrete: {1212 parents: 1,1213 interior: {1214 X1: {1215 Parachain: POLKADEX_CHAIN,1216 },1217 },1218 },1219 },1220 testAmount,1221 );12221223 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1224 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1225 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1226 });12271228 await expectFailedToTransact(helper, messageSent);1229 });1230});12311232describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1233 // Unique constants1234 let alice: IKeyringPair;1235 let uniqueAssetLocation;12361237 let randomAccountUnique: IKeyringPair;1238 let randomAccountMoonbeam: IKeyringPair;12391240 // Moonbeam constants1241 let assetId: string;12421243 const uniqueAssetMetadata = {1244 name: 'xcUnique',1245 symbol: 'xcUNQ',1246 decimals: 18,1247 isFrozen: false,1248 minimalBalance: 1n,1249 };12501251 let balanceUniqueTokenInit: bigint;1252 let balanceUniqueTokenMiddle: bigint;1253 let balanceUniqueTokenFinal: bigint;1254 let balanceForeignUnqTokenInit: bigint;1255 let balanceForeignUnqTokenMiddle: bigint;1256 let balanceForeignUnqTokenFinal: bigint;1257 let balanceGlmrTokenInit: bigint;1258 let balanceGlmrTokenMiddle: bigint;1259 let balanceGlmrTokenFinal: bigint;12601261 before(async () => {1262 await usingPlaygrounds(async (helper, privateKey) => {1263 alice = await privateKey('//Alice');1264 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12651266 balanceForeignUnqTokenInit = 0n;12671268 // Set the default version to wrap the first message to other chains.1269 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1270 });12711272 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1273 const alithAccount = helper.account.alithAccount();1274 const baltatharAccount = helper.account.baltatharAccount();1275 const dorothyAccount = helper.account.dorothyAccount();12761277 randomAccountMoonbeam = helper.account.create();12781279 // >>> Sponsoring Dorothy >>>1280 console.log('Sponsoring Dorothy.......');1281 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1282 console.log('Sponsoring Dorothy.......DONE');1283 // <<< Sponsoring Dorothy <<<12841285 uniqueAssetLocation = {1286 XCM: {1287 parents: 1,1288 interior: {X1: {Parachain: UNIQUE_CHAIN}},1289 },1290 };1291 const existentialDeposit = 1n;1292 const isSufficient = true;1293 const unitsPerSecond = 1n;1294 const numAssetsWeightHint = 0;12951296 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1297 console.log('Unique asset is already registered on MoonBeam');1298 } else {1299 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1300 location: uniqueAssetLocation,1301 metadata: uniqueAssetMetadata,1302 existentialDeposit,1303 isSufficient,1304 unitsPerSecond,1305 numAssetsWeightHint,1306 });13071308 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);13091310 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);13111312 // >>> Acquire Unique AssetId Info on Moonbeam >>>1313 console.log('Acquire Unique AssetId Info on Moonbeam.......');13141315 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();13161317 console.log('UNQ asset ID is %s', assetId);1318 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1319 }1320 // >>> Acquire Unique AssetId Info on Moonbeam >>>13211322 // >>> Sponsoring random Account >>>1323 console.log('Sponsoring random Account.......');1324 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1325 console.log('Sponsoring random Account.......DONE');1326 // <<< Sponsoring random Account <<<13271328 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1329 });13301331 await usingPlaygrounds(async (helper) => {1332 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1333 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1334 });1335 });13361337 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1338 const currencyId = {1339 NativeAssetId: 'Here',1340 };1341 const dest = {1342 V2: {1343 parents: 1,1344 interior: {1345 X2: [1346 {Parachain: MOONBEAM_CHAIN},1347 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1348 ],1349 },1350 },1351 };1352 const amount = TRANSFER_AMOUNT;13531354 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13551356 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1357 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13581359 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1360 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1361 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13621363 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1364 await helper.wait.newBlocks(3);13651366 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13671368 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1369 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1370 expect(glmrFees == 0n).to.be.true;13711372 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13731374 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1375 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1376 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1377 });1378 });13791380 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1381 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1382 const asset = {1383 V2: {1384 id: {1385 Concrete: {1386 parents: 1,1387 interior: {1388 X1: {Parachain: UNIQUE_CHAIN},1389 },1390 },1391 },1392 fun: {1393 Fungible: TRANSFER_AMOUNT,1394 },1395 },1396 };1397 const destination = {1398 V2: {1399 parents: 1,1400 interior: {1401 X2: [1402 {Parachain: UNIQUE_CHAIN},1403 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1404 ],1405 },1406 },1407 };14081409 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');14101411 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);14121413 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1414 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1415 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;14161417 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);14181419 expect(unqRandomAccountAsset).to.be.null;14201421 balanceForeignUnqTokenFinal = 0n;14221423 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1424 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1425 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1426 });14271428 await helper.wait.newBlocks(3);14291430 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1431 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1432 expect(actuallyDelivered > 0).to.be.true;14331434 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));14351436 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1437 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1438 expect(unqFees == 0n).to.be.true;1439 });14401441 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1442 // set Moonbeam's sovereign account's balance1443 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1444 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1445 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14461447 const moreThanMoonbeamHas = moonbeamBalance * 2n;14481449 let targetAccountBalance = 0n;1450 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14511452 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1453 targetAccount.addressRaw,1454 {1455 Concrete: {1456 parents: 0,1457 interior: 'Here',1458 },1459 },1460 moreThanMoonbeamHas,1461 );14621463 let maliciousXcmProgramSent: any;1464 const maxWaitBlocks = 3;14651466 // Try to trick Unique1467 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1468 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14691470 // Needed to bypass the call filter.1471 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1472 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14731474 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1475 });14761477 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1478 && event.outcome.isFailedToTransactAsset);14791480 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1481 expect(targetAccountBalance).to.be.equal(0n);14821483 // But Moonbeam still can send the correct amount1484 const validTransferAmount = moonbeamBalance / 2n;1485 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1486 targetAccount.addressRaw,1487 {1488 Concrete: {1489 parents: 0,1490 interior: 'Here',1491 },1492 },1493 validTransferAmount,1494 );14951496 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1497 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);14981499 // Needed to bypass the call filter.1500 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1501 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1502 });15031504 await helper.wait.newBlocks(maxWaitBlocks);15051506 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1507 expect(targetAccountBalance).to.be.equal(validTransferAmount);1508 });15091510 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1511 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1512 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15131514 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1515 targetAccount.addressRaw,1516 {1517 Concrete: {1518 parents: 0,1519 interior: {1520 X1: {1521 Parachain: UNIQUE_CHAIN,1522 },1523 },1524 },1525 },1526 testAmount,1527 );15281529 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1530 targetAccount.addressRaw,1531 {1532 Concrete: {1533 parents: 0,1534 interior: 'Here',1535 },1536 },1537 testAmount,1538 );15391540 let maliciousXcmProgramFullIdSent: any;1541 let maliciousXcmProgramHereIdSent: any;1542 const maxWaitBlocks = 3;15431544 // Try to trick Unique using full UNQ identification1545 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1546 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15471548 // Needed to bypass the call filter.1549 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1550 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15511552 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1553 });15541555 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1556 && event.outcome.isUntrustedReserveLocation);15571558 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1559 expect(accountBalance).to.be.equal(0n);15601561 // Try to trick Unique using shortened UNQ identification1562 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1563 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15641565 // Needed to bypass the call filter.1566 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1567 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15681569 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1570 });15711572 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1573 && event.outcome.isUntrustedReserveLocation);15741575 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1576 expect(accountBalance).to.be.equal(0n);1577 });1578});15791580describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1581 let alice: IKeyringPair;1582 let randomAccount: IKeyringPair;15831584 const UNQ_ASSET_ID_ON_ASTAR = 1;1585 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;15861587 // Unique -> Astar1588 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1589 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1590 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1591 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens15921593 // Astar -> Unique1594 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1595 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ15961597 let balanceAfterUniqueToAstarXCM: bigint;15981599 before(async () => {1600 await usingPlaygrounds(async (helper, privateKey) => {1601 alice = await privateKey('//Alice');1602 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1603 console.log('randomAccount', randomAccount.address);16041605 // Set the default version to wrap the first message to other chains.1606 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1607 });16081609 await usingAstarPlaygrounds(astarUrl, async (helper) => {1610 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1611 console.log('1. Create foreign asset and metadata');1612 // TODO update metadata with values from production1613 await helper.assets.create(1614 alice,1615 UNQ_ASSET_ID_ON_ASTAR,1616 alice.address,1617 UNQ_MINIMAL_BALANCE_ON_ASTAR,1618 );16191620 await helper.assets.setMetadata(1621 alice,1622 UNQ_ASSET_ID_ON_ASTAR,1623 'Cross chain UNQ',1624 'xcUNQ',1625 Number(UNQ_DECIMALS),1626 );16271628 console.log('2. Register asset location on Astar');1629 const assetLocation = {1630 V2: {1631 parents: 1,1632 interior: {1633 X1: {1634 Parachain: UNIQUE_CHAIN,1635 },1636 },1637 },1638 };16391640 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16411642 console.log('3. Set UNQ payment for XCM execution on Astar');1643 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1644 }1645 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1646 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1647 });1648 });16491650 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1651 const destination = {1652 V2: {1653 parents: 1,1654 interior: {1655 X1: {1656 Parachain: ASTAR_CHAIN,1657 },1658 },1659 },1660 };16611662 const beneficiary = {1663 V2: {1664 parents: 0,1665 interior: {1666 X1: {1667 AccountId32: {1668 network: 'Any',1669 id: randomAccount.addressRaw,1670 },1671 },1672 },1673 },1674 };16751676 const assets = {1677 V2: [1678 {1679 id: {1680 Concrete: {1681 parents: 0,1682 interior: 'Here',1683 },1684 },1685 fun: {1686 Fungible: unqToAstarTransferred,1687 },1688 },1689 ],1690 };16911692 // Initial balance is 100 UNQ1693 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1694 console.log(`Initial balance is: ${balanceBefore}`);16951696 const feeAssetItem = 0;1697 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16981699 // Balance after reserve transfer is less than 901700 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1701 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1702 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1703 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17041705 await usingAstarPlaygrounds(astarUrl, async (helper) => {1706 await helper.wait.newBlocks(3);1707 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1708 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17091710 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1711 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);17121713 expect(xcUNQbalance).to.eq(unqToAstarArrived);1714 // Astar balance does not changed1715 expect(astarBalance).to.eq(astarInitialBalance);1716 });1717 });17181719 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1720 await usingAstarPlaygrounds(astarUrl, async (helper) => {1721 const destination = {1722 V2: {1723 parents: 1,1724 interior: {1725 X1: {1726 Parachain: UNIQUE_CHAIN,1727 },1728 },1729 },1730 };17311732 const beneficiary = {1733 V2: {1734 parents: 0,1735 interior: {1736 X1: {1737 AccountId32: {1738 network: 'Any',1739 id: randomAccount.addressRaw,1740 },1741 },1742 },1743 },1744 };17451746 const assets = {1747 V2: [1748 {1749 id: {1750 Concrete: {1751 parents: 1,1752 interior: {1753 X1: {1754 Parachain: UNIQUE_CHAIN,1755 },1756 },1757 },1758 },1759 fun: {1760 Fungible: unqFromAstarTransfered,1761 },1762 },1763 ],1764 };17651766 // Initial balance is 1 ASTR1767 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1768 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1769 expect(balanceASTRbefore).to.eq(astarInitialBalance);17701771 const feeAssetItem = 0;1772 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1773 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17741775 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1776 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1777 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17781779 // Assert: xcUNQ balance correctly decreased1780 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1781 // Assert: ASTR balance is 0.996...1782 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1783 });17841785 await helper.wait.newBlocks(3);1786 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1787 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1788 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1789 });17901791 itSub('Astar can send only up to its balance', async ({helper}) => {1792 // set Astar's sovereign account's balance1793 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1794 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1795 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17961797 const moreThanAstarHas = astarBalance * 2n;17981799 let targetAccountBalance = 0n;1800 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18011802 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1803 targetAccount.addressRaw,1804 {1805 Concrete: {1806 parents: 0,1807 interior: 'Here',1808 },1809 },1810 moreThanAstarHas,1811 );18121813 let maliciousXcmProgramSent: any;1814 const maxWaitBlocks = 3;18151816 // Try to trick Unique1817 await usingAstarPlaygrounds(astarUrl, async (helper) => {1818 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18191820 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1821 });18221823 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1824 && event.outcome.isFailedToTransactAsset);18251826 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1827 expect(targetAccountBalance).to.be.equal(0n);18281829 // But Astar still can send the correct amount1830 const validTransferAmount = astarBalance / 2n;1831 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1832 targetAccount.addressRaw,1833 {1834 Concrete: {1835 parents: 0,1836 interior: 'Here',1837 },1838 },1839 validTransferAmount,1840 );18411842 await usingAstarPlaygrounds(astarUrl, async (helper) => {1843 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1844 });18451846 await helper.wait.newBlocks(maxWaitBlocks);18471848 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1849 expect(targetAccountBalance).to.be.equal(validTransferAmount);1850 });18511852 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1853 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1854 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18551856 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1857 targetAccount.addressRaw,1858 {1859 Concrete: {1860 parents: 1,1861 interior: {1862 X1: {1863 Parachain: UNIQUE_CHAIN,1864 },1865 },1866 },1867 },1868 testAmount,1869 );18701871 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1872 targetAccount.addressRaw,1873 {1874 Concrete: {1875 parents: 0,1876 interior: 'Here',1877 },1878 },1879 testAmount,1880 );18811882 let maliciousXcmProgramFullIdSent: any;1883 let maliciousXcmProgramHereIdSent: any;1884 const maxWaitBlocks = 3;18851886 // Try to trick Unique using full UNQ identification1887 await usingAstarPlaygrounds(astarUrl, async (helper) => {1888 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);18891890 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1891 });18921893 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1894 && event.outcome.isUntrustedReserveLocation);18951896 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1897 expect(accountBalance).to.be.equal(0n);18981899 // Try to trick Unique using shortened UNQ identification1900 await usingAstarPlaygrounds(astarUrl, async (helper) => {1901 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19021903 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1904 });19051906 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1907 && event.outcome.isUntrustedReserveLocation);19081909 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1910 expect(accountBalance).to.be.equal(0n);1911 });1912});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, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';21import {hexToString, nToBigInt} from '@polkadot/util';2223const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);24const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);25const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);26const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);27const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);28const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);2930const STATEMINT_PALLET_INSTANCE = 50;3132const relayUrl = config.relayUrl;33const statemintUrl = config.statemintUrl;34const acalaUrl = config.acalaUrl;35const moonbeamUrl = config.moonbeamUrl;36const astarUrl = config.astarUrl;37const polkadexUrl = config.polkadexUrl;3839const RELAY_DECIMALS = 12;40const STATEMINT_DECIMALS = 12;41const ACALA_DECIMALS = 12;42const ASTAR_DECIMALS = 18n;43const UNQ_DECIMALS = 18n;4445const TRANSFER_AMOUNT = 2000000000000000000000000n;4647const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4849const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;5051const USDT_ASSET_ID = 100;52const USDT_ASSET_METADATA_DECIMALS = 18;53const USDT_ASSET_METADATA_NAME = 'USDT';54const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';55const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;56const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5758const SAFE_XCM_VERSION = 2;59const maxWaitBlocks = 6;6061const uniqueMultilocation = {62 V2: {63 parents: 1,64 interior: {65 X1: {66 Parachain: UNIQUE_CHAIN,67 },68 },69 },70};7172const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {73 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash74 && event.outcome.isFailedToTransactAsset);75};76const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {77 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash78 && event.outcome.isUntrustedReserveLocation);79};80describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {81 let alice: IKeyringPair;82 let bob: IKeyringPair;8384 let balanceStmnBefore: bigint;85 let balanceStmnAfter: bigint;8687 let balanceUniqueBefore: bigint;88 let balanceUniqueAfter: bigint;89 let balanceUniqueFinal: bigint;9091 let balanceBobBefore: bigint;92 let balanceBobAfter: bigint;93 let balanceBobFinal: bigint;9495 let balanceBobRelayTokenBefore: bigint;96 let balanceBobRelayTokenAfter: bigint;979899 before(async () => {100 await usingPlaygrounds(async (helper, privateKey) => {101 alice = await privateKey('//Alice');102 bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor103104 // Set the default version to wrap the first message to other chains.105 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);106 });107108 await usingRelayPlaygrounds(relayUrl, async (helper) => {109 // Fund accounts on Statemint110 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);111 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);112 });113114 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {115 const sovereignFundingAmount = 3_500_000_000n;116117 await helper.assets.create(118 alice,119 USDT_ASSET_ID,120 alice.address,121 USDT_ASSET_METADATA_MINIMAL_BALANCE,122 );123 await helper.assets.setMetadata(124 alice,125 USDT_ASSET_ID,126 USDT_ASSET_METADATA_NAME,127 USDT_ASSET_METADATA_DESCRIPTION,128 USDT_ASSET_METADATA_DECIMALS,129 );130 await helper.assets.mint(131 alice,132 USDT_ASSET_ID,133 alice.address,134 USDT_ASSET_AMOUNT,135 );136137 // funding parachain sovereing account on Statemint.138 // The sovereign account should be created before any action139 // (the assets pallet on Statemint check if the sovereign account exists)140 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);141 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);142 });143144145 await usingPlaygrounds(async (helper) => {146 const location = {147 V2: {148 parents: 1,149 interior: {X3: [150 {151 Parachain: STATEMINT_CHAIN,152 },153 {154 PalletInstance: STATEMINT_PALLET_INSTANCE,155 },156 {157 GeneralIndex: USDT_ASSET_ID,158 },159 ]},160 },161 };162163 const metadata =164 {165 name: USDT_ASSET_ID,166 symbol: USDT_ASSET_METADATA_NAME,167 decimals: USDT_ASSET_METADATA_DECIMALS,168 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,169 };170 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);171 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);172 });173174175 // Providing the relay currency to the unique sender account176 // (fee for USDT XCM are paid in relay tokens)177 await usingRelayPlaygrounds(relayUrl, async (helper) => {178 const destination = {179 V2: {180 parents: 0,181 interior: {X1: {182 Parachain: UNIQUE_CHAIN,183 },184 },185 }};186187 const beneficiary = {188 V2: {189 parents: 0,190 interior: {X1: {191 AccountId32: {192 network: 'Any',193 id: alice.addressRaw,194 },195 }},196 },197 };198199 const assets = {200 V2: [201 {202 id: {203 Concrete: {204 parents: 0,205 interior: 'Here',206 },207 },208 fun: {209 Fungible: TRANSFER_AMOUNT_RELAY,210 },211 },212 ],213 };214215 const feeAssetItem = 0;216217 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');218 });219220 });221222 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {223 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {224 const dest = {225 V2: {226 parents: 1,227 interior: {X1: {228 Parachain: UNIQUE_CHAIN,229 },230 },231 }};232233 const beneficiary = {234 V2: {235 parents: 0,236 interior: {X1: {237 AccountId32: {238 network: 'Any',239 id: alice.addressRaw,240 },241 }},242 },243 };244245 const assets = {246 V2: [247 {248 id: {249 Concrete: {250 parents: 0,251 interior: {252 X2: [253 {254 PalletInstance: STATEMINT_PALLET_INSTANCE,255 },256 {257 GeneralIndex: USDT_ASSET_ID,258 },259 ]},260 },261 },262 fun: {263 Fungible: TRANSFER_AMOUNT,264 },265 },266 ],267 };268269 const feeAssetItem = 0;270271 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);272 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');273274 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);275276 // common good parachain take commission in it native token277 console.log(278 '[Statemint -> Unique] transaction fees on Statemint: %s WND',279 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),280 );281 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;282283 });284285286 // ensure that asset has been delivered287 await helper.wait.newBlocks(3);288289 // expext collection id will be with id 1290 const free = await helper.ft.getBalance(1, {Substrate: alice.address});291292 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);293294 console.log(295 '[Statemint -> Unique] transaction fees on Unique: %s USDT',296 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),297 );298 console.log(299 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',300 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),301 );302 // commission has not paid in USDT token303 expect(free).to.be.equal(TRANSFER_AMOUNT);304 // ... and parachain native token305 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;306 });307308 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {309 const destination = {310 V2: {311 parents: 1,312 interior: {X2: [313 {314 Parachain: STATEMINT_CHAIN,315 },316 {317 AccountId32: {318 network: 'Any',319 id: alice.addressRaw,320 },321 },322 ]},323 },324 };325326 const relayFee = 400_000_000_000_000n;327 const currencies: [any, bigint][] = [328 [329 {330 ForeignAssetId: 0,331 },332 TRANSFER_AMOUNT,333 ],334 [335 {336 NativeAssetId: 'Parent',337 },338 relayFee,339 ],340 ];341342 const feeItem = 1;343344 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');345346 // the commission has been paid in parachain native token347 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);348 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));349 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;350351 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {352 await helper.wait.newBlocks(3);353354 // The USDT token never paid fees. Its amount not changed from begin value.355 // Also check that xcm transfer has been succeeded356 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;357 });358 });359360 itSub('Should connect and send Relay token to Unique', async ({helper}) => {361 balanceBobBefore = await helper.balance.getSubstrate(bob.address);362 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});363364 await usingRelayPlaygrounds(relayUrl, async (helper) => {365 const destination = {366 V2: {367 parents: 0,368 interior: {X1: {369 Parachain: UNIQUE_CHAIN,370 },371 },372 }};373374 const beneficiary = {375 V2: {376 parents: 0,377 interior: {X1: {378 AccountId32: {379 network: 'Any',380 id: bob.addressRaw,381 },382 }},383 },384 };385386 const assets = {387 V2: [388 {389 id: {390 Concrete: {391 parents: 0,392 interior: 'Here',393 },394 },395 fun: {396 Fungible: TRANSFER_AMOUNT_RELAY,397 },398 },399 ],400 };401402 const feeAssetItem = 0;403404 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');405 });406407 await helper.wait.newBlocks(3);408409 balanceBobAfter = await helper.balance.getSubstrate(bob.address);410 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});411412 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;413 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;414 console.log(415 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',416 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),417 );418 console.log(419 '[Relay (Westend) -> Unique] transaction fees: %s WND',420 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),421 );422 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);423 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;424 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;425 });426427 itSub('Should connect and send Relay token back', async ({helper}) => {428 let relayTokenBalanceBefore: bigint;429 let relayTokenBalanceAfter: bigint;430 await usingRelayPlaygrounds(relayUrl, async (helper) => {431 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);432 });433434 const destination = {435 V2: {436 parents: 1,437 interior: {438 X1:{439 AccountId32: {440 network: 'Any',441 id: bob.addressRaw,442 },443 },444 },445 },446 };447448 const currencies: any = [449 [450 {451 NativeAssetId: 'Parent',452 },453 TRANSFER_AMOUNT_RELAY,454 ],455 ];456457 const feeItem = 0;458459 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');460461 balanceBobFinal = await helper.balance.getSubstrate(bob.address);462 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));463464 await usingRelayPlaygrounds(relayUrl, async (helper) => {465 await helper.wait.newBlocks(10);466 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);467468 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;469 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));470 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;471 });472 });473});474475describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {476 let alice: IKeyringPair;477 let randomAccount: IKeyringPair;478479 let balanceUniqueTokenInit: bigint;480 let balanceUniqueTokenMiddle: bigint;481 let balanceUniqueTokenFinal: bigint;482 let balanceAcalaTokenInit: bigint;483 let balanceAcalaTokenMiddle: bigint;484 let balanceAcalaTokenFinal: bigint;485 let balanceUniqueForeignTokenInit: bigint;486 let balanceUniqueForeignTokenMiddle: bigint;487 let balanceUniqueForeignTokenFinal: bigint;488489 // computed by a test transfer from prod Unique to prod Acala.490 // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48491 // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)492 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;493 const acalaEps = 8n * 10n ** 16n;494495 let acalaBackwardTransferAmount: bigint;496497 before(async () => {498 await usingPlaygrounds(async (helper, privateKey) => {499 alice = await privateKey('//Alice');500 [randomAccount] = await helper.arrange.createAccounts([0n], alice);501502 // Set the default version to wrap the first message to other chains.503 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);504 });505506 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {507 const destination = {508 V2: {509 parents: 1,510 interior: {511 X1: {512 Parachain: UNIQUE_CHAIN,513 },514 },515 },516 };517518 const metadata = {519 name: 'Unique Network',520 symbol: 'UNQ',521 decimals: 18,522 minimalBalance: 1250000000000000000n,523 };524 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>525 hexToString(v.toJSON()['symbol'])) as string[];526527 if(!assets.includes('UNQ')) {528 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);529 } else {530 console.log('UNQ token already registered on Acala assetRegistry pallet');531 }532 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);533 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);534 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});535 });536537 await usingPlaygrounds(async (helper) => {538 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);539 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);540 });541 });542543 itSub('Should connect and send UNQ to Acala', async ({helper}) => {544545 const destination = {546 V2: {547 parents: 1,548 interior: {549 X1: {550 Parachain: ACALA_CHAIN,551 },552 },553 },554 };555556 const beneficiary = {557 V2: {558 parents: 0,559 interior: {560 X1: {561 AccountId32: {562 network: 'Any',563 id: randomAccount.addressRaw,564 },565 },566 },567 },568 };569570 const assets = {571 V2: [572 {573 id: {574 Concrete: {575 parents: 0,576 interior: 'Here',577 },578 },579 fun: {580 Fungible: TRANSFER_AMOUNT,581 },582 },583 ],584 };585586 const feeAssetItem = 0;587588 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');589 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);590591 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;592 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));593 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;594595 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {596 await helper.wait.newBlocks(3);597598 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});599 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);600601 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;602 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;603 acalaBackwardTransferAmount = unqIncomeTransfer;604605 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;606607 console.log(608 '[Unique -> Acala] transaction fees on Acala: %s ACA',609 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),610 );611 console.log(612 '[Unique -> Acala] transaction fees on Acala: %s UNQ',613 helper.util.bigIntToDecimals(acaUnqFees),614 );615 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));616 expect(acaFees == 0n).to.be.true;617618 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;619620 expect(621 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,622 'Acala took different income fee, check the Acala foreign asset config',623 ).to.be.true;624 });625 });626627 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {628 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {629 const destination = {630 V2: {631 parents: 1,632 interior: {633 X2: [634 {Parachain: UNIQUE_CHAIN},635 {636 AccountId32: {637 network: 'Any',638 id: randomAccount.addressRaw,639 },640 },641 ],642 },643 },644 };645646 const id = {647 ForeignAsset: 0,648 };649650 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');651 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);652 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);653654 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;655 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;656657 console.log(658 '[Acala -> Unique] transaction fees on Acala: %s ACA',659 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),660 );661 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));662663 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;664 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;665 });666667 await helper.wait.newBlocks(3);668669 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);670 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;671 expect(actuallyDelivered > 0).to.be.true;672673 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));674675 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;676 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));677 expect(unqFees == 0n).to.be.true;678 });679680 itSub('Acala can send only up to its balance', async ({helper}) => {681 // set Acala's sovereign account's balance682 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);683 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);684 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);685686 const moreThanAcalaHas = acalaBalance * 2n;687688 let targetAccountBalance = 0n;689 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);690691 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(692 targetAccount.addressRaw,693 {694 Concrete: {695 parents: 0,696 interior: 'Here',697 },698 },699 moreThanAcalaHas,700 );701702 let maliciousXcmProgramSent: any;703 const maxWaitBlocks = 3;704705 // Try to trick Unique706 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {707 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);708709 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);710 });711712 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash713 && event.outcome.isFailedToTransactAsset);714715 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);716 expect(targetAccountBalance).to.be.equal(0n);717718 // But Acala still can send the correct amount719 const validTransferAmount = acalaBalance / 2n;720 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(721 targetAccount.addressRaw,722 {723 Concrete: {724 parents: 0,725 interior: 'Here',726 },727 },728 validTransferAmount,729 );730731 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {732 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);733 });734735 await helper.wait.newBlocks(maxWaitBlocks);736737 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);738 expect(targetAccountBalance).to.be.equal(validTransferAmount);739 });740741 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {742 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);743 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);744745 const uniqueMultilocation = {746 V2: {747 parents: 1,748 interior: {749 X1: {750 Parachain: UNIQUE_CHAIN,751 },752 },753 },754 };755756 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(757 targetAccount.addressRaw,758 {759 Concrete: {760 parents: 1,761 interior: {762 X1: {763 Parachain: UNIQUE_CHAIN,764 },765 },766 },767 },768 testAmount,769 );770771 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(772 targetAccount.addressRaw,773 {774 Concrete: {775 parents: 0,776 interior: 'Here',777 },778 },779 testAmount,780 );781782 let maliciousXcmProgramFullIdSent: any;783 let maliciousXcmProgramHereIdSent: any;784 const maxWaitBlocks = 3;785786 // Try to trick Unique using full UNQ identification787 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {788 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);789790 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);791 });792793 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash794 && event.outcome.isUntrustedReserveLocation);795796 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);797 expect(accountBalance).to.be.equal(0n);798799 // Try to trick Unique using shortened UNQ identification800 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {801 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);802803 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);804 });805806 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash807 && event.outcome.isUntrustedReserveLocation);808809 accountBalance = await helper.balance.getSubstrate(targetAccount.address);810 expect(accountBalance).to.be.equal(0n);811 });812});813814describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {815 let alice: IKeyringPair;816 let randomAccount: IKeyringPair;817 let unqFees: bigint;818 let balanceUniqueTokenInit: bigint;819 let balanceUniqueTokenMiddle: bigint;820 let balanceUniqueTokenFinal: bigint;821 const maxWaitBlocks = 6;822823 const uniqueAssetId = {824 Concrete: {825 parents: 1,826 interior: {827 X1: {828 Parachain: UNIQUE_CHAIN,829 },830 },831 },832 };833834 before(async () => {835 await usingPlaygrounds(async (helper, privateKey) => {836 alice = await privateKey('//Alice');837 [randomAccount] = await helper.arrange.createAccounts([0n], alice);838839 // Set the default version to wrap the first message to other chains.840 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);841 });842843 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {844 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))845 .toJSON() as [])846 .map(nToBigInt).length != 0;847 /*848 Check whether the Unique token has been added849 to the whitelist, since an error will occur850 if it is added again. Needed for debugging851 when this test is run multiple times.852 */853 if(!isWhitelisted) {854 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);855 }856857 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);858 });859860 await usingPlaygrounds(async (helper) => {861 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);862 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);863 });864 });865866 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {867868 const destination = {869 V2: {870 parents: 1,871 interior: {872 X1: {873 Parachain: POLKADEX_CHAIN,874 },875 },876 },877 };878879 const beneficiary = {880 V2: {881 parents: 0,882 interior: {883 X1: {884 AccountId32: {885 network: 'Any',886 id: randomAccount.addressRaw,887 },888 },889 },890 },891 };892893 const assets = {894 V2: [895 {896 id: {897 Concrete: {898 parents: 0,899 interior: 'Here',900 },901 },902 fun: {903 Fungible: TRANSFER_AMOUNT,904 },905 },906 ],907 };908909 const feeAssetItem = 0;910911 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');912 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);913 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);914915 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;916 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));917 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;918919 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {920 /*921 Since only the parachain part of the Polkadex922 infrastructure is launched (without their923 solochain validators), processing incoming924 assets will lead to an error.925 This error indicates that the Polkadex chain926 received a message from the Unique network,927 since the hash is being checked to ensure928 it matches what was sent.929 */930 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);931 });932 });933934935 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {936937 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(938 randomAccount.addressRaw,939 {940 Concrete: {941 parents: 1,942 interior: {943 X1: {Parachain: UNIQUE_CHAIN},944 },945 },946 },947 TRANSFER_AMOUNT,948 );949950 let xcmProgramSent: any;951952953 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {954 await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);955956 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);957 });958959 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);960961 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);962963 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);964 });965966 itSub('Polkadex can send only up to its balance', async ({helper}) => {967 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);968 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);969 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);970 const moreThanPolkadexHas = 2n * polkadexBalance;971972 const targetAccount = helper.arrange.createEmptyAccount();973974 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(975 targetAccount.addressRaw,976 {977 Concrete: {978 parents: 0,979 interior: 'Here',980 },981 },982 moreThanPolkadexHas,983 );984985 let maliciousXcmProgramSent: any;986987988 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {989 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);990991 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);992 });993994 await expectFailedToTransact(helper, maliciousXcmProgramSent);995996 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);997 expect(targetAccountBalance).to.be.equal(0n);998 });9991000 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {1001 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1002 const targetAccount = helper.arrange.createEmptyAccount();10031004 const uniqueMultilocation = {1005 V2: {1006 parents: 1,1007 interior: {1008 X1: {1009 Parachain: UNIQUE_CHAIN,1010 },1011 },1012 },1013 };10141015 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1016 targetAccount.addressRaw,1017 {1018 Concrete: {1019 parents: 1,1020 interior: {1021 X1: {1022 Parachain: UNIQUE_CHAIN,1023 },1024 },1025 },1026 },1027 testAmount,1028 );10291030 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1031 targetAccount.addressRaw,1032 {1033 Concrete: {1034 parents: 0,1035 interior: 'Here',1036 },1037 },1038 testAmount,1039 );10401041 let maliciousXcmProgramFullIdSent: any;1042 let maliciousXcmProgramHereIdSent: any;1043 const maxWaitBlocks = 3;10441045 // Try to trick Unique using full UNQ identification1046 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1047 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10481049 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1050 });10511052 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10531054 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1055 expect(accountBalance).to.be.equal(0n);10561057 // Try to trick Unique using shortened UNQ identification1058 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1059 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10601061 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1062 });10631064 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10651066 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1067 expect(accountBalance).to.be.equal(0n);1068 });1069});10701071// These tests are relevant only when1072// the the corresponding foreign assets are not registered1073describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1074 let alice: IKeyringPair;1075 let alith: IKeyringPair;10761077 const testAmount = 100_000_000_000n;1078 let uniqueParachainJunction;1079 let uniqueAccountJunction;10801081 let uniqueParachainMultilocation: any;1082 let uniqueAccountMultilocation: any;1083 let uniqueCombinedMultilocation: any;1084 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210851086 let messageSent: any;10871088 const maxWaitBlocks = 3;10891090 before(async () => {1091 await usingPlaygrounds(async (helper, privateKey) => {1092 alice = await privateKey('//Alice');10931094 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1095 uniqueAccountJunction = {1096 AccountId32: {1097 network: 'Any',1098 id: alice.addressRaw,1099 },1100 };11011102 uniqueParachainMultilocation = {1103 V2: {1104 parents: 1,1105 interior: {1106 X1: uniqueParachainJunction,1107 },1108 },1109 };11101111 uniqueAccountMultilocation = {1112 V2: {1113 parents: 0,1114 interior: {1115 X1: uniqueAccountJunction,1116 },1117 },1118 };11191120 uniqueCombinedMultilocation = {1121 V2: {1122 parents: 1,1123 interior: {1124 X2: [uniqueParachainJunction, uniqueAccountJunction],1125 },1126 },1127 };11281129 uniqueCombinedMultilocationAcala = {1130 V2: {1131 parents: 1,1132 interior: {1133 X2: [uniqueParachainJunction, uniqueAccountJunction],1134 },1135 },1136 };11371138 // Set the default version to wrap the first message to other chains.1139 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1140 });11411142 // eslint-disable-next-line require-await1143 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1144 alith = helper.account.alithAccount();1145 });1146 });1147114811491150 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1151 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1152 const id = {1153 Token: 'ACA',1154 };1155 const destination = uniqueCombinedMultilocationAcala;1156 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11571158 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1159 });11601161 await expectFailedToTransact(helper, messageSent);1162 });11631164 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1165 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1166 const id = 'SelfReserve';1167 const destination = uniqueCombinedMultilocation;1168 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11691170 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1171 });11721173 await expectFailedToTransact(helper, messageSent);1174 });11751176 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1177 await usingAstarPlaygrounds(astarUrl, async (helper) => {1178 const destinationParachain = uniqueParachainMultilocation;1179 const beneficiary = uniqueAccountMultilocation;1180 const assets = {1181 V2: [{1182 id: {1183 Concrete: {1184 parents: 0,1185 interior: 'Here',1186 },1187 },1188 fun: {1189 Fungible: testAmount,1190 },1191 }],1192 };1193 const feeAssetItem = 0;11941195 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1196 destinationParachain,1197 beneficiary,1198 assets,1199 feeAssetItem,1200 ]);12011202 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1203 });12041205 await expectFailedToTransact(helper, messageSent);1206 });12071208 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12091210 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1211 helper.arrange.createEmptyAccount().addressRaw,1212 {1213 Concrete: {1214 parents: 1,1215 interior: {1216 X1: {1217 Parachain: POLKADEX_CHAIN,1218 },1219 },1220 },1221 },1222 testAmount,1223 );12241225 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1226 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1227 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1228 });12291230 await expectFailedToTransact(helper, messageSent);1231 });1232});12331234describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1235 // Unique constants1236 let alice: IKeyringPair;1237 let uniqueAssetLocation;12381239 let randomAccountUnique: IKeyringPair;1240 let randomAccountMoonbeam: IKeyringPair;12411242 // Moonbeam constants1243 let assetId: string;12441245 const uniqueAssetMetadata = {1246 name: 'xcUnique',1247 symbol: 'xcUNQ',1248 decimals: 18,1249 isFrozen: false,1250 minimalBalance: 1n,1251 };12521253 let balanceUniqueTokenInit: bigint;1254 let balanceUniqueTokenMiddle: bigint;1255 let balanceUniqueTokenFinal: bigint;1256 let balanceForeignUnqTokenInit: bigint;1257 let balanceForeignUnqTokenMiddle: bigint;1258 let balanceForeignUnqTokenFinal: bigint;1259 let balanceGlmrTokenInit: bigint;1260 let balanceGlmrTokenMiddle: bigint;1261 let balanceGlmrTokenFinal: bigint;12621263 before(async () => {1264 await usingPlaygrounds(async (helper, privateKey) => {1265 alice = await privateKey('//Alice');1266 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12671268 balanceForeignUnqTokenInit = 0n;12691270 // Set the default version to wrap the first message to other chains.1271 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1272 });12731274 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1275 const alithAccount = helper.account.alithAccount();1276 const baltatharAccount = helper.account.baltatharAccount();1277 const dorothyAccount = helper.account.dorothyAccount();12781279 randomAccountMoonbeam = helper.account.create();12801281 // >>> Sponsoring Dorothy >>>1282 console.log('Sponsoring Dorothy.......');1283 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1284 console.log('Sponsoring Dorothy.......DONE');1285 // <<< Sponsoring Dorothy <<<12861287 uniqueAssetLocation = {1288 XCM: {1289 parents: 1,1290 interior: {X1: {Parachain: UNIQUE_CHAIN}},1291 },1292 };1293 const existentialDeposit = 1n;1294 const isSufficient = true;1295 const unitsPerSecond = 1n;1296 const numAssetsWeightHint = 0;12971298 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1299 console.log('Unique asset is already registered on MoonBeam');1300 } else {1301 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1302 location: uniqueAssetLocation,1303 metadata: uniqueAssetMetadata,1304 existentialDeposit,1305 isSufficient,1306 unitsPerSecond,1307 numAssetsWeightHint,1308 });13091310 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);13111312 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);13131314 // >>> Acquire Unique AssetId Info on Moonbeam >>>1315 console.log('Acquire Unique AssetId Info on Moonbeam.......');13161317 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();13181319 console.log('UNQ asset ID is %s', assetId);1320 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1321 }1322 // >>> Acquire Unique AssetId Info on Moonbeam >>>13231324 // >>> Sponsoring random Account >>>1325 console.log('Sponsoring random Account.......');1326 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1327 console.log('Sponsoring random Account.......DONE');1328 // <<< Sponsoring random Account <<<13291330 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1331 });13321333 await usingPlaygrounds(async (helper) => {1334 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1335 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1336 });1337 });13381339 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1340 const currencyId = {1341 NativeAssetId: 'Here',1342 };1343 const dest = {1344 V2: {1345 parents: 1,1346 interior: {1347 X2: [1348 {Parachain: MOONBEAM_CHAIN},1349 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1350 ],1351 },1352 },1353 };1354 const amount = TRANSFER_AMOUNT;13551356 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13571358 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1359 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13601361 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1362 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1363 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13641365 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1366 await helper.wait.newBlocks(3);1367 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13681369 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1370 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1371 expect(glmrFees == 0n).to.be.true;13721373 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13741375 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1376 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1377 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1378 });1379 });13801381 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1382 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1383 const asset = {1384 V2: {1385 id: {1386 Concrete: {1387 parents: 1,1388 interior: {1389 X1: {Parachain: UNIQUE_CHAIN},1390 },1391 },1392 },1393 fun: {1394 Fungible: TRANSFER_AMOUNT,1395 },1396 },1397 };1398 const destination = {1399 V2: {1400 parents: 1,1401 interior: {1402 X2: [1403 {Parachain: UNIQUE_CHAIN},1404 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1405 ],1406 },1407 },1408 };14091410 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');14111412 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);14131414 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1415 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1416 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;14171418 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);14191420 expect(unqRandomAccountAsset).to.be.null;14211422 balanceForeignUnqTokenFinal = 0n;14231424 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1425 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1426 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1427 });14281429 await helper.wait.newBlocks(3);14301431 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1432 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1433 expect(actuallyDelivered > 0).to.be.true;14341435 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));14361437 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1438 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1439 expect(unqFees == 0n).to.be.true;1440 });14411442 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1443 // set Moonbeam's sovereign account's balance1444 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1445 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1446 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14471448 const moreThanMoonbeamHas = moonbeamBalance * 2n;14491450 let targetAccountBalance = 0n;1451 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14521453 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1454 targetAccount.addressRaw,1455 {1456 Concrete: {1457 parents: 0,1458 interior: 'Here',1459 },1460 },1461 moreThanMoonbeamHas,1462 );14631464 let maliciousXcmProgramSent: any;1465 const maxWaitBlocks = 3;14661467 // Try to trick Unique1468 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1469 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14701471 // Needed to bypass the call filter.1472 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1473 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14741475 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1476 });14771478 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1479 && event.outcome.isFailedToTransactAsset);14801481 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1482 expect(targetAccountBalance).to.be.equal(0n);14831484 // But Moonbeam still can send the correct amount1485 const validTransferAmount = moonbeamBalance / 2n;1486 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1487 targetAccount.addressRaw,1488 {1489 Concrete: {1490 parents: 0,1491 interior: 'Here',1492 },1493 },1494 validTransferAmount,1495 );14961497 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1498 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);14991500 // Needed to bypass the call filter.1501 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1502 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1503 });15041505 await helper.wait.newBlocks(maxWaitBlocks);15061507 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1508 expect(targetAccountBalance).to.be.equal(validTransferAmount);1509 });15101511 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1512 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1513 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15141515 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1516 targetAccount.addressRaw,1517 {1518 Concrete: {1519 parents: 0,1520 interior: {1521 X1: {1522 Parachain: UNIQUE_CHAIN,1523 },1524 },1525 },1526 },1527 testAmount,1528 );15291530 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1531 targetAccount.addressRaw,1532 {1533 Concrete: {1534 parents: 0,1535 interior: 'Here',1536 },1537 },1538 testAmount,1539 );15401541 let maliciousXcmProgramFullIdSent: any;1542 let maliciousXcmProgramHereIdSent: any;1543 const maxWaitBlocks = 3;15441545 // Try to trick Unique using full UNQ identification1546 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1547 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15481549 // Needed to bypass the call filter.1550 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1551 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15521553 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1554 });15551556 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1557 && event.outcome.isUntrustedReserveLocation);15581559 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1560 expect(accountBalance).to.be.equal(0n);15611562 // Try to trick Unique using shortened UNQ identification1563 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1564 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15651566 // Needed to bypass the call filter.1567 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1568 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15691570 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1571 });15721573 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1574 && event.outcome.isUntrustedReserveLocation);15751576 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1577 expect(accountBalance).to.be.equal(0n);1578 });1579});15801581describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1582 let alice: IKeyringPair;1583 let randomAccount: IKeyringPair;15841585 const UNQ_ASSET_ID_ON_ASTAR = 1;1586 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;15871588 // Unique -> Astar1589 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1590 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1591 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1592 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens15931594 // Astar -> Unique1595 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1596 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ15971598 let balanceAfterUniqueToAstarXCM: bigint;15991600 before(async () => {1601 await usingPlaygrounds(async (helper, privateKey) => {1602 alice = await privateKey('//Alice');1603 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1604 console.log('randomAccount', randomAccount.address);16051606 // Set the default version to wrap the first message to other chains.1607 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1608 });16091610 await usingAstarPlaygrounds(astarUrl, async (helper) => {1611 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1612 console.log('1. Create foreign asset and metadata');1613 // TODO update metadata with values from production1614 await helper.assets.create(1615 alice,1616 UNQ_ASSET_ID_ON_ASTAR,1617 alice.address,1618 UNQ_MINIMAL_BALANCE_ON_ASTAR,1619 );16201621 await helper.assets.setMetadata(1622 alice,1623 UNQ_ASSET_ID_ON_ASTAR,1624 'Cross chain UNQ',1625 'xcUNQ',1626 Number(UNQ_DECIMALS),1627 );16281629 console.log('2. Register asset location on Astar');1630 const assetLocation = {1631 V2: {1632 parents: 1,1633 interior: {1634 X1: {1635 Parachain: UNIQUE_CHAIN,1636 },1637 },1638 },1639 };16401641 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16421643 console.log('3. Set UNQ payment for XCM execution on Astar');1644 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1645 }1646 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1647 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1648 });1649 });16501651 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1652 const destination = {1653 V2: {1654 parents: 1,1655 interior: {1656 X1: {1657 Parachain: ASTAR_CHAIN,1658 },1659 },1660 },1661 };16621663 const beneficiary = {1664 V2: {1665 parents: 0,1666 interior: {1667 X1: {1668 AccountId32: {1669 network: 'Any',1670 id: randomAccount.addressRaw,1671 },1672 },1673 },1674 },1675 };16761677 const assets = {1678 V2: [1679 {1680 id: {1681 Concrete: {1682 parents: 0,1683 interior: 'Here',1684 },1685 },1686 fun: {1687 Fungible: unqToAstarTransferred,1688 },1689 },1690 ],1691 };16921693 // Initial balance is 100 UNQ1694 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1695 console.log(`Initial balance is: ${balanceBefore}`);16961697 const feeAssetItem = 0;1698 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16991700 // Balance after reserve transfer is less than 901701 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1702 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1703 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1704 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17051706 await usingAstarPlaygrounds(astarUrl, async (helper) => {1707 await helper.wait.newBlocks(3);1708 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1709 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17101711 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1712 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);17131714 expect(xcUNQbalance).to.eq(unqToAstarArrived);1715 // Astar balance does not changed1716 expect(astarBalance).to.eq(astarInitialBalance);1717 });1718 });17191720 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1721 await usingAstarPlaygrounds(astarUrl, async (helper) => {1722 const destination = {1723 V2: {1724 parents: 1,1725 interior: {1726 X1: {1727 Parachain: UNIQUE_CHAIN,1728 },1729 },1730 },1731 };17321733 const beneficiary = {1734 V2: {1735 parents: 0,1736 interior: {1737 X1: {1738 AccountId32: {1739 network: 'Any',1740 id: randomAccount.addressRaw,1741 },1742 },1743 },1744 },1745 };17461747 const assets = {1748 V2: [1749 {1750 id: {1751 Concrete: {1752 parents: 1,1753 interior: {1754 X1: {1755 Parachain: UNIQUE_CHAIN,1756 },1757 },1758 },1759 },1760 fun: {1761 Fungible: unqFromAstarTransfered,1762 },1763 },1764 ],1765 };17661767 // Initial balance is 1 ASTR1768 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1769 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1770 expect(balanceASTRbefore).to.eq(astarInitialBalance);17711772 const feeAssetItem = 0;1773 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1774 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17751776 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1777 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1778 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17791780 // Assert: xcUNQ balance correctly decreased1781 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1782 // Assert: ASTR balance is 0.996...1783 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1784 });17851786 await helper.wait.newBlocks(3);1787 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1788 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1789 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1790 });17911792 itSub('Astar can send only up to its balance', async ({helper}) => {1793 // set Astar's sovereign account's balance1794 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1795 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1796 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17971798 const moreThanAstarHas = astarBalance * 2n;17991800 let targetAccountBalance = 0n;1801 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18021803 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1804 targetAccount.addressRaw,1805 {1806 Concrete: {1807 parents: 0,1808 interior: 'Here',1809 },1810 },1811 moreThanAstarHas,1812 );18131814 let maliciousXcmProgramSent: any;1815 const maxWaitBlocks = 3;18161817 // Try to trick Unique1818 await usingAstarPlaygrounds(astarUrl, async (helper) => {1819 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18201821 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1822 });18231824 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1825 && event.outcome.isFailedToTransactAsset);18261827 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1828 expect(targetAccountBalance).to.be.equal(0n);18291830 // But Astar still can send the correct amount1831 const validTransferAmount = astarBalance / 2n;1832 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1833 targetAccount.addressRaw,1834 {1835 Concrete: {1836 parents: 0,1837 interior: 'Here',1838 },1839 },1840 validTransferAmount,1841 );18421843 await usingAstarPlaygrounds(astarUrl, async (helper) => {1844 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1845 });18461847 await helper.wait.newBlocks(maxWaitBlocks);18481849 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1850 expect(targetAccountBalance).to.be.equal(validTransferAmount);1851 });18521853 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1854 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1855 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18561857 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1858 targetAccount.addressRaw,1859 {1860 Concrete: {1861 parents: 1,1862 interior: {1863 X1: {1864 Parachain: UNIQUE_CHAIN,1865 },1866 },1867 },1868 },1869 testAmount,1870 );18711872 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1873 targetAccount.addressRaw,1874 {1875 Concrete: {1876 parents: 0,1877 interior: 'Here',1878 },1879 },1880 testAmount,1881 );18821883 let maliciousXcmProgramFullIdSent: any;1884 let maliciousXcmProgramHereIdSent: any;1885 const maxWaitBlocks = 3;18861887 // Try to trick Unique using full UNQ identification1888 await usingAstarPlaygrounds(astarUrl, async (helper) => {1889 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);18901891 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1892 });18931894 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1895 && event.outcome.isUntrustedReserveLocation);18961897 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1898 expect(accountBalance).to.be.equal(0n);18991900 // Try to trick Unique using shortened UNQ identification1901 await usingAstarPlaygrounds(astarUrl, async (helper) => {1902 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19031904 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1905 });19061907 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1908 && event.outcome.isUntrustedReserveLocation);19091910 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1911 expect(accountBalance).to.be.equal(0n);1912 });1913});