difftreelog
doc(polkadex CI): added comment
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 {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;60const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {61 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash62 && event.outcome.isFailedToTransactAsset);63};64const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {65 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash66 && event.outcome.isUntrustedReserveLocation);67};68describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {69 let alice: IKeyringPair;70 let bob: IKeyringPair;7172 let balanceStmnBefore: bigint;73 let balanceStmnAfter: bigint;7475 let balanceUniqueBefore: bigint;76 let balanceUniqueAfter: bigint;77 let balanceUniqueFinal: bigint;7879 let balanceBobBefore: bigint;80 let balanceBobAfter: bigint;81 let balanceBobFinal: bigint;8283 let balanceBobRelayTokenBefore: bigint;84 let balanceBobRelayTokenAfter: bigint;858687 before(async () => {88 await usingPlaygrounds(async (helper, privateKey) => {89 alice = await privateKey('//Alice');90 bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor9192 // Set the default version to wrap the first message to other chains.93 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);94 });9596 await usingRelayPlaygrounds(relayUrl, async (helper) => {97 // Fund accounts on Statemint98 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);99 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);100 });101102 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {103 const sovereignFundingAmount = 3_500_000_000n;104105 await helper.assets.create(106 alice,107 USDT_ASSET_ID,108 alice.address,109 USDT_ASSET_METADATA_MINIMAL_BALANCE,110 );111 await helper.assets.setMetadata(112 alice,113 USDT_ASSET_ID,114 USDT_ASSET_METADATA_NAME,115 USDT_ASSET_METADATA_DESCRIPTION,116 USDT_ASSET_METADATA_DECIMALS,117 );118 await helper.assets.mint(119 alice,120 USDT_ASSET_ID,121 alice.address,122 USDT_ASSET_AMOUNT,123 );124125 // funding parachain sovereing account on Statemint.126 // The sovereign account should be created before any action127 // (the assets pallet on Statemint check if the sovereign account exists)128 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);129 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);130 });131132133 await usingPlaygrounds(async (helper) => {134 const location = {135 V2: {136 parents: 1,137 interior: {X3: [138 {139 Parachain: STATEMINT_CHAIN,140 },141 {142 PalletInstance: STATEMINT_PALLET_INSTANCE,143 },144 {145 GeneralIndex: USDT_ASSET_ID,146 },147 ]},148 },149 };150151 const metadata =152 {153 name: USDT_ASSET_ID,154 symbol: USDT_ASSET_METADATA_NAME,155 decimals: USDT_ASSET_METADATA_DECIMALS,156 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,157 };158 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);159 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);160 });161162163 // Providing the relay currency to the unique sender account164 // (fee for USDT XCM are paid in relay tokens)165 await usingRelayPlaygrounds(relayUrl, async (helper) => {166 const destination = {167 V2: {168 parents: 0,169 interior: {X1: {170 Parachain: UNIQUE_CHAIN,171 },172 },173 }};174175 const beneficiary = {176 V2: {177 parents: 0,178 interior: {X1: {179 AccountId32: {180 network: 'Any',181 id: alice.addressRaw,182 },183 }},184 },185 };186187 const assets = {188 V2: [189 {190 id: {191 Concrete: {192 parents: 0,193 interior: 'Here',194 },195 },196 fun: {197 Fungible: TRANSFER_AMOUNT_RELAY,198 },199 },200 ],201 };202203 const feeAssetItem = 0;204205 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');206 });207208 });209210 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {211 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {212 const dest = {213 V2: {214 parents: 1,215 interior: {X1: {216 Parachain: UNIQUE_CHAIN,217 },218 },219 }};220221 const beneficiary = {222 V2: {223 parents: 0,224 interior: {X1: {225 AccountId32: {226 network: 'Any',227 id: alice.addressRaw,228 },229 }},230 },231 };232233 const assets = {234 V2: [235 {236 id: {237 Concrete: {238 parents: 0,239 interior: {240 X2: [241 {242 PalletInstance: STATEMINT_PALLET_INSTANCE,243 },244 {245 GeneralIndex: USDT_ASSET_ID,246 },247 ]},248 },249 },250 fun: {251 Fungible: TRANSFER_AMOUNT,252 },253 },254 ],255 };256257 const feeAssetItem = 0;258259 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);260 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');261262 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);263264 // common good parachain take commission in it native token265 console.log(266 '[Statemint -> Unique] transaction fees on Statemint: %s WND',267 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),268 );269 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;270271 });272273274 // ensure that asset has been delivered275 await helper.wait.newBlocks(3);276277 // expext collection id will be with id 1278 const free = await helper.ft.getBalance(1, {Substrate: alice.address});279280 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);281282 console.log(283 '[Statemint -> Unique] transaction fees on Unique: %s USDT',284 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),285 );286 console.log(287 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',288 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),289 );290 // commission has not paid in USDT token291 expect(free).to.be.equal(TRANSFER_AMOUNT);292 // ... and parachain native token293 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;294 });295296 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {297 const destination = {298 V2: {299 parents: 1,300 interior: {X2: [301 {302 Parachain: STATEMINT_CHAIN,303 },304 {305 AccountId32: {306 network: 'Any',307 id: alice.addressRaw,308 },309 },310 ]},311 },312 };313314 const relayFee = 400_000_000_000_000n;315 const currencies: [any, bigint][] = [316 [317 {318 ForeignAssetId: 0,319 },320 TRANSFER_AMOUNT,321 ],322 [323 {324 NativeAssetId: 'Parent',325 },326 relayFee,327 ],328 ];329330 const feeItem = 1;331332 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');333334 // the commission has been paid in parachain native token335 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);336 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));337 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;338339 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {340 await helper.wait.newBlocks(3);341342 // The USDT token never paid fees. Its amount not changed from begin value.343 // Also check that xcm transfer has been succeeded344 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;345 });346 });347348 itSub('Should connect and send Relay token to Unique', async ({helper}) => {349 balanceBobBefore = await helper.balance.getSubstrate(bob.address);350 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});351352 await usingRelayPlaygrounds(relayUrl, async (helper) => {353 const destination = {354 V2: {355 parents: 0,356 interior: {X1: {357 Parachain: UNIQUE_CHAIN,358 },359 },360 }};361362 const beneficiary = {363 V2: {364 parents: 0,365 interior: {X1: {366 AccountId32: {367 network: 'Any',368 id: bob.addressRaw,369 },370 }},371 },372 };373374 const assets = {375 V2: [376 {377 id: {378 Concrete: {379 parents: 0,380 interior: 'Here',381 },382 },383 fun: {384 Fungible: TRANSFER_AMOUNT_RELAY,385 },386 },387 ],388 };389390 const feeAssetItem = 0;391392 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');393 });394395 await helper.wait.newBlocks(3);396397 balanceBobAfter = await helper.balance.getSubstrate(bob.address);398 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});399400 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;401 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;402 console.log(403 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',404 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),405 );406 console.log(407 '[Relay (Westend) -> Unique] transaction fees: %s WND',408 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),409 );410 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);411 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;412 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;413 });414415 itSub('Should connect and send Relay token back', async ({helper}) => {416 let relayTokenBalanceBefore: bigint;417 let relayTokenBalanceAfter: bigint;418 await usingRelayPlaygrounds(relayUrl, async (helper) => {419 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);420 });421422 const destination = {423 V2: {424 parents: 1,425 interior: {426 X1:{427 AccountId32: {428 network: 'Any',429 id: bob.addressRaw,430 },431 },432 },433 },434 };435436 const currencies: any = [437 [438 {439 NativeAssetId: 'Parent',440 },441 TRANSFER_AMOUNT_RELAY,442 ],443 ];444445 const feeItem = 0;446447 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');448449 balanceBobFinal = await helper.balance.getSubstrate(bob.address);450 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));451452 await usingRelayPlaygrounds(relayUrl, async (helper) => {453 await helper.wait.newBlocks(10);454 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);455456 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;457 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));458 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;459 });460 });461});462463describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {464 let alice: IKeyringPair;465 let randomAccount: IKeyringPair;466467 let balanceUniqueTokenInit: bigint;468 let balanceUniqueTokenMiddle: bigint;469 let balanceUniqueTokenFinal: bigint;470 let balanceAcalaTokenInit: bigint;471 let balanceAcalaTokenMiddle: bigint;472 let balanceAcalaTokenFinal: bigint;473 let balanceUniqueForeignTokenInit: bigint;474 let balanceUniqueForeignTokenMiddle: bigint;475 let balanceUniqueForeignTokenFinal: bigint;476477 // computed by a test transfer from prod Unique to prod Acala.478 // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48479 // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)480 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;481 const acalaEps = 8n * 10n ** 16n;482483 let acalaBackwardTransferAmount: bigint;484485 before(async () => {486 await usingPlaygrounds(async (helper, privateKey) => {487 alice = await privateKey('//Alice');488 [randomAccount] = await helper.arrange.createAccounts([0n], alice);489490 // Set the default version to wrap the first message to other chains.491 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);492 });493494 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {495 const destination = {496 V2: {497 parents: 1,498 interior: {499 X1: {500 Parachain: UNIQUE_CHAIN,501 },502 },503 },504 };505506 const metadata = {507 name: 'Unique Network',508 symbol: 'UNQ',509 decimals: 18,510 minimalBalance: 1250000000000000000n,511 };512513 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);514 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);515 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);516 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});517 });518519 await usingPlaygrounds(async (helper) => {520 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);521 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);522 });523 });524525 itSub('Should connect and send UNQ to Acala', async ({helper}) => {526527 const destination = {528 V2: {529 parents: 1,530 interior: {531 X1: {532 Parachain: ACALA_CHAIN,533 },534 },535 },536 };537538 const beneficiary = {539 V2: {540 parents: 0,541 interior: {542 X1: {543 AccountId32: {544 network: 'Any',545 id: randomAccount.addressRaw,546 },547 },548 },549 },550 };551552 const assets = {553 V2: [554 {555 id: {556 Concrete: {557 parents: 0,558 interior: 'Here',559 },560 },561 fun: {562 Fungible: TRANSFER_AMOUNT,563 },564 },565 ],566 };567568 const feeAssetItem = 0;569570 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');571 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);572573 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;574 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));575 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;576577 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {578 await helper.wait.newBlocks(3);579580 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});581 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);582583 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;584 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;585 acalaBackwardTransferAmount = unqIncomeTransfer;586587 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;588589 console.log(590 '[Unique -> Acala] transaction fees on Acala: %s ACA',591 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),592 );593 console.log(594 '[Unique -> Acala] transaction fees on Acala: %s UNQ',595 helper.util.bigIntToDecimals(acaUnqFees),596 );597 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));598 expect(acaFees == 0n).to.be.true;599600 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;601602 expect(603 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,604 'Acala took different income fee, check the Acala foreign asset config',605 ).to.be.true;606 });607 });608609 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {610 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {611 const destination = {612 V2: {613 parents: 1,614 interior: {615 X2: [616 {Parachain: UNIQUE_CHAIN},617 {618 AccountId32: {619 network: 'Any',620 id: randomAccount.addressRaw,621 },622 },623 ],624 },625 },626 };627628 const id = {629 ForeignAsset: 0,630 };631632 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');633 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);634 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);635636 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;637 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;638639 console.log(640 '[Acala -> Unique] transaction fees on Acala: %s ACA',641 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),642 );643 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));644645 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;646 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;647 });648649 await helper.wait.newBlocks(3);650651 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);652 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;653 expect(actuallyDelivered > 0).to.be.true;654655 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));656657 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;658 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));659 expect(unqFees == 0n).to.be.true;660 });661662 itSub('Acala can send only up to its balance', async ({helper}) => {663 // set Acala's sovereign account's balance664 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);665 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);666 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);667668 const moreThanAcalaHas = acalaBalance * 2n;669670 let targetAccountBalance = 0n;671 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);672673 const uniqueMultilocation = {674 V2: {675 parents: 1,676 interior: {677 X1: {Parachain: UNIQUE_CHAIN},678 },679 },680 };681682 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(683 targetAccount.addressRaw,684 {685 Concrete: {686 parents: 0,687 interior: 'Here',688 },689 },690 moreThanAcalaHas,691 );692693 let maliciousXcmProgramSent: any;694 const maxWaitBlocks = 3;695696 // Try to trick Unique697 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {698 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);699700 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);701 });702703 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash704 && event.outcome.isFailedToTransactAsset);705706 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);707 expect(targetAccountBalance).to.be.equal(0n);708709 // But Acala still can send the correct amount710 const validTransferAmount = acalaBalance / 2n;711 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(712 targetAccount.addressRaw,713 {714 Concrete: {715 parents: 0,716 interior: 'Here',717 },718 },719 validTransferAmount,720 );721722 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {723 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);724 });725726 await helper.wait.newBlocks(maxWaitBlocks);727728 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);729 expect(targetAccountBalance).to.be.equal(validTransferAmount);730 });731732 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {733 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);734 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);735736 const uniqueMultilocation = {737 V2: {738 parents: 1,739 interior: {740 X1: {741 Parachain: UNIQUE_CHAIN,742 },743 },744 },745 };746747 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(748 targetAccount.addressRaw,749 {750 Concrete: {751 parents: 1,752 interior: {753 X1: {754 Parachain: UNIQUE_CHAIN,755 },756 },757 },758 },759 testAmount,760 );761762 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(763 targetAccount.addressRaw,764 {765 Concrete: {766 parents: 0,767 interior: 'Here',768 },769 },770 testAmount,771 );772773 let maliciousXcmProgramFullIdSent: any;774 let maliciousXcmProgramHereIdSent: any;775 const maxWaitBlocks = 3;776777 // Try to trick Unique using full UNQ identification778 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {779 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);780781 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);782 });783784 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash785 && event.outcome.isUntrustedReserveLocation);786787 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);788 expect(accountBalance).to.be.equal(0n);789790 // Try to trick Unique using shortened UNQ identification791 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {792 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);793794 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);795 });796797 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash798 && event.outcome.isUntrustedReserveLocation);799800 accountBalance = await helper.balance.getSubstrate(targetAccount.address);801 expect(accountBalance).to.be.equal(0n);802 });803});804805describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {806 let alice: IKeyringPair;807 let randomAccount: IKeyringPair;808 let unqFees: bigint;809 let balanceUniqueTokenInit: bigint;810 let balanceUniqueTokenMiddle: bigint;811 let balanceUniqueTokenFinal: bigint;812 const maxWaitBlocks = 6;813814 const uniqueAssetId = {815 Concrete: {816 parents: 1,817 interior: {818 X1: {819 Parachain: UNIQUE_CHAIN,820 },821 },822 },823 };824825 before(async () => {826 await usingPlaygrounds(async (helper, privateKey) => {827 alice = await privateKey('//Alice');828 [randomAccount] = await helper.arrange.createAccounts([0n], alice);829830 // Set the default version to wrap the first message to other chains.831 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);832 });833834 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {835 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', [])).toJSON() as [])836 .map(nToBigInt).length != 0;837838 if(!isWhitelisted) {839 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);840 }841842 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);843 });844845 await usingPlaygrounds(async (helper) => {846 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);847 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);848 });849 });850851 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {852853 const destination = {854 V2: {855 parents: 1,856 interior: {857 X1: {858 Parachain: POLKADEX_CHAIN,859 },860 },861 },862 };863864 const beneficiary = {865 V2: {866 parents: 0,867 interior: {868 X1: {869 AccountId32: {870 network: 'Any',871 id: randomAccount.addressRaw,872 },873 },874 },875 },876 };877878 const assets = {879 V2: [880 {881 id: {882 Concrete: {883 parents: 0,884 interior: 'Here',885 },886 },887 fun: {888 Fungible: TRANSFER_AMOUNT,889 },890 },891 ],892 };893894 const feeAssetItem = 0;895896 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');897 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);898 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);899900 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;901 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));902 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;903904 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {905 /*906 Since only the parachain part of the Polkadex907 infrastructure is launched (without their908 solochain validators), processing incoming909 assets will lead to an error.910 This error indicates that the Polkadex chain911 received a message from the Unique network,912 since the hash is being checked to ensure913 it matches what was sent.914 */915 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);916 });917 });918919920 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {921922 const uniqueMultilocation = {923 V2: {924 parents: 1,925 interior: {926 X1: {Parachain: UNIQUE_CHAIN},927 },928 },929 };930931 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(932 randomAccount.addressRaw,933 {934 Concrete: {935 parents: 1,936 interior: {937 X1: {Parachain: UNIQUE_CHAIN},938 },939 },940 },941 TRANSFER_AMOUNT,942 );943944 let xcmProgramSent: any;945946947 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {948 await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);949950 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);951 });952953 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);954955 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);956957 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);958 });959960 itSub('Polkadex can send only up to its balance', async ({helper}) => {961 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);962 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);963 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);964 const moreThanPolkadexHas = 2n * polkadexBalance;965966 const targetAccount = helper.arrange.createEmptyAccount();967968 const uniqueMultilocation = {969 V2: {970 parents: 1,971 interior: {972 X1: {Parachain: UNIQUE_CHAIN},973 },974 },975 };976977 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(978 targetAccount.addressRaw,979 {980 Concrete: {981 parents: 0,982 interior: 'Here',983 },984 },985 moreThanPolkadexHas,986 );987988 let maliciousXcmProgramSent: any;989990991 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {992 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);993994 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);995 });996997 await expectFailedToTransact(helper, maliciousXcmProgramSent);998999 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1000 expect(targetAccountBalance).to.be.equal(0n);1001 });10021003 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {1004 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1005 const targetAccount = helper.arrange.createEmptyAccount();10061007 const uniqueMultilocation = {1008 V2: {1009 parents: 1,1010 interior: {1011 X1: {1012 Parachain: UNIQUE_CHAIN,1013 },1014 },1015 },1016 };10171018 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1019 targetAccount.addressRaw,1020 {1021 Concrete: {1022 parents: 1,1023 interior: {1024 X1: {1025 Parachain: UNIQUE_CHAIN,1026 },1027 },1028 },1029 },1030 testAmount,1031 );10321033 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1034 targetAccount.addressRaw,1035 {1036 Concrete: {1037 parents: 0,1038 interior: 'Here',1039 },1040 },1041 testAmount,1042 );10431044 let maliciousXcmProgramFullIdSent: any;1045 let maliciousXcmProgramHereIdSent: any;1046 const maxWaitBlocks = 3;10471048 // Try to trick Unique using full UNQ identification1049 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1050 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10511052 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1053 });10541055 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10561057 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1058 expect(accountBalance).to.be.equal(0n);10591060 // Try to trick Unique using shortened UNQ identification1061 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1062 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10631064 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1065 });10661067 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10681069 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1070 expect(accountBalance).to.be.equal(0n);1071 });1072});10731074// These tests are relevant only when1075// the the corresponding foreign assets are not registered1076describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1077 let alice: IKeyringPair;1078 let alith: IKeyringPair;10791080 const testAmount = 100_000_000_000n;1081 let uniqueParachainJunction;1082 let uniqueAccountJunction;10831084 let uniqueParachainMultilocation: any;1085 let uniqueAccountMultilocation: any;1086 let uniqueCombinedMultilocation: any;1087 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210881089 let messageSent: any;10901091 const maxWaitBlocks = 3;10921093 before(async () => {1094 await usingPlaygrounds(async (helper, privateKey) => {1095 alice = await privateKey('//Alice');10961097 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1098 uniqueAccountJunction = {1099 AccountId32: {1100 network: 'Any',1101 id: alice.addressRaw,1102 },1103 };11041105 uniqueParachainMultilocation = {1106 V2: {1107 parents: 1,1108 interior: {1109 X1: uniqueParachainJunction,1110 },1111 },1112 };11131114 uniqueAccountMultilocation = {1115 V2: {1116 parents: 0,1117 interior: {1118 X1: uniqueAccountJunction,1119 },1120 },1121 };11221123 uniqueCombinedMultilocation = {1124 V2: {1125 parents: 1,1126 interior: {1127 X2: [uniqueParachainJunction, uniqueAccountJunction],1128 },1129 },1130 };11311132 uniqueCombinedMultilocationAcala = {1133 V2: {1134 parents: 1,1135 interior: {1136 X2: [uniqueParachainJunction, uniqueAccountJunction],1137 },1138 },1139 };11401141 // Set the default version to wrap the first message to other chains.1142 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1143 });11441145 // eslint-disable-next-line require-await1146 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1147 alith = helper.account.alithAccount();1148 });1149 });1150115111521153 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1154 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1155 const id = {1156 Token: 'ACA',1157 };1158 const destination = uniqueCombinedMultilocationAcala;1159 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11601161 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1162 });11631164 await expectFailedToTransact(helper, messageSent);1165 });11661167 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1168 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1169 const id = 'SelfReserve';1170 const destination = uniqueCombinedMultilocation;1171 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11721173 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1174 });11751176 await expectFailedToTransact(helper, messageSent);1177 });11781179 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1180 await usingAstarPlaygrounds(astarUrl, async (helper) => {1181 const destinationParachain = uniqueParachainMultilocation;1182 const beneficiary = uniqueAccountMultilocation;1183 const assets = {1184 V2: [{1185 id: {1186 Concrete: {1187 parents: 0,1188 interior: 'Here',1189 },1190 },1191 fun: {1192 Fungible: testAmount,1193 },1194 }],1195 };1196 const feeAssetItem = 0;11971198 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1199 destinationParachain,1200 beneficiary,1201 assets,1202 feeAssetItem,1203 ]);12041205 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1206 });12071208 await expectFailedToTransact(helper, messageSent);1209 });12101211 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12121213 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1214 helper.arrange.createEmptyAccount().addressRaw,1215 {1216 Concrete: {1217 parents: 1,1218 interior: {1219 X1: {1220 Parachain: POLKADEX_CHAIN,1221 },1222 },1223 },1224 },1225 testAmount,1226 );12271228 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1229 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1230 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1231 });12321233 await expectFailedToTransact(helper, messageSent);1234 });1235});12361237describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1238 // Unique constants1239 let alice: IKeyringPair;1240 let uniqueAssetLocation;12411242 let randomAccountUnique: IKeyringPair;1243 let randomAccountMoonbeam: IKeyringPair;12441245 // Moonbeam constants1246 let assetId: string;12471248 const uniqueAssetMetadata = {1249 name: 'xcUnique',1250 symbol: 'xcUNQ',1251 decimals: 18,1252 isFrozen: false,1253 minimalBalance: 1n,1254 };12551256 let balanceUniqueTokenInit: bigint;1257 let balanceUniqueTokenMiddle: bigint;1258 let balanceUniqueTokenFinal: bigint;1259 let balanceForeignUnqTokenInit: bigint;1260 let balanceForeignUnqTokenMiddle: bigint;1261 let balanceForeignUnqTokenFinal: bigint;1262 let balanceGlmrTokenInit: bigint;1263 let balanceGlmrTokenMiddle: bigint;1264 let balanceGlmrTokenFinal: bigint;12651266 before(async () => {1267 await usingPlaygrounds(async (helper, privateKey) => {1268 alice = await privateKey('//Alice');1269 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12701271 balanceForeignUnqTokenInit = 0n;12721273 // Set the default version to wrap the first message to other chains.1274 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1275 });12761277 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1278 const alithAccount = helper.account.alithAccount();1279 const baltatharAccount = helper.account.baltatharAccount();1280 const dorothyAccount = helper.account.dorothyAccount();12811282 randomAccountMoonbeam = helper.account.create();12831284 // >>> Sponsoring Dorothy >>>1285 console.log('Sponsoring Dorothy.......');1286 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1287 console.log('Sponsoring Dorothy.......DONE');1288 // <<< Sponsoring Dorothy <<<12891290 uniqueAssetLocation = {1291 XCM: {1292 parents: 1,1293 interior: {X1: {Parachain: UNIQUE_CHAIN}},1294 },1295 };1296 const existentialDeposit = 1n;1297 const isSufficient = true;1298 const unitsPerSecond = 1n;1299 const numAssetsWeightHint = 0;13001301 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 // >>> Acquire Unique AssetId Info on Moonbeam >>>13221323 // >>> Sponsoring random Account >>>1324 console.log('Sponsoring random Account.......');1325 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1326 console.log('Sponsoring random Account.......DONE');1327 // <<< Sponsoring random Account <<<13281329 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1330 });13311332 await usingPlaygrounds(async (helper) => {1333 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1334 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1335 });1336 });13371338 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1339 const currencyId = {1340 NativeAssetId: 'Here',1341 };1342 const dest = {1343 V2: {1344 parents: 1,1345 interior: {1346 X2: [1347 {Parachain: MOONBEAM_CHAIN},1348 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1349 ],1350 },1351 },1352 };1353 const amount = TRANSFER_AMOUNT;13541355 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13561357 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1358 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13591360 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1361 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1362 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13631364 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1365 await helper.wait.newBlocks(3);13661367 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 uniqueMultilocation = {1454 V2: {1455 parents: 1,1456 interior: {1457 X1: {Parachain: UNIQUE_CHAIN},1458 },1459 },1460 };14611462 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1463 targetAccount.addressRaw,1464 {1465 Concrete: {1466 parents: 0,1467 interior: 'Here',1468 },1469 },1470 moreThanMoonbeamHas,1471 );14721473 let maliciousXcmProgramSent: any;1474 const maxWaitBlocks = 3;14751476 // Try to trick Unique1477 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1478 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14791480 // Needed to bypass the call filter.1481 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1482 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14831484 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1485 });14861487 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1488 && event.outcome.isFailedToTransactAsset);14891490 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1491 expect(targetAccountBalance).to.be.equal(0n);14921493 // But Moonbeam still can send the correct amount1494 const validTransferAmount = moonbeamBalance / 2n;1495 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1496 targetAccount.addressRaw,1497 {1498 Concrete: {1499 parents: 0,1500 interior: 'Here',1501 },1502 },1503 validTransferAmount,1504 );15051506 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1507 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);15081509 // Needed to bypass the call filter.1510 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1511 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1512 });15131514 await helper.wait.newBlocks(maxWaitBlocks);15151516 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1517 expect(targetAccountBalance).to.be.equal(validTransferAmount);1518 });15191520 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1521 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1522 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15231524 const uniqueMultilocation = {1525 V2: {1526 parents: 1,1527 interior: {1528 X1: {1529 Parachain: UNIQUE_CHAIN,1530 },1531 },1532 },1533 };15341535 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1536 targetAccount.addressRaw,1537 {1538 Concrete: {1539 parents: 0,1540 interior: {1541 X1: {1542 Parachain: UNIQUE_CHAIN,1543 },1544 },1545 },1546 },1547 testAmount,1548 );15491550 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1551 targetAccount.addressRaw,1552 {1553 Concrete: {1554 parents: 0,1555 interior: 'Here',1556 },1557 },1558 testAmount,1559 );15601561 let maliciousXcmProgramFullIdSent: any;1562 let maliciousXcmProgramHereIdSent: any;1563 const maxWaitBlocks = 3;15641565 // Try to trick Unique using full UNQ identification1566 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1567 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15681569 // Needed to bypass the call filter.1570 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1571 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15721573 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1574 });15751576 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1577 && event.outcome.isUntrustedReserveLocation);15781579 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1580 expect(accountBalance).to.be.equal(0n);15811582 // Try to trick Unique using shortened UNQ identification1583 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1584 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15851586 // Needed to bypass the call filter.1587 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1588 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15891590 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1591 });15921593 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1594 && event.outcome.isUntrustedReserveLocation);15951596 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1597 expect(accountBalance).to.be.equal(0n);1598 });1599});16001601describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1602 let alice: IKeyringPair;1603 let randomAccount: IKeyringPair;16041605 const UNQ_ASSET_ID_ON_ASTAR = 1;1606 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;16071608 // Unique -> Astar1609 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1610 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1611 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1612 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens16131614 // Astar -> Unique1615 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1616 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ16171618 let balanceAfterUniqueToAstarXCM: bigint;16191620 before(async () => {1621 await usingPlaygrounds(async (helper, privateKey) => {1622 alice = await privateKey('//Alice');1623 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1624 console.log('randomAccount', randomAccount.address);16251626 // Set the default version to wrap the first message to other chains.1627 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1628 });16291630 await usingAstarPlaygrounds(astarUrl, async (helper) => {1631 console.log('1. Create foreign asset and metadata');1632 // TODO update metadata with values from production1633 await helper.assets.create(1634 alice,1635 UNQ_ASSET_ID_ON_ASTAR,1636 alice.address,1637 UNQ_MINIMAL_BALANCE_ON_ASTAR,1638 );16391640 await helper.assets.setMetadata(1641 alice,1642 UNQ_ASSET_ID_ON_ASTAR,1643 'Cross chain UNQ',1644 'xcUNQ',1645 Number(UNQ_DECIMALS),1646 );16471648 console.log('2. Register asset location on Astar');1649 const assetLocation = {1650 V2: {1651 parents: 1,1652 interior: {1653 X1: {1654 Parachain: UNIQUE_CHAIN,1655 },1656 },1657 },1658 };16591660 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16611662 console.log('3. Set UNQ payment for XCM execution on Astar');1663 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);16641665 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1666 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1667 });1668 });16691670 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1671 const destination = {1672 V2: {1673 parents: 1,1674 interior: {1675 X1: {1676 Parachain: ASTAR_CHAIN,1677 },1678 },1679 },1680 };16811682 const beneficiary = {1683 V2: {1684 parents: 0,1685 interior: {1686 X1: {1687 AccountId32: {1688 network: 'Any',1689 id: randomAccount.addressRaw,1690 },1691 },1692 },1693 },1694 };16951696 const assets = {1697 V2: [1698 {1699 id: {1700 Concrete: {1701 parents: 0,1702 interior: 'Here',1703 },1704 },1705 fun: {1706 Fungible: unqToAstarTransferred,1707 },1708 },1709 ],1710 };17111712 // Initial balance is 100 UNQ1713 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1714 console.log(`Initial balance is: ${balanceBefore}`);17151716 const feeAssetItem = 0;1717 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');17181719 // Balance after reserve transfer is less than 901720 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1721 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1722 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1723 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17241725 await usingAstarPlaygrounds(astarUrl, async (helper) => {1726 await helper.wait.newBlocks(3);1727 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1728 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17291730 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1731 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);17321733 expect(xcUNQbalance).to.eq(unqToAstarArrived);1734 // Astar balance does not changed1735 expect(astarBalance).to.eq(astarInitialBalance);1736 });1737 });17381739 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1740 await usingAstarPlaygrounds(astarUrl, async (helper) => {1741 const destination = {1742 V2: {1743 parents: 1,1744 interior: {1745 X1: {1746 Parachain: UNIQUE_CHAIN,1747 },1748 },1749 },1750 };17511752 const beneficiary = {1753 V2: {1754 parents: 0,1755 interior: {1756 X1: {1757 AccountId32: {1758 network: 'Any',1759 id: randomAccount.addressRaw,1760 },1761 },1762 },1763 },1764 };17651766 const assets = {1767 V2: [1768 {1769 id: {1770 Concrete: {1771 parents: 1,1772 interior: {1773 X1: {1774 Parachain: UNIQUE_CHAIN,1775 },1776 },1777 },1778 },1779 fun: {1780 Fungible: unqFromAstarTransfered,1781 },1782 },1783 ],1784 };17851786 // Initial balance is 1 ASTR1787 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1788 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1789 expect(balanceASTRbefore).to.eq(astarInitialBalance);17901791 const feeAssetItem = 0;1792 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1793 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17941795 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1796 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1797 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17981799 // Assert: xcUNQ balance correctly decreased1800 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1801 // Assert: ASTR balance is 0.996...1802 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1803 });18041805 await helper.wait.newBlocks(3);1806 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1807 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1808 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1809 });18101811 itSub('Astar can send only up to its balance', async ({helper}) => {1812 // set Astar's sovereign account's balance1813 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1814 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1815 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);18161817 const moreThanAstarHas = astarBalance * 2n;18181819 let targetAccountBalance = 0n;1820 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18211822 const uniqueMultilocation = {1823 V2: {1824 parents: 1,1825 interior: {1826 X1: {Parachain: UNIQUE_CHAIN},1827 },1828 },1829 };18301831 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1832 targetAccount.addressRaw,1833 {1834 Concrete: {1835 parents: 0,1836 interior: 'Here',1837 },1838 },1839 moreThanAstarHas,1840 );18411842 let maliciousXcmProgramSent: any;1843 const maxWaitBlocks = 3;18441845 // Try to trick Unique1846 await usingAstarPlaygrounds(astarUrl, async (helper) => {1847 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18481849 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1850 });18511852 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1853 && event.outcome.isFailedToTransactAsset);18541855 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1856 expect(targetAccountBalance).to.be.equal(0n);18571858 // But Astar still can send the correct amount1859 const validTransferAmount = astarBalance / 2n;1860 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1861 targetAccount.addressRaw,1862 {1863 Concrete: {1864 parents: 0,1865 interior: 'Here',1866 },1867 },1868 validTransferAmount,1869 );18701871 await usingAstarPlaygrounds(astarUrl, async (helper) => {1872 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1873 });18741875 await helper.wait.newBlocks(maxWaitBlocks);18761877 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1878 expect(targetAccountBalance).to.be.equal(validTransferAmount);1879 });18801881 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1882 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1883 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18841885 const uniqueMultilocation = {1886 V2: {1887 parents: 1,1888 interior: {1889 X1: {1890 Parachain: UNIQUE_CHAIN,1891 },1892 },1893 },1894 };18951896 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1897 targetAccount.addressRaw,1898 {1899 Concrete: {1900 parents: 1,1901 interior: {1902 X1: {1903 Parachain: UNIQUE_CHAIN,1904 },1905 },1906 },1907 },1908 testAmount,1909 );19101911 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1912 targetAccount.addressRaw,1913 {1914 Concrete: {1915 parents: 0,1916 interior: 'Here',1917 },1918 },1919 testAmount,1920 );19211922 let maliciousXcmProgramFullIdSent: any;1923 let maliciousXcmProgramHereIdSent: any;1924 const maxWaitBlocks = 3;19251926 // Try to trick Unique using full UNQ identification1927 await usingAstarPlaygrounds(astarUrl, async (helper) => {1928 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);19291930 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1931 });19321933 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1934 && event.outcome.isUntrustedReserveLocation);19351936 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1937 expect(accountBalance).to.be.equal(0n);19381939 // Try to trick Unique using shortened UNQ identification1940 await usingAstarPlaygrounds(astarUrl, async (helper) => {1941 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19421943 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1944 });19451946 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1947 && event.outcome.isUntrustedReserveLocation);19481949 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1950 expect(accountBalance).to.be.equal(0n);1951 });1952});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 {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;60const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {61 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash62 && event.outcome.isFailedToTransactAsset);63};64const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {65 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash66 && event.outcome.isUntrustedReserveLocation);67};68describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {69 let alice: IKeyringPair;70 let bob: IKeyringPair;7172 let balanceStmnBefore: bigint;73 let balanceStmnAfter: bigint;7475 let balanceUniqueBefore: bigint;76 let balanceUniqueAfter: bigint;77 let balanceUniqueFinal: bigint;7879 let balanceBobBefore: bigint;80 let balanceBobAfter: bigint;81 let balanceBobFinal: bigint;8283 let balanceBobRelayTokenBefore: bigint;84 let balanceBobRelayTokenAfter: bigint;858687 before(async () => {88 await usingPlaygrounds(async (helper, privateKey) => {89 alice = await privateKey('//Alice');90 bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor9192 // Set the default version to wrap the first message to other chains.93 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);94 });9596 await usingRelayPlaygrounds(relayUrl, async (helper) => {97 // Fund accounts on Statemint98 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);99 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);100 });101102 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {103 const sovereignFundingAmount = 3_500_000_000n;104105 await helper.assets.create(106 alice,107 USDT_ASSET_ID,108 alice.address,109 USDT_ASSET_METADATA_MINIMAL_BALANCE,110 );111 await helper.assets.setMetadata(112 alice,113 USDT_ASSET_ID,114 USDT_ASSET_METADATA_NAME,115 USDT_ASSET_METADATA_DESCRIPTION,116 USDT_ASSET_METADATA_DECIMALS,117 );118 await helper.assets.mint(119 alice,120 USDT_ASSET_ID,121 alice.address,122 USDT_ASSET_AMOUNT,123 );124125 // funding parachain sovereing account on Statemint.126 // The sovereign account should be created before any action127 // (the assets pallet on Statemint check if the sovereign account exists)128 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);129 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);130 });131132133 await usingPlaygrounds(async (helper) => {134 const location = {135 V2: {136 parents: 1,137 interior: {X3: [138 {139 Parachain: STATEMINT_CHAIN,140 },141 {142 PalletInstance: STATEMINT_PALLET_INSTANCE,143 },144 {145 GeneralIndex: USDT_ASSET_ID,146 },147 ]},148 },149 };150151 const metadata =152 {153 name: USDT_ASSET_ID,154 symbol: USDT_ASSET_METADATA_NAME,155 decimals: USDT_ASSET_METADATA_DECIMALS,156 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,157 };158 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);159 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);160 });161162163 // Providing the relay currency to the unique sender account164 // (fee for USDT XCM are paid in relay tokens)165 await usingRelayPlaygrounds(relayUrl, async (helper) => {166 const destination = {167 V2: {168 parents: 0,169 interior: {X1: {170 Parachain: UNIQUE_CHAIN,171 },172 },173 }};174175 const beneficiary = {176 V2: {177 parents: 0,178 interior: {X1: {179 AccountId32: {180 network: 'Any',181 id: alice.addressRaw,182 },183 }},184 },185 };186187 const assets = {188 V2: [189 {190 id: {191 Concrete: {192 parents: 0,193 interior: 'Here',194 },195 },196 fun: {197 Fungible: TRANSFER_AMOUNT_RELAY,198 },199 },200 ],201 };202203 const feeAssetItem = 0;204205 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');206 });207208 });209210 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {211 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {212 const dest = {213 V2: {214 parents: 1,215 interior: {X1: {216 Parachain: UNIQUE_CHAIN,217 },218 },219 }};220221 const beneficiary = {222 V2: {223 parents: 0,224 interior: {X1: {225 AccountId32: {226 network: 'Any',227 id: alice.addressRaw,228 },229 }},230 },231 };232233 const assets = {234 V2: [235 {236 id: {237 Concrete: {238 parents: 0,239 interior: {240 X2: [241 {242 PalletInstance: STATEMINT_PALLET_INSTANCE,243 },244 {245 GeneralIndex: USDT_ASSET_ID,246 },247 ]},248 },249 },250 fun: {251 Fungible: TRANSFER_AMOUNT,252 },253 },254 ],255 };256257 const feeAssetItem = 0;258259 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);260 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');261262 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);263264 // common good parachain take commission in it native token265 console.log(266 '[Statemint -> Unique] transaction fees on Statemint: %s WND',267 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),268 );269 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;270271 });272273274 // ensure that asset has been delivered275 await helper.wait.newBlocks(3);276277 // expext collection id will be with id 1278 const free = await helper.ft.getBalance(1, {Substrate: alice.address});279280 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);281282 console.log(283 '[Statemint -> Unique] transaction fees on Unique: %s USDT',284 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),285 );286 console.log(287 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',288 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),289 );290 // commission has not paid in USDT token291 expect(free).to.be.equal(TRANSFER_AMOUNT);292 // ... and parachain native token293 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;294 });295296 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {297 const destination = {298 V2: {299 parents: 1,300 interior: {X2: [301 {302 Parachain: STATEMINT_CHAIN,303 },304 {305 AccountId32: {306 network: 'Any',307 id: alice.addressRaw,308 },309 },310 ]},311 },312 };313314 const relayFee = 400_000_000_000_000n;315 const currencies: [any, bigint][] = [316 [317 {318 ForeignAssetId: 0,319 },320 TRANSFER_AMOUNT,321 ],322 [323 {324 NativeAssetId: 'Parent',325 },326 relayFee,327 ],328 ];329330 const feeItem = 1;331332 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');333334 // the commission has been paid in parachain native token335 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);336 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));337 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;338339 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {340 await helper.wait.newBlocks(3);341342 // The USDT token never paid fees. Its amount not changed from begin value.343 // Also check that xcm transfer has been succeeded344 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;345 });346 });347348 itSub('Should connect and send Relay token to Unique', async ({helper}) => {349 balanceBobBefore = await helper.balance.getSubstrate(bob.address);350 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});351352 await usingRelayPlaygrounds(relayUrl, async (helper) => {353 const destination = {354 V2: {355 parents: 0,356 interior: {X1: {357 Parachain: UNIQUE_CHAIN,358 },359 },360 }};361362 const beneficiary = {363 V2: {364 parents: 0,365 interior: {X1: {366 AccountId32: {367 network: 'Any',368 id: bob.addressRaw,369 },370 }},371 },372 };373374 const assets = {375 V2: [376 {377 id: {378 Concrete: {379 parents: 0,380 interior: 'Here',381 },382 },383 fun: {384 Fungible: TRANSFER_AMOUNT_RELAY,385 },386 },387 ],388 };389390 const feeAssetItem = 0;391392 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');393 });394395 await helper.wait.newBlocks(3);396397 balanceBobAfter = await helper.balance.getSubstrate(bob.address);398 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});399400 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;401 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;402 console.log(403 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',404 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),405 );406 console.log(407 '[Relay (Westend) -> Unique] transaction fees: %s WND',408 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),409 );410 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);411 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;412 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;413 });414415 itSub('Should connect and send Relay token back', async ({helper}) => {416 let relayTokenBalanceBefore: bigint;417 let relayTokenBalanceAfter: bigint;418 await usingRelayPlaygrounds(relayUrl, async (helper) => {419 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);420 });421422 const destination = {423 V2: {424 parents: 1,425 interior: {426 X1:{427 AccountId32: {428 network: 'Any',429 id: bob.addressRaw,430 },431 },432 },433 },434 };435436 const currencies: any = [437 [438 {439 NativeAssetId: 'Parent',440 },441 TRANSFER_AMOUNT_RELAY,442 ],443 ];444445 const feeItem = 0;446447 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');448449 balanceBobFinal = await helper.balance.getSubstrate(bob.address);450 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));451452 await usingRelayPlaygrounds(relayUrl, async (helper) => {453 await helper.wait.newBlocks(10);454 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);455456 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;457 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));458 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;459 });460 });461});462463describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {464 let alice: IKeyringPair;465 let randomAccount: IKeyringPair;466467 let balanceUniqueTokenInit: bigint;468 let balanceUniqueTokenMiddle: bigint;469 let balanceUniqueTokenFinal: bigint;470 let balanceAcalaTokenInit: bigint;471 let balanceAcalaTokenMiddle: bigint;472 let balanceAcalaTokenFinal: bigint;473 let balanceUniqueForeignTokenInit: bigint;474 let balanceUniqueForeignTokenMiddle: bigint;475 let balanceUniqueForeignTokenFinal: bigint;476477 // computed by a test transfer from prod Unique to prod Acala.478 // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48479 // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)480 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;481 const acalaEps = 8n * 10n ** 16n;482483 let acalaBackwardTransferAmount: bigint;484485 before(async () => {486 await usingPlaygrounds(async (helper, privateKey) => {487 alice = await privateKey('//Alice');488 [randomAccount] = await helper.arrange.createAccounts([0n], alice);489490 // Set the default version to wrap the first message to other chains.491 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);492 });493494 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {495 const destination = {496 V2: {497 parents: 1,498 interior: {499 X1: {500 Parachain: UNIQUE_CHAIN,501 },502 },503 },504 };505506 const metadata = {507 name: 'Unique Network',508 symbol: 'UNQ',509 decimals: 18,510 minimalBalance: 1250000000000000000n,511 };512513 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);514 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);515 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);516 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});517 });518519 await usingPlaygrounds(async (helper) => {520 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);521 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);522 });523 });524525 itSub('Should connect and send UNQ to Acala', async ({helper}) => {526527 const destination = {528 V2: {529 parents: 1,530 interior: {531 X1: {532 Parachain: ACALA_CHAIN,533 },534 },535 },536 };537538 const beneficiary = {539 V2: {540 parents: 0,541 interior: {542 X1: {543 AccountId32: {544 network: 'Any',545 id: randomAccount.addressRaw,546 },547 },548 },549 },550 };551552 const assets = {553 V2: [554 {555 id: {556 Concrete: {557 parents: 0,558 interior: 'Here',559 },560 },561 fun: {562 Fungible: TRANSFER_AMOUNT,563 },564 },565 ],566 };567568 const feeAssetItem = 0;569570 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');571 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);572573 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;574 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));575 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;576577 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {578 await helper.wait.newBlocks(3);579580 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});581 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);582583 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;584 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;585 acalaBackwardTransferAmount = unqIncomeTransfer;586587 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;588589 console.log(590 '[Unique -> Acala] transaction fees on Acala: %s ACA',591 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),592 );593 console.log(594 '[Unique -> Acala] transaction fees on Acala: %s UNQ',595 helper.util.bigIntToDecimals(acaUnqFees),596 );597 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));598 expect(acaFees == 0n).to.be.true;599600 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;601602 expect(603 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,604 'Acala took different income fee, check the Acala foreign asset config',605 ).to.be.true;606 });607 });608609 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {610 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {611 const destination = {612 V2: {613 parents: 1,614 interior: {615 X2: [616 {Parachain: UNIQUE_CHAIN},617 {618 AccountId32: {619 network: 'Any',620 id: randomAccount.addressRaw,621 },622 },623 ],624 },625 },626 };627628 const id = {629 ForeignAsset: 0,630 };631632 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');633 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);634 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);635636 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;637 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;638639 console.log(640 '[Acala -> Unique] transaction fees on Acala: %s ACA',641 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),642 );643 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));644645 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;646 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;647 });648649 await helper.wait.newBlocks(3);650651 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);652 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;653 expect(actuallyDelivered > 0).to.be.true;654655 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));656657 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;658 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));659 expect(unqFees == 0n).to.be.true;660 });661662 itSub('Acala can send only up to its balance', async ({helper}) => {663 // set Acala's sovereign account's balance664 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);665 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);666 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);667668 const moreThanAcalaHas = acalaBalance * 2n;669670 let targetAccountBalance = 0n;671 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);672673 const uniqueMultilocation = {674 V2: {675 parents: 1,676 interior: {677 X1: {Parachain: UNIQUE_CHAIN},678 },679 },680 };681682 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(683 targetAccount.addressRaw,684 {685 Concrete: {686 parents: 0,687 interior: 'Here',688 },689 },690 moreThanAcalaHas,691 );692693 let maliciousXcmProgramSent: any;694 const maxWaitBlocks = 3;695696 // Try to trick Unique697 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {698 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);699700 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);701 });702703 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash704 && event.outcome.isFailedToTransactAsset);705706 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);707 expect(targetAccountBalance).to.be.equal(0n);708709 // But Acala still can send the correct amount710 const validTransferAmount = acalaBalance / 2n;711 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(712 targetAccount.addressRaw,713 {714 Concrete: {715 parents: 0,716 interior: 'Here',717 },718 },719 validTransferAmount,720 );721722 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {723 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);724 });725726 await helper.wait.newBlocks(maxWaitBlocks);727728 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);729 expect(targetAccountBalance).to.be.equal(validTransferAmount);730 });731732 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {733 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);734 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);735736 const uniqueMultilocation = {737 V2: {738 parents: 1,739 interior: {740 X1: {741 Parachain: UNIQUE_CHAIN,742 },743 },744 },745 };746747 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(748 targetAccount.addressRaw,749 {750 Concrete: {751 parents: 1,752 interior: {753 X1: {754 Parachain: UNIQUE_CHAIN,755 },756 },757 },758 },759 testAmount,760 );761762 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(763 targetAccount.addressRaw,764 {765 Concrete: {766 parents: 0,767 interior: 'Here',768 },769 },770 testAmount,771 );772773 let maliciousXcmProgramFullIdSent: any;774 let maliciousXcmProgramHereIdSent: any;775 const maxWaitBlocks = 3;776777 // Try to trick Unique using full UNQ identification778 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {779 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);780781 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);782 });783784 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash785 && event.outcome.isUntrustedReserveLocation);786787 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);788 expect(accountBalance).to.be.equal(0n);789790 // Try to trick Unique using shortened UNQ identification791 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {792 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);793794 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);795 });796797 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash798 && event.outcome.isUntrustedReserveLocation);799800 accountBalance = await helper.balance.getSubstrate(targetAccount.address);801 expect(accountBalance).to.be.equal(0n);802 });803});804805describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {806 let alice: IKeyringPair;807 let randomAccount: IKeyringPair;808 let unqFees: bigint;809 let balanceUniqueTokenInit: bigint;810 let balanceUniqueTokenMiddle: bigint;811 let balanceUniqueTokenFinal: bigint;812 const maxWaitBlocks = 6;813814 const uniqueAssetId = {815 Concrete: {816 parents: 1,817 interior: {818 X1: {819 Parachain: UNIQUE_CHAIN,820 },821 },822 },823 };824825 before(async () => {826 await usingPlaygrounds(async (helper, privateKey) => {827 alice = await privateKey('//Alice');828 [randomAccount] = await helper.arrange.createAccounts([0n], alice);829830 // Set the default version to wrap the first message to other chains.831 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);832 });833834 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {835 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', [])).toJSON() as [])836 .map(nToBigInt).length != 0;837 /*838 Check whether the Unique token has been added839 to the whitelist, since an error will occur840 if it is added again.841 */842 if(!isWhitelisted) {843 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);844 }845846 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);847 });848849 await usingPlaygrounds(async (helper) => {850 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);851 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);852 });853 });854855 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {856857 const destination = {858 V2: {859 parents: 1,860 interior: {861 X1: {862 Parachain: POLKADEX_CHAIN,863 },864 },865 },866 };867868 const beneficiary = {869 V2: {870 parents: 0,871 interior: {872 X1: {873 AccountId32: {874 network: 'Any',875 id: randomAccount.addressRaw,876 },877 },878 },879 },880 };881882 const assets = {883 V2: [884 {885 id: {886 Concrete: {887 parents: 0,888 interior: 'Here',889 },890 },891 fun: {892 Fungible: TRANSFER_AMOUNT,893 },894 },895 ],896 };897898 const feeAssetItem = 0;899900 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');901 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);902 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);903904 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;905 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));906 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;907908 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {909 /*910 Since only the parachain part of the Polkadex911 infrastructure is launched (without their912 solochain validators), processing incoming913 assets will lead to an error.914 This error indicates that the Polkadex chain915 received a message from the Unique network,916 since the hash is being checked to ensure917 it matches what was sent.918 */919 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);920 });921 });922923924 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {925926 const uniqueMultilocation = {927 V2: {928 parents: 1,929 interior: {930 X1: {Parachain: UNIQUE_CHAIN},931 },932 },933 };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 uniqueMultilocation = {973 V2: {974 parents: 1,975 interior: {976 X1: {Parachain: UNIQUE_CHAIN},977 },978 },979 };980981 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(982 targetAccount.addressRaw,983 {984 Concrete: {985 parents: 0,986 interior: 'Here',987 },988 },989 moreThanPolkadexHas,990 );991992 let maliciousXcmProgramSent: any;993994995 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {996 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);997998 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);999 });10001001 await expectFailedToTransact(helper, maliciousXcmProgramSent);10021003 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1004 expect(targetAccountBalance).to.be.equal(0n);1005 });10061007 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {1008 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1009 const targetAccount = helper.arrange.createEmptyAccount();10101011 const uniqueMultilocation = {1012 V2: {1013 parents: 1,1014 interior: {1015 X1: {1016 Parachain: UNIQUE_CHAIN,1017 },1018 },1019 },1020 };10211022 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1023 targetAccount.addressRaw,1024 {1025 Concrete: {1026 parents: 1,1027 interior: {1028 X1: {1029 Parachain: UNIQUE_CHAIN,1030 },1031 },1032 },1033 },1034 testAmount,1035 );10361037 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1038 targetAccount.addressRaw,1039 {1040 Concrete: {1041 parents: 0,1042 interior: 'Here',1043 },1044 },1045 testAmount,1046 );10471048 let maliciousXcmProgramFullIdSent: any;1049 let maliciousXcmProgramHereIdSent: any;1050 const maxWaitBlocks = 3;10511052 // Try to trick Unique using full UNQ identification1053 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1054 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10551056 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1057 });10581059 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10601061 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1062 expect(accountBalance).to.be.equal(0n);10631064 // Try to trick Unique using shortened UNQ identification1065 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1066 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10671068 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1069 });10701071 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10721073 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1074 expect(accountBalance).to.be.equal(0n);1075 });1076});10771078// These tests are relevant only when1079// the the corresponding foreign assets are not registered1080describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1081 let alice: IKeyringPair;1082 let alith: IKeyringPair;10831084 const testAmount = 100_000_000_000n;1085 let uniqueParachainJunction;1086 let uniqueAccountJunction;10871088 let uniqueParachainMultilocation: any;1089 let uniqueAccountMultilocation: any;1090 let uniqueCombinedMultilocation: any;1091 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210921093 let messageSent: any;10941095 const maxWaitBlocks = 3;10961097 before(async () => {1098 await usingPlaygrounds(async (helper, privateKey) => {1099 alice = await privateKey('//Alice');11001101 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1102 uniqueAccountJunction = {1103 AccountId32: {1104 network: 'Any',1105 id: alice.addressRaw,1106 },1107 };11081109 uniqueParachainMultilocation = {1110 V2: {1111 parents: 1,1112 interior: {1113 X1: uniqueParachainJunction,1114 },1115 },1116 };11171118 uniqueAccountMultilocation = {1119 V2: {1120 parents: 0,1121 interior: {1122 X1: uniqueAccountJunction,1123 },1124 },1125 };11261127 uniqueCombinedMultilocation = {1128 V2: {1129 parents: 1,1130 interior: {1131 X2: [uniqueParachainJunction, uniqueAccountJunction],1132 },1133 },1134 };11351136 uniqueCombinedMultilocationAcala = {1137 V2: {1138 parents: 1,1139 interior: {1140 X2: [uniqueParachainJunction, uniqueAccountJunction],1141 },1142 },1143 };11441145 // Set the default version to wrap the first message to other chains.1146 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1147 });11481149 // eslint-disable-next-line require-await1150 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1151 alith = helper.account.alithAccount();1152 });1153 });1154115511561157 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1158 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1159 const id = {1160 Token: 'ACA',1161 };1162 const destination = uniqueCombinedMultilocationAcala;1163 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11641165 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1166 });11671168 await expectFailedToTransact(helper, messageSent);1169 });11701171 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1172 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1173 const id = 'SelfReserve';1174 const destination = uniqueCombinedMultilocation;1175 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11761177 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1178 });11791180 await expectFailedToTransact(helper, messageSent);1181 });11821183 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1184 await usingAstarPlaygrounds(astarUrl, async (helper) => {1185 const destinationParachain = uniqueParachainMultilocation;1186 const beneficiary = uniqueAccountMultilocation;1187 const assets = {1188 V2: [{1189 id: {1190 Concrete: {1191 parents: 0,1192 interior: 'Here',1193 },1194 },1195 fun: {1196 Fungible: testAmount,1197 },1198 }],1199 };1200 const feeAssetItem = 0;12011202 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1203 destinationParachain,1204 beneficiary,1205 assets,1206 feeAssetItem,1207 ]);12081209 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1210 });12111212 await expectFailedToTransact(helper, messageSent);1213 });12141215 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12161217 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1218 helper.arrange.createEmptyAccount().addressRaw,1219 {1220 Concrete: {1221 parents: 1,1222 interior: {1223 X1: {1224 Parachain: POLKADEX_CHAIN,1225 },1226 },1227 },1228 },1229 testAmount,1230 );12311232 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1233 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1234 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1235 });12361237 await expectFailedToTransact(helper, messageSent);1238 });1239});12401241describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1242 // Unique constants1243 let alice: IKeyringPair;1244 let uniqueAssetLocation;12451246 let randomAccountUnique: IKeyringPair;1247 let randomAccountMoonbeam: IKeyringPair;12481249 // Moonbeam constants1250 let assetId: string;12511252 const uniqueAssetMetadata = {1253 name: 'xcUnique',1254 symbol: 'xcUNQ',1255 decimals: 18,1256 isFrozen: false,1257 minimalBalance: 1n,1258 };12591260 let balanceUniqueTokenInit: bigint;1261 let balanceUniqueTokenMiddle: bigint;1262 let balanceUniqueTokenFinal: bigint;1263 let balanceForeignUnqTokenInit: bigint;1264 let balanceForeignUnqTokenMiddle: bigint;1265 let balanceForeignUnqTokenFinal: bigint;1266 let balanceGlmrTokenInit: bigint;1267 let balanceGlmrTokenMiddle: bigint;1268 let balanceGlmrTokenFinal: bigint;12691270 before(async () => {1271 await usingPlaygrounds(async (helper, privateKey) => {1272 alice = await privateKey('//Alice');1273 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12741275 balanceForeignUnqTokenInit = 0n;12761277 // Set the default version to wrap the first message to other chains.1278 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1279 });12801281 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1282 const alithAccount = helper.account.alithAccount();1283 const baltatharAccount = helper.account.baltatharAccount();1284 const dorothyAccount = helper.account.dorothyAccount();12851286 randomAccountMoonbeam = helper.account.create();12871288 // >>> Sponsoring Dorothy >>>1289 console.log('Sponsoring Dorothy.......');1290 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1291 console.log('Sponsoring Dorothy.......DONE');1292 // <<< Sponsoring Dorothy <<<12931294 uniqueAssetLocation = {1295 XCM: {1296 parents: 1,1297 interior: {X1: {Parachain: UNIQUE_CHAIN}},1298 },1299 };1300 const existentialDeposit = 1n;1301 const isSufficient = true;1302 const unitsPerSecond = 1n;1303 const numAssetsWeightHint = 0;13041305 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1306 location: uniqueAssetLocation,1307 metadata: uniqueAssetMetadata,1308 existentialDeposit,1309 isSufficient,1310 unitsPerSecond,1311 numAssetsWeightHint,1312 });13131314 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);13151316 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);13171318 // >>> Acquire Unique AssetId Info on Moonbeam >>>1319 console.log('Acquire Unique AssetId Info on Moonbeam.......');13201321 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();13221323 console.log('UNQ asset ID is %s', assetId);1324 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1325 // >>> Acquire Unique AssetId Info on Moonbeam >>>13261327 // >>> Sponsoring random Account >>>1328 console.log('Sponsoring random Account.......');1329 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1330 console.log('Sponsoring random Account.......DONE');1331 // <<< Sponsoring random Account <<<13321333 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1334 });13351336 await usingPlaygrounds(async (helper) => {1337 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1338 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1339 });1340 });13411342 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1343 const currencyId = {1344 NativeAssetId: 'Here',1345 };1346 const dest = {1347 V2: {1348 parents: 1,1349 interior: {1350 X2: [1351 {Parachain: MOONBEAM_CHAIN},1352 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1353 ],1354 },1355 },1356 };1357 const amount = TRANSFER_AMOUNT;13581359 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13601361 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1362 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13631364 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1365 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1366 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13671368 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1369 await helper.wait.newBlocks(3);13701371 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13721373 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1374 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1375 expect(glmrFees == 0n).to.be.true;13761377 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13781379 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1380 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1381 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1382 });1383 });13841385 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1386 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1387 const asset = {1388 V2: {1389 id: {1390 Concrete: {1391 parents: 1,1392 interior: {1393 X1: {Parachain: UNIQUE_CHAIN},1394 },1395 },1396 },1397 fun: {1398 Fungible: TRANSFER_AMOUNT,1399 },1400 },1401 };1402 const destination = {1403 V2: {1404 parents: 1,1405 interior: {1406 X2: [1407 {Parachain: UNIQUE_CHAIN},1408 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1409 ],1410 },1411 },1412 };14131414 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');14151416 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);14171418 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1419 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1420 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;14211422 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);14231424 expect(unqRandomAccountAsset).to.be.null;14251426 balanceForeignUnqTokenFinal = 0n;14271428 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1429 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1430 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1431 });14321433 await helper.wait.newBlocks(3);14341435 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1436 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1437 expect(actuallyDelivered > 0).to.be.true;14381439 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));14401441 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1442 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1443 expect(unqFees == 0n).to.be.true;1444 });14451446 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1447 // set Moonbeam's sovereign account's balance1448 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1449 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1450 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14511452 const moreThanMoonbeamHas = moonbeamBalance * 2n;14531454 let targetAccountBalance = 0n;1455 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14561457 const uniqueMultilocation = {1458 V2: {1459 parents: 1,1460 interior: {1461 X1: {Parachain: UNIQUE_CHAIN},1462 },1463 },1464 };14651466 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1467 targetAccount.addressRaw,1468 {1469 Concrete: {1470 parents: 0,1471 interior: 'Here',1472 },1473 },1474 moreThanMoonbeamHas,1475 );14761477 let maliciousXcmProgramSent: any;1478 const maxWaitBlocks = 3;14791480 // Try to trick Unique1481 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1482 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14831484 // Needed to bypass the call filter.1485 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1486 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14871488 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1489 });14901491 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1492 && event.outcome.isFailedToTransactAsset);14931494 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1495 expect(targetAccountBalance).to.be.equal(0n);14961497 // But Moonbeam still can send the correct amount1498 const validTransferAmount = moonbeamBalance / 2n;1499 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1500 targetAccount.addressRaw,1501 {1502 Concrete: {1503 parents: 0,1504 interior: 'Here',1505 },1506 },1507 validTransferAmount,1508 );15091510 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1511 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);15121513 // Needed to bypass the call filter.1514 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1515 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1516 });15171518 await helper.wait.newBlocks(maxWaitBlocks);15191520 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1521 expect(targetAccountBalance).to.be.equal(validTransferAmount);1522 });15231524 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1525 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1526 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15271528 const uniqueMultilocation = {1529 V2: {1530 parents: 1,1531 interior: {1532 X1: {1533 Parachain: UNIQUE_CHAIN,1534 },1535 },1536 },1537 };15381539 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1540 targetAccount.addressRaw,1541 {1542 Concrete: {1543 parents: 0,1544 interior: {1545 X1: {1546 Parachain: UNIQUE_CHAIN,1547 },1548 },1549 },1550 },1551 testAmount,1552 );15531554 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1555 targetAccount.addressRaw,1556 {1557 Concrete: {1558 parents: 0,1559 interior: 'Here',1560 },1561 },1562 testAmount,1563 );15641565 let maliciousXcmProgramFullIdSent: any;1566 let maliciousXcmProgramHereIdSent: any;1567 const maxWaitBlocks = 3;15681569 // Try to trick Unique using full UNQ identification1570 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1571 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15721573 // Needed to bypass the call filter.1574 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1575 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15761577 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1578 });15791580 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1581 && event.outcome.isUntrustedReserveLocation);15821583 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1584 expect(accountBalance).to.be.equal(0n);15851586 // Try to trick Unique using shortened UNQ identification1587 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1588 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15891590 // Needed to bypass the call filter.1591 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1592 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15931594 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1595 });15961597 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1598 && event.outcome.isUntrustedReserveLocation);15991600 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1601 expect(accountBalance).to.be.equal(0n);1602 });1603});16041605describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1606 let alice: IKeyringPair;1607 let randomAccount: IKeyringPair;16081609 const UNQ_ASSET_ID_ON_ASTAR = 1;1610 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;16111612 // Unique -> Astar1613 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1614 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1615 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1616 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens16171618 // Astar -> Unique1619 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1620 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ16211622 let balanceAfterUniqueToAstarXCM: bigint;16231624 before(async () => {1625 await usingPlaygrounds(async (helper, privateKey) => {1626 alice = await privateKey('//Alice');1627 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1628 console.log('randomAccount', randomAccount.address);16291630 // Set the default version to wrap the first message to other chains.1631 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1632 });16331634 await usingAstarPlaygrounds(astarUrl, async (helper) => {1635 console.log('1. Create foreign asset and metadata');1636 // TODO update metadata with values from production1637 await helper.assets.create(1638 alice,1639 UNQ_ASSET_ID_ON_ASTAR,1640 alice.address,1641 UNQ_MINIMAL_BALANCE_ON_ASTAR,1642 );16431644 await helper.assets.setMetadata(1645 alice,1646 UNQ_ASSET_ID_ON_ASTAR,1647 'Cross chain UNQ',1648 'xcUNQ',1649 Number(UNQ_DECIMALS),1650 );16511652 console.log('2. Register asset location on Astar');1653 const assetLocation = {1654 V2: {1655 parents: 1,1656 interior: {1657 X1: {1658 Parachain: UNIQUE_CHAIN,1659 },1660 },1661 },1662 };16631664 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16651666 console.log('3. Set UNQ payment for XCM execution on Astar');1667 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);16681669 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1670 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1671 });1672 });16731674 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1675 const destination = {1676 V2: {1677 parents: 1,1678 interior: {1679 X1: {1680 Parachain: ASTAR_CHAIN,1681 },1682 },1683 },1684 };16851686 const beneficiary = {1687 V2: {1688 parents: 0,1689 interior: {1690 X1: {1691 AccountId32: {1692 network: 'Any',1693 id: randomAccount.addressRaw,1694 },1695 },1696 },1697 },1698 };16991700 const assets = {1701 V2: [1702 {1703 id: {1704 Concrete: {1705 parents: 0,1706 interior: 'Here',1707 },1708 },1709 fun: {1710 Fungible: unqToAstarTransferred,1711 },1712 },1713 ],1714 };17151716 // Initial balance is 100 UNQ1717 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1718 console.log(`Initial balance is: ${balanceBefore}`);17191720 const feeAssetItem = 0;1721 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');17221723 // Balance after reserve transfer is less than 901724 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1725 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1726 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1727 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17281729 await usingAstarPlaygrounds(astarUrl, async (helper) => {1730 await helper.wait.newBlocks(3);1731 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1732 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17331734 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1735 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);17361737 expect(xcUNQbalance).to.eq(unqToAstarArrived);1738 // Astar balance does not changed1739 expect(astarBalance).to.eq(astarInitialBalance);1740 });1741 });17421743 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1744 await usingAstarPlaygrounds(astarUrl, async (helper) => {1745 const destination = {1746 V2: {1747 parents: 1,1748 interior: {1749 X1: {1750 Parachain: UNIQUE_CHAIN,1751 },1752 },1753 },1754 };17551756 const beneficiary = {1757 V2: {1758 parents: 0,1759 interior: {1760 X1: {1761 AccountId32: {1762 network: 'Any',1763 id: randomAccount.addressRaw,1764 },1765 },1766 },1767 },1768 };17691770 const assets = {1771 V2: [1772 {1773 id: {1774 Concrete: {1775 parents: 1,1776 interior: {1777 X1: {1778 Parachain: UNIQUE_CHAIN,1779 },1780 },1781 },1782 },1783 fun: {1784 Fungible: unqFromAstarTransfered,1785 },1786 },1787 ],1788 };17891790 // Initial balance is 1 ASTR1791 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1792 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1793 expect(balanceASTRbefore).to.eq(astarInitialBalance);17941795 const feeAssetItem = 0;1796 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1797 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17981799 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1800 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1801 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);18021803 // Assert: xcUNQ balance correctly decreased1804 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1805 // Assert: ASTR balance is 0.996...1806 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1807 });18081809 await helper.wait.newBlocks(3);1810 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1811 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1812 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1813 });18141815 itSub('Astar can send only up to its balance', async ({helper}) => {1816 // set Astar's sovereign account's balance1817 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1818 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1819 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);18201821 const moreThanAstarHas = astarBalance * 2n;18221823 let targetAccountBalance = 0n;1824 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18251826 const uniqueMultilocation = {1827 V2: {1828 parents: 1,1829 interior: {1830 X1: {Parachain: UNIQUE_CHAIN},1831 },1832 },1833 };18341835 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1836 targetAccount.addressRaw,1837 {1838 Concrete: {1839 parents: 0,1840 interior: 'Here',1841 },1842 },1843 moreThanAstarHas,1844 );18451846 let maliciousXcmProgramSent: any;1847 const maxWaitBlocks = 3;18481849 // Try to trick Unique1850 await usingAstarPlaygrounds(astarUrl, async (helper) => {1851 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18521853 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1854 });18551856 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1857 && event.outcome.isFailedToTransactAsset);18581859 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1860 expect(targetAccountBalance).to.be.equal(0n);18611862 // But Astar still can send the correct amount1863 const validTransferAmount = astarBalance / 2n;1864 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1865 targetAccount.addressRaw,1866 {1867 Concrete: {1868 parents: 0,1869 interior: 'Here',1870 },1871 },1872 validTransferAmount,1873 );18741875 await usingAstarPlaygrounds(astarUrl, async (helper) => {1876 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1877 });18781879 await helper.wait.newBlocks(maxWaitBlocks);18801881 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1882 expect(targetAccountBalance).to.be.equal(validTransferAmount);1883 });18841885 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1886 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1887 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18881889 const uniqueMultilocation = {1890 V2: {1891 parents: 1,1892 interior: {1893 X1: {1894 Parachain: UNIQUE_CHAIN,1895 },1896 },1897 },1898 };18991900 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1901 targetAccount.addressRaw,1902 {1903 Concrete: {1904 parents: 1,1905 interior: {1906 X1: {1907 Parachain: UNIQUE_CHAIN,1908 },1909 },1910 },1911 },1912 testAmount,1913 );19141915 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1916 targetAccount.addressRaw,1917 {1918 Concrete: {1919 parents: 0,1920 interior: 'Here',1921 },1922 },1923 testAmount,1924 );19251926 let maliciousXcmProgramFullIdSent: any;1927 let maliciousXcmProgramHereIdSent: any;1928 const maxWaitBlocks = 3;19291930 // Try to trick Unique using full UNQ identification1931 await usingAstarPlaygrounds(astarUrl, async (helper) => {1932 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);19331934 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1935 });19361937 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1938 && event.outcome.isUntrustedReserveLocation);19391940 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1941 expect(accountBalance).to.be.equal(0n);19421943 // Try to trick Unique using shortened UNQ identification1944 await usingAstarPlaygrounds(astarUrl, async (helper) => {1945 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19461947 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1948 });19491950 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1951 && event.outcome.isUntrustedReserveLocation);19521953 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1954 expect(accountBalance).to.be.equal(0n);1955 });1956});