difftreelog
Update tests/src/xcm/xcmUnique.test.ts
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', []))836 .toJSON() as [])837 .map(nToBigInt).length != 0;838 /*839 Check whether the Unique token has been added840 to the whitelist, since an error will occur841 if it is added again. Needed for debugging842 when this test is run multiple times.843 */844 if(!isWhitelisted) {845 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);846 }847848 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);849 });850851 await usingPlaygrounds(async (helper) => {852 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);853 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);854 });855 });856857 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {858859 const destination = {860 V2: {861 parents: 1,862 interior: {863 X1: {864 Parachain: POLKADEX_CHAIN,865 },866 },867 },868 };869870 const beneficiary = {871 V2: {872 parents: 0,873 interior: {874 X1: {875 AccountId32: {876 network: 'Any',877 id: randomAccount.addressRaw,878 },879 },880 },881 },882 };883884 const assets = {885 V2: [886 {887 id: {888 Concrete: {889 parents: 0,890 interior: 'Here',891 },892 },893 fun: {894 Fungible: TRANSFER_AMOUNT,895 },896 },897 ],898 };899900 const feeAssetItem = 0;901902 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');903 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);904 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);905906 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;907 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));908 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;909910 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {911 /*912 Since only the parachain part of the Polkadex913 infrastructure is launched (without their914 solochain validators), processing incoming915 assets will lead to an error.916 This error indicates that the Polkadex chain917 received a message from the Unique network,918 since the hash is being checked to ensure919 it matches what was sent.920 */921 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);922 });923 });924925926 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {927928 const uniqueMultilocation = {929 V2: {930 parents: 1,931 interior: {932 X1: {Parachain: UNIQUE_CHAIN},933 },934 },935 };936937 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(938 randomAccount.addressRaw,939 {940 Concrete: {941 parents: 1,942 interior: {943 X1: {Parachain: UNIQUE_CHAIN},944 },945 },946 },947 TRANSFER_AMOUNT,948 );949950 let xcmProgramSent: any;951952953 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {954 await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);955956 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);957 });958959 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);960961 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);962963 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);964 });965966 itSub('Polkadex can send only up to its balance', async ({helper}) => {967 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);968 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);969 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);970 const moreThanPolkadexHas = 2n * polkadexBalance;971972 const targetAccount = helper.arrange.createEmptyAccount();973974 const uniqueMultilocation = {975 V2: {976 parents: 1,977 interior: {978 X1: {Parachain: UNIQUE_CHAIN},979 },980 },981 };982983 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(984 targetAccount.addressRaw,985 {986 Concrete: {987 parents: 0,988 interior: 'Here',989 },990 },991 moreThanPolkadexHas,992 );993994 let maliciousXcmProgramSent: any;995996997 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {998 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);9991000 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1001 });10021003 await expectFailedToTransact(helper, maliciousXcmProgramSent);10041005 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1006 expect(targetAccountBalance).to.be.equal(0n);1007 });10081009 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {1010 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1011 const targetAccount = helper.arrange.createEmptyAccount();10121013 const uniqueMultilocation = {1014 V2: {1015 parents: 1,1016 interior: {1017 X1: {1018 Parachain: UNIQUE_CHAIN,1019 },1020 },1021 },1022 };10231024 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1025 targetAccount.addressRaw,1026 {1027 Concrete: {1028 parents: 1,1029 interior: {1030 X1: {1031 Parachain: UNIQUE_CHAIN,1032 },1033 },1034 },1035 },1036 testAmount,1037 );10381039 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1040 targetAccount.addressRaw,1041 {1042 Concrete: {1043 parents: 0,1044 interior: 'Here',1045 },1046 },1047 testAmount,1048 );10491050 let maliciousXcmProgramFullIdSent: any;1051 let maliciousXcmProgramHereIdSent: any;1052 const maxWaitBlocks = 3;10531054 // Try to trick Unique using full UNQ identification1055 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1056 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10571058 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1059 });10601061 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10621063 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1064 expect(accountBalance).to.be.equal(0n);10651066 // Try to trick Unique using shortened UNQ identification1067 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1068 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10691070 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1071 });10721073 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10741075 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1076 expect(accountBalance).to.be.equal(0n);1077 });1078});10791080// These tests are relevant only when1081// the the corresponding foreign assets are not registered1082describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1083 let alice: IKeyringPair;1084 let alith: IKeyringPair;10851086 const testAmount = 100_000_000_000n;1087 let uniqueParachainJunction;1088 let uniqueAccountJunction;10891090 let uniqueParachainMultilocation: any;1091 let uniqueAccountMultilocation: any;1092 let uniqueCombinedMultilocation: any;1093 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210941095 let messageSent: any;10961097 const maxWaitBlocks = 3;10981099 before(async () => {1100 await usingPlaygrounds(async (helper, privateKey) => {1101 alice = await privateKey('//Alice');11021103 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1104 uniqueAccountJunction = {1105 AccountId32: {1106 network: 'Any',1107 id: alice.addressRaw,1108 },1109 };11101111 uniqueParachainMultilocation = {1112 V2: {1113 parents: 1,1114 interior: {1115 X1: uniqueParachainJunction,1116 },1117 },1118 };11191120 uniqueAccountMultilocation = {1121 V2: {1122 parents: 0,1123 interior: {1124 X1: uniqueAccountJunction,1125 },1126 },1127 };11281129 uniqueCombinedMultilocation = {1130 V2: {1131 parents: 1,1132 interior: {1133 X2: [uniqueParachainJunction, uniqueAccountJunction],1134 },1135 },1136 };11371138 uniqueCombinedMultilocationAcala = {1139 V2: {1140 parents: 1,1141 interior: {1142 X2: [uniqueParachainJunction, uniqueAccountJunction],1143 },1144 },1145 };11461147 // Set the default version to wrap the first message to other chains.1148 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1149 });11501151 // eslint-disable-next-line require-await1152 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1153 alith = helper.account.alithAccount();1154 });1155 });1156115711581159 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1160 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1161 const id = {1162 Token: 'ACA',1163 };1164 const destination = uniqueCombinedMultilocationAcala;1165 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11661167 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1168 });11691170 await expectFailedToTransact(helper, messageSent);1171 });11721173 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1174 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1175 const id = 'SelfReserve';1176 const destination = uniqueCombinedMultilocation;1177 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11781179 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1180 });11811182 await expectFailedToTransact(helper, messageSent);1183 });11841185 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1186 await usingAstarPlaygrounds(astarUrl, async (helper) => {1187 const destinationParachain = uniqueParachainMultilocation;1188 const beneficiary = uniqueAccountMultilocation;1189 const assets = {1190 V2: [{1191 id: {1192 Concrete: {1193 parents: 0,1194 interior: 'Here',1195 },1196 },1197 fun: {1198 Fungible: testAmount,1199 },1200 }],1201 };1202 const feeAssetItem = 0;12031204 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1205 destinationParachain,1206 beneficiary,1207 assets,1208 feeAssetItem,1209 ]);12101211 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1212 });12131214 await expectFailedToTransact(helper, messageSent);1215 });12161217 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12181219 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1220 helper.arrange.createEmptyAccount().addressRaw,1221 {1222 Concrete: {1223 parents: 1,1224 interior: {1225 X1: {1226 Parachain: POLKADEX_CHAIN,1227 },1228 },1229 },1230 },1231 testAmount,1232 );12331234 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1235 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1236 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1237 });12381239 await expectFailedToTransact(helper, messageSent);1240 });1241});12421243describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1244 // Unique constants1245 let alice: IKeyringPair;1246 let uniqueAssetLocation;12471248 let randomAccountUnique: IKeyringPair;1249 let randomAccountMoonbeam: IKeyringPair;12501251 // Moonbeam constants1252 let assetId: string;12531254 const uniqueAssetMetadata = {1255 name: 'xcUnique',1256 symbol: 'xcUNQ',1257 decimals: 18,1258 isFrozen: false,1259 minimalBalance: 1n,1260 };12611262 let balanceUniqueTokenInit: bigint;1263 let balanceUniqueTokenMiddle: bigint;1264 let balanceUniqueTokenFinal: bigint;1265 let balanceForeignUnqTokenInit: bigint;1266 let balanceForeignUnqTokenMiddle: bigint;1267 let balanceForeignUnqTokenFinal: bigint;1268 let balanceGlmrTokenInit: bigint;1269 let balanceGlmrTokenMiddle: bigint;1270 let balanceGlmrTokenFinal: bigint;12711272 before(async () => {1273 await usingPlaygrounds(async (helper, privateKey) => {1274 alice = await privateKey('//Alice');1275 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12761277 balanceForeignUnqTokenInit = 0n;12781279 // Set the default version to wrap the first message to other chains.1280 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1281 });12821283 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1284 const alithAccount = helper.account.alithAccount();1285 const baltatharAccount = helper.account.baltatharAccount();1286 const dorothyAccount = helper.account.dorothyAccount();12871288 randomAccountMoonbeam = helper.account.create();12891290 // >>> Sponsoring Dorothy >>>1291 console.log('Sponsoring Dorothy.......');1292 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1293 console.log('Sponsoring Dorothy.......DONE');1294 // <<< Sponsoring Dorothy <<<12951296 uniqueAssetLocation = {1297 XCM: {1298 parents: 1,1299 interior: {X1: {Parachain: UNIQUE_CHAIN}},1300 },1301 };1302 const existentialDeposit = 1n;1303 const isSufficient = true;1304 const unitsPerSecond = 1n;1305 const numAssetsWeightHint = 0;13061307 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1308 location: uniqueAssetLocation,1309 metadata: uniqueAssetMetadata,1310 existentialDeposit,1311 isSufficient,1312 unitsPerSecond,1313 numAssetsWeightHint,1314 });13151316 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);13171318 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);13191320 // >>> Acquire Unique AssetId Info on Moonbeam >>>1321 console.log('Acquire Unique AssetId Info on Moonbeam.......');13221323 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();13241325 console.log('UNQ asset ID is %s', assetId);1326 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1327 // >>> Acquire Unique AssetId Info on Moonbeam >>>13281329 // >>> Sponsoring random Account >>>1330 console.log('Sponsoring random Account.......');1331 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1332 console.log('Sponsoring random Account.......DONE');1333 // <<< Sponsoring random Account <<<13341335 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1336 });13371338 await usingPlaygrounds(async (helper) => {1339 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1340 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1341 });1342 });13431344 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1345 const currencyId = {1346 NativeAssetId: 'Here',1347 };1348 const dest = {1349 V2: {1350 parents: 1,1351 interior: {1352 X2: [1353 {Parachain: MOONBEAM_CHAIN},1354 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1355 ],1356 },1357 },1358 };1359 const amount = TRANSFER_AMOUNT;13601361 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13621363 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1364 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13651366 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1367 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1368 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13691370 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1371 await helper.wait.newBlocks(3);13721373 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13741375 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1376 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1377 expect(glmrFees == 0n).to.be.true;13781379 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13801381 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1382 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1383 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1384 });1385 });13861387 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1388 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1389 const asset = {1390 V2: {1391 id: {1392 Concrete: {1393 parents: 1,1394 interior: {1395 X1: {Parachain: UNIQUE_CHAIN},1396 },1397 },1398 },1399 fun: {1400 Fungible: TRANSFER_AMOUNT,1401 },1402 },1403 };1404 const destination = {1405 V2: {1406 parents: 1,1407 interior: {1408 X2: [1409 {Parachain: UNIQUE_CHAIN},1410 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1411 ],1412 },1413 },1414 };14151416 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');14171418 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);14191420 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1421 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1422 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;14231424 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);14251426 expect(unqRandomAccountAsset).to.be.null;14271428 balanceForeignUnqTokenFinal = 0n;14291430 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1431 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1432 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1433 });14341435 await helper.wait.newBlocks(3);14361437 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1438 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1439 expect(actuallyDelivered > 0).to.be.true;14401441 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));14421443 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1444 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1445 expect(unqFees == 0n).to.be.true;1446 });14471448 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1449 // set Moonbeam's sovereign account's balance1450 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1451 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1452 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14531454 const moreThanMoonbeamHas = moonbeamBalance * 2n;14551456 let targetAccountBalance = 0n;1457 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14581459 const uniqueMultilocation = {1460 V2: {1461 parents: 1,1462 interior: {1463 X1: {Parachain: UNIQUE_CHAIN},1464 },1465 },1466 };14671468 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1469 targetAccount.addressRaw,1470 {1471 Concrete: {1472 parents: 0,1473 interior: 'Here',1474 },1475 },1476 moreThanMoonbeamHas,1477 );14781479 let maliciousXcmProgramSent: any;1480 const maxWaitBlocks = 3;14811482 // Try to trick Unique1483 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1484 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14851486 // Needed to bypass the call filter.1487 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1488 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14891490 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1491 });14921493 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1494 && event.outcome.isFailedToTransactAsset);14951496 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1497 expect(targetAccountBalance).to.be.equal(0n);14981499 // But Moonbeam still can send the correct amount1500 const validTransferAmount = moonbeamBalance / 2n;1501 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1502 targetAccount.addressRaw,1503 {1504 Concrete: {1505 parents: 0,1506 interior: 'Here',1507 },1508 },1509 validTransferAmount,1510 );15111512 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1513 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);15141515 // Needed to bypass the call filter.1516 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1517 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1518 });15191520 await helper.wait.newBlocks(maxWaitBlocks);15211522 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1523 expect(targetAccountBalance).to.be.equal(validTransferAmount);1524 });15251526 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1527 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1528 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15291530 const uniqueMultilocation = {1531 V2: {1532 parents: 1,1533 interior: {1534 X1: {1535 Parachain: UNIQUE_CHAIN,1536 },1537 },1538 },1539 };15401541 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1542 targetAccount.addressRaw,1543 {1544 Concrete: {1545 parents: 0,1546 interior: {1547 X1: {1548 Parachain: UNIQUE_CHAIN,1549 },1550 },1551 },1552 },1553 testAmount,1554 );15551556 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1557 targetAccount.addressRaw,1558 {1559 Concrete: {1560 parents: 0,1561 interior: 'Here',1562 },1563 },1564 testAmount,1565 );15661567 let maliciousXcmProgramFullIdSent: any;1568 let maliciousXcmProgramHereIdSent: any;1569 const maxWaitBlocks = 3;15701571 // Try to trick Unique using full UNQ identification1572 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1573 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15741575 // Needed to bypass the call filter.1576 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1577 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15781579 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1580 });15811582 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1583 && event.outcome.isUntrustedReserveLocation);15841585 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1586 expect(accountBalance).to.be.equal(0n);15871588 // Try to trick Unique using shortened UNQ identification1589 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1590 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15911592 // Needed to bypass the call filter.1593 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1594 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15951596 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1597 });15981599 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1600 && event.outcome.isUntrustedReserveLocation);16011602 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1603 expect(accountBalance).to.be.equal(0n);1604 });1605});16061607describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1608 let alice: IKeyringPair;1609 let randomAccount: IKeyringPair;16101611 const UNQ_ASSET_ID_ON_ASTAR = 1;1612 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;16131614 // Unique -> Astar1615 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1616 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1617 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1618 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens16191620 // Astar -> Unique1621 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1622 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ16231624 let balanceAfterUniqueToAstarXCM: bigint;16251626 before(async () => {1627 await usingPlaygrounds(async (helper, privateKey) => {1628 alice = await privateKey('//Alice');1629 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1630 console.log('randomAccount', randomAccount.address);16311632 // Set the default version to wrap the first message to other chains.1633 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1634 });16351636 await usingAstarPlaygrounds(astarUrl, async (helper) => {1637 console.log('1. Create foreign asset and metadata');1638 // TODO update metadata with values from production1639 await helper.assets.create(1640 alice,1641 UNQ_ASSET_ID_ON_ASTAR,1642 alice.address,1643 UNQ_MINIMAL_BALANCE_ON_ASTAR,1644 );16451646 await helper.assets.setMetadata(1647 alice,1648 UNQ_ASSET_ID_ON_ASTAR,1649 'Cross chain UNQ',1650 'xcUNQ',1651 Number(UNQ_DECIMALS),1652 );16531654 console.log('2. Register asset location on Astar');1655 const assetLocation = {1656 V2: {1657 parents: 1,1658 interior: {1659 X1: {1660 Parachain: UNIQUE_CHAIN,1661 },1662 },1663 },1664 };16651666 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16671668 console.log('3. Set UNQ payment for XCM execution on Astar');1669 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);16701671 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1672 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1673 });1674 });16751676 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1677 const destination = {1678 V2: {1679 parents: 1,1680 interior: {1681 X1: {1682 Parachain: ASTAR_CHAIN,1683 },1684 },1685 },1686 };16871688 const beneficiary = {1689 V2: {1690 parents: 0,1691 interior: {1692 X1: {1693 AccountId32: {1694 network: 'Any',1695 id: randomAccount.addressRaw,1696 },1697 },1698 },1699 },1700 };17011702 const assets = {1703 V2: [1704 {1705 id: {1706 Concrete: {1707 parents: 0,1708 interior: 'Here',1709 },1710 },1711 fun: {1712 Fungible: unqToAstarTransferred,1713 },1714 },1715 ],1716 };17171718 // Initial balance is 100 UNQ1719 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1720 console.log(`Initial balance is: ${balanceBefore}`);17211722 const feeAssetItem = 0;1723 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');17241725 // Balance after reserve transfer is less than 901726 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1727 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1728 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1729 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17301731 await usingAstarPlaygrounds(astarUrl, async (helper) => {1732 await helper.wait.newBlocks(3);1733 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1734 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17351736 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1737 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);17381739 expect(xcUNQbalance).to.eq(unqToAstarArrived);1740 // Astar balance does not changed1741 expect(astarBalance).to.eq(astarInitialBalance);1742 });1743 });17441745 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1746 await usingAstarPlaygrounds(astarUrl, async (helper) => {1747 const destination = {1748 V2: {1749 parents: 1,1750 interior: {1751 X1: {1752 Parachain: UNIQUE_CHAIN,1753 },1754 },1755 },1756 };17571758 const beneficiary = {1759 V2: {1760 parents: 0,1761 interior: {1762 X1: {1763 AccountId32: {1764 network: 'Any',1765 id: randomAccount.addressRaw,1766 },1767 },1768 },1769 },1770 };17711772 const assets = {1773 V2: [1774 {1775 id: {1776 Concrete: {1777 parents: 1,1778 interior: {1779 X1: {1780 Parachain: UNIQUE_CHAIN,1781 },1782 },1783 },1784 },1785 fun: {1786 Fungible: unqFromAstarTransfered,1787 },1788 },1789 ],1790 };17911792 // Initial balance is 1 ASTR1793 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1794 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1795 expect(balanceASTRbefore).to.eq(astarInitialBalance);17961797 const feeAssetItem = 0;1798 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1799 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);18001801 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1802 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1803 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);18041805 // Assert: xcUNQ balance correctly decreased1806 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1807 // Assert: ASTR balance is 0.996...1808 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1809 });18101811 await helper.wait.newBlocks(3);1812 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1813 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1814 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1815 });18161817 itSub('Astar can send only up to its balance', async ({helper}) => {1818 // set Astar's sovereign account's balance1819 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1820 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1821 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);18221823 const moreThanAstarHas = astarBalance * 2n;18241825 let targetAccountBalance = 0n;1826 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18271828 const uniqueMultilocation = {1829 V2: {1830 parents: 1,1831 interior: {1832 X1: {Parachain: UNIQUE_CHAIN},1833 },1834 },1835 };18361837 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1838 targetAccount.addressRaw,1839 {1840 Concrete: {1841 parents: 0,1842 interior: 'Here',1843 },1844 },1845 moreThanAstarHas,1846 );18471848 let maliciousXcmProgramSent: any;1849 const maxWaitBlocks = 3;18501851 // Try to trick Unique1852 await usingAstarPlaygrounds(astarUrl, async (helper) => {1853 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18541855 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1856 });18571858 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1859 && event.outcome.isFailedToTransactAsset);18601861 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1862 expect(targetAccountBalance).to.be.equal(0n);18631864 // But Astar still can send the correct amount1865 const validTransferAmount = astarBalance / 2n;1866 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1867 targetAccount.addressRaw,1868 {1869 Concrete: {1870 parents: 0,1871 interior: 'Here',1872 },1873 },1874 validTransferAmount,1875 );18761877 await usingAstarPlaygrounds(astarUrl, async (helper) => {1878 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1879 });18801881 await helper.wait.newBlocks(maxWaitBlocks);18821883 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1884 expect(targetAccountBalance).to.be.equal(validTransferAmount);1885 });18861887 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1888 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1889 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18901891 const uniqueMultilocation = {1892 V2: {1893 parents: 1,1894 interior: {1895 X1: {1896 Parachain: UNIQUE_CHAIN,1897 },1898 },1899 },1900 };19011902 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1903 targetAccount.addressRaw,1904 {1905 Concrete: {1906 parents: 1,1907 interior: {1908 X1: {1909 Parachain: UNIQUE_CHAIN,1910 },1911 },1912 },1913 },1914 testAmount,1915 );19161917 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1918 targetAccount.addressRaw,1919 {1920 Concrete: {1921 parents: 0,1922 interior: 'Here',1923 },1924 },1925 testAmount,1926 );19271928 let maliciousXcmProgramFullIdSent: any;1929 let maliciousXcmProgramHereIdSent: any;1930 const maxWaitBlocks = 3;19311932 // Try to trick Unique using full UNQ identification1933 await usingAstarPlaygrounds(astarUrl, async (helper) => {1934 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);19351936 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1937 });19381939 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1940 && event.outcome.isUntrustedReserveLocation);19411942 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1943 expect(accountBalance).to.be.equal(0n);19441945 // Try to trick Unique using shortened UNQ identification1946 await usingAstarPlaygrounds(astarUrl, async (helper) => {1947 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19481949 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1950 });19511952 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1953 && event.outcome.isUntrustedReserveLocation);19541955 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1956 expect(accountBalance).to.be.equal(0n);1957 });1958});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', []))836 .toJSON() as [])837 .map(nToBigInt).length != 0;838 /*839 Check whether the Unique token has been added840 to the whitelist, since an error will occur841 if it is added again. Needed for debugging842 when this test is run multiple times.843 */844 if(!isWhitelisted) {845 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);846 }847848 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);849 });850851 await usingPlaygrounds(async (helper) => {852 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);853 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);854 });855 });856857 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {858859 const destination = {860 V2: {861 parents: 1,862 interior: {863 X1: {864 Parachain: POLKADEX_CHAIN,865 },866 },867 },868 };869870 const beneficiary = {871 V2: {872 parents: 0,873 interior: {874 X1: {875 AccountId32: {876 network: 'Any',877 id: randomAccount.addressRaw,878 },879 },880 },881 },882 };883884 const assets = {885 V2: [886 {887 id: {888 Concrete: {889 parents: 0,890 interior: 'Here',891 },892 },893 fun: {894 Fungible: TRANSFER_AMOUNT,895 },896 },897 ],898 };899900 const feeAssetItem = 0;901902 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');903 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);904 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);905906 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;907 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));908 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;909910 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {911 /*912 Since only the parachain part of the Polkadex913 infrastructure is launched (without their914 solochain validators), processing incoming915 assets will lead to an error.916 This error indicates that the Polkadex chain917 received a message from the Unique network,918 since the hash is being checked to ensure919 it matches what was sent.920 */921 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);922 });923 });924925926 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {927928 const uniqueMultilocation = {929 V2: {930 parents: 1,931 interior: {932 X1: {Parachain: UNIQUE_CHAIN},933 },934 },935 };936937 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(938 randomAccount.addressRaw,939 {940 Concrete: {941 parents: 1,942 interior: {943 X1: {Parachain: UNIQUE_CHAIN},944 },945 },946 },947 TRANSFER_AMOUNT,948 );949950 let xcmProgramSent: any;951952953 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {954 await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);955956 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);957 });958959 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);960961 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);962963 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);964 });965966 itSub('Polkadex can send only up to its balance', async ({helper}) => {967 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);968 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);969 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);970 const moreThanPolkadexHas = 2n * polkadexBalance;971972 const targetAccount = helper.arrange.createEmptyAccount();973974 const uniqueMultilocation = {975 V2: {976 parents: 1,977 interior: {978 X1: {Parachain: UNIQUE_CHAIN},979 },980 },981 };982983 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(984 targetAccount.addressRaw,985 {986 Concrete: {987 parents: 0,988 interior: 'Here',989 },990 },991 moreThanPolkadexHas,992 );993994 let maliciousXcmProgramSent: any;995996997 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {998 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);9991000 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1001 });10021003 await expectFailedToTransact(helper, maliciousXcmProgramSent);10041005 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1006 expect(targetAccountBalance).to.be.equal(0n);1007 });10081009 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {1010 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1011 const targetAccount = helper.arrange.createEmptyAccount();10121013 const uniqueMultilocation = {1014 V2: {1015 parents: 1,1016 interior: {1017 X1: {1018 Parachain: UNIQUE_CHAIN,1019 },1020 },1021 },1022 };10231024 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1025 targetAccount.addressRaw,1026 {1027 Concrete: {1028 parents: 1,1029 interior: {1030 X1: {1031 Parachain: UNIQUE_CHAIN,1032 },1033 },1034 },1035 },1036 testAmount,1037 );10381039 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1040 targetAccount.addressRaw,1041 {1042 Concrete: {1043 parents: 0,1044 interior: 'Here',1045 },1046 },1047 testAmount,1048 );10491050 let maliciousXcmProgramFullIdSent: any;1051 let maliciousXcmProgramHereIdSent: any;1052 const maxWaitBlocks = 3;10531054 // Try to trick Unique using full UNQ identification1055 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1056 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10571058 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1059 });10601061 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10621063 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1064 expect(accountBalance).to.be.equal(0n);10651066 // Try to trick Unique using shortened UNQ identification1067 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1068 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10691070 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1071 });10721073 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10741075 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1076 expect(accountBalance).to.be.equal(0n);1077 });1078});10791080// These tests are relevant only when1081// the the corresponding foreign assets are not registered1082describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1083 let alice: IKeyringPair;1084 let alith: IKeyringPair;10851086 const testAmount = 100_000_000_000n;1087 let uniqueParachainJunction;1088 let uniqueAccountJunction;10891090 let uniqueParachainMultilocation: any;1091 let uniqueAccountMultilocation: any;1092 let uniqueCombinedMultilocation: any;1093 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210941095 let messageSent: any;10961097 const maxWaitBlocks = 3;10981099 before(async () => {1100 await usingPlaygrounds(async (helper, privateKey) => {1101 alice = await privateKey('//Alice');11021103 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1104 uniqueAccountJunction = {1105 AccountId32: {1106 network: 'Any',1107 id: alice.addressRaw,1108 },1109 };11101111 uniqueParachainMultilocation = {1112 V2: {1113 parents: 1,1114 interior: {1115 X1: uniqueParachainJunction,1116 },1117 },1118 };11191120 uniqueAccountMultilocation = {1121 V2: {1122 parents: 0,1123 interior: {1124 X1: uniqueAccountJunction,1125 },1126 },1127 };11281129 uniqueCombinedMultilocation = {1130 V2: {1131 parents: 1,1132 interior: {1133 X2: [uniqueParachainJunction, uniqueAccountJunction],1134 },1135 },1136 };11371138 uniqueCombinedMultilocationAcala = {1139 V2: {1140 parents: 1,1141 interior: {1142 X2: [uniqueParachainJunction, uniqueAccountJunction],1143 },1144 },1145 };11461147 // Set the default version to wrap the first message to other chains.1148 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1149 });11501151 // eslint-disable-next-line require-await1152 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1153 alith = helper.account.alithAccount();1154 });1155 });1156115711581159 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1160 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1161 const id = {1162 Token: 'ACA',1163 };1164 const destination = uniqueCombinedMultilocationAcala;1165 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11661167 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1168 });11691170 await expectFailedToTransact(helper, messageSent);1171 });11721173 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1174 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1175 const id = 'SelfReserve';1176 const destination = uniqueCombinedMultilocation;1177 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11781179 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1180 });11811182 await expectFailedToTransact(helper, messageSent);1183 });11841185 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1186 await usingAstarPlaygrounds(astarUrl, async (helper) => {1187 const destinationParachain = uniqueParachainMultilocation;1188 const beneficiary = uniqueAccountMultilocation;1189 const assets = {1190 V2: [{1191 id: {1192 Concrete: {1193 parents: 0,1194 interior: 'Here',1195 },1196 },1197 fun: {1198 Fungible: testAmount,1199 },1200 }],1201 };1202 const feeAssetItem = 0;12031204 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1205 destinationParachain,1206 beneficiary,1207 assets,1208 feeAssetItem,1209 ]);12101211 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1212 });12131214 await expectFailedToTransact(helper, messageSent);1215 });12161217 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12181219 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1220 helper.arrange.createEmptyAccount().addressRaw,1221 {1222 Concrete: {1223 parents: 1,1224 interior: {1225 X1: {1226 Parachain: POLKADEX_CHAIN,1227 },1228 },1229 },1230 },1231 testAmount,1232 );12331234 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1235 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1236 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1237 });12381239 await expectFailedToTransact(helper, messageSent);1240 });1241});12421243describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1244 // Unique constants1245 let alice: IKeyringPair;1246 let uniqueAssetLocation;12471248 let randomAccountUnique: IKeyringPair;1249 let randomAccountMoonbeam: IKeyringPair;12501251 // Moonbeam constants1252 let assetId: string;12531254 const uniqueAssetMetadata = {1255 name: 'xcUnique',1256 symbol: 'xcUNQ',1257 decimals: 18,1258 isFrozen: false,1259 minimalBalance: 1n,1260 };12611262 let balanceUniqueTokenInit: bigint;1263 let balanceUniqueTokenMiddle: bigint;1264 let balanceUniqueTokenFinal: bigint;1265 let balanceForeignUnqTokenInit: bigint;1266 let balanceForeignUnqTokenMiddle: bigint;1267 let balanceForeignUnqTokenFinal: bigint;1268 let balanceGlmrTokenInit: bigint;1269 let balanceGlmrTokenMiddle: bigint;1270 let balanceGlmrTokenFinal: bigint;12711272 before(async () => {1273 await usingPlaygrounds(async (helper, privateKey) => {1274 alice = await privateKey('//Alice');1275 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12761277 balanceForeignUnqTokenInit = 0n;12781279 // Set the default version to wrap the first message to other chains.1280 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1281 });12821283 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1284 const alithAccount = helper.account.alithAccount();1285 const baltatharAccount = helper.account.baltatharAccount();1286 const dorothyAccount = helper.account.dorothyAccount();12871288 randomAccountMoonbeam = helper.account.create();12891290 // >>> Sponsoring Dorothy >>>1291 console.log('Sponsoring Dorothy.......');1292 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1293 console.log('Sponsoring Dorothy.......DONE');1294 // <<< Sponsoring Dorothy <<<12951296 uniqueAssetLocation = {1297 XCM: {1298 parents: 1,1299 interior: {X1: {Parachain: UNIQUE_CHAIN}},1300 },1301 };1302 const existentialDeposit = 1n;1303 const isSufficient = true;1304 const unitsPerSecond = 1n;1305 const numAssetsWeightHint = 0;13061307 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1308 location: uniqueAssetLocation,1309 metadata: uniqueAssetMetadata,1310 existentialDeposit,1311 isSufficient,1312 unitsPerSecond,1313 numAssetsWeightHint,1314 });13151316 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);13171318 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);13191320 // >>> Acquire Unique AssetId Info on Moonbeam >>>1321 console.log('Acquire Unique AssetId Info on Moonbeam.......');13221323 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();13241325 console.log('UNQ asset ID is %s', assetId);1326 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1327 // >>> Acquire Unique AssetId Info on Moonbeam >>>13281329 // >>> Sponsoring random Account >>>1330 console.log('Sponsoring random Account.......');1331 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1332 console.log('Sponsoring random Account.......DONE');1333 // <<< Sponsoring random Account <<<13341335 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1336 });13371338 await usingPlaygrounds(async (helper) => {1339 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1340 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1341 });1342 });13431344 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1345 const currencyId = {1346 NativeAssetId: 'Here',1347 };1348 const dest = {1349 V2: {1350 parents: 1,1351 interior: {1352 X2: [1353 {Parachain: MOONBEAM_CHAIN},1354 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1355 ],1356 },1357 },1358 };1359 const amount = TRANSFER_AMOUNT;13601361 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13621363 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1364 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13651366 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1367 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1368 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13691370 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1371 await helper.wait.newBlocks(3);13721373 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13741375 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1376 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1377 expect(glmrFees == 0n).to.be.true;13781379 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13801381 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1382 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1383 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1384 });1385 });13861387 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1388 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1389 const asset = {1390 V2: {1391 id: {1392 Concrete: {1393 parents: 1,1394 interior: {1395 X1: {Parachain: UNIQUE_CHAIN},1396 },1397 },1398 },1399 fun: {1400 Fungible: TRANSFER_AMOUNT,1401 },1402 },1403 };1404 const destination = {1405 V2: {1406 parents: 1,1407 interior: {1408 X2: [1409 {Parachain: UNIQUE_CHAIN},1410 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1411 ],1412 },1413 },1414 };14151416 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');14171418 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);14191420 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1421 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1422 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;14231424 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);14251426 expect(unqRandomAccountAsset).to.be.null;14271428 balanceForeignUnqTokenFinal = 0n;14291430 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1431 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1432 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1433 });14341435 await helper.wait.newBlocks(3);14361437 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1438 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1439 expect(actuallyDelivered > 0).to.be.true;14401441 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));14421443 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1444 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1445 expect(unqFees == 0n).to.be.true;1446 });14471448 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1449 // set Moonbeam's sovereign account's balance1450 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1451 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1452 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14531454 const moreThanMoonbeamHas = moonbeamBalance * 2n;14551456 let targetAccountBalance = 0n;1457 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14581459 const uniqueMultilocation = {1460 V2: {1461 parents: 1,1462 interior: {1463 X1: {Parachain: UNIQUE_CHAIN},1464 },1465 },1466 };14671468 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1469 targetAccount.addressRaw,1470 {1471 Concrete: {1472 parents: 0,1473 interior: 'Here',1474 },1475 },1476 moreThanMoonbeamHas,1477 );14781479 let maliciousXcmProgramSent: any;1480 const maxWaitBlocks = 3;14811482 // Try to trick Unique1483 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1484 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14851486 // Needed to bypass the call filter.1487 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1488 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14891490 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1491 });14921493 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1494 && event.outcome.isFailedToTransactAsset);14951496 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1497 expect(targetAccountBalance).to.be.equal(0n);14981499 // But Moonbeam still can send the correct amount1500 const validTransferAmount = moonbeamBalance / 2n;1501 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1502 targetAccount.addressRaw,1503 {1504 Concrete: {1505 parents: 0,1506 interior: 'Here',1507 },1508 },1509 validTransferAmount,1510 );15111512 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1513 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);15141515 // Needed to bypass the call filter.1516 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1517 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1518 });15191520 await helper.wait.newBlocks(maxWaitBlocks);15211522 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1523 expect(targetAccountBalance).to.be.equal(validTransferAmount);1524 });15251526 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1527 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1528 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15291530 const uniqueMultilocation = {1531 V2: {1532 parents: 1,1533 interior: {1534 X1: {1535 Parachain: UNIQUE_CHAIN,1536 },1537 },1538 },1539 };15401541 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1542 targetAccount.addressRaw,1543 {1544 Concrete: {1545 parents: 0,1546 interior: {1547 X1: {1548 Parachain: UNIQUE_CHAIN,1549 },1550 },1551 },1552 },1553 testAmount,1554 );15551556 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1557 targetAccount.addressRaw,1558 {1559 Concrete: {1560 parents: 0,1561 interior: 'Here',1562 },1563 },1564 testAmount,1565 );15661567 let maliciousXcmProgramFullIdSent: any;1568 let maliciousXcmProgramHereIdSent: any;1569 const maxWaitBlocks = 3;15701571 // Try to trick Unique using full UNQ identification1572 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1573 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15741575 // Needed to bypass the call filter.1576 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1577 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15781579 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1580 });15811582 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1583 && event.outcome.isUntrustedReserveLocation);15841585 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1586 expect(accountBalance).to.be.equal(0n);15871588 // Try to trick Unique using shortened UNQ identification1589 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1590 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15911592 // Needed to bypass the call filter.1593 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1594 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15951596 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1597 });15981599 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1600 && event.outcome.isUntrustedReserveLocation);16011602 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1603 expect(accountBalance).to.be.equal(0n);1604 });1605});16061607describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1608 let alice: IKeyringPair;1609 let randomAccount: IKeyringPair;16101611 const UNQ_ASSET_ID_ON_ASTAR = 1;1612 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;16131614 // Unique -> Astar1615 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1616 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1617 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1618 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens16191620 // Astar -> Unique1621 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1622 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ16231624 let balanceAfterUniqueToAstarXCM: bigint;16251626 before(async () => {1627 await usingPlaygrounds(async (helper, privateKey) => {1628 alice = await privateKey('//Alice');1629 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1630 console.log('randomAccount', randomAccount.address);16311632 // Set the default version to wrap the first message to other chains.1633 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1634 });16351636 await usingAstarPlaygrounds(astarUrl, async (helper) => {1637 console.log('1. Create foreign asset and metadata');1638 // TODO update metadata with values from production1639 await helper.assets.create(1640 alice,1641 UNQ_ASSET_ID_ON_ASTAR,1642 alice.address,1643 UNQ_MINIMAL_BALANCE_ON_ASTAR,1644 );16451646 await helper.assets.setMetadata(1647 alice,1648 UNQ_ASSET_ID_ON_ASTAR,1649 'Cross chain UNQ',1650 'xcUNQ',1651 Number(UNQ_DECIMALS),1652 );16531654 console.log('2. Register asset location on Astar');1655 const assetLocation = {1656 V2: {1657 parents: 1,1658 interior: {1659 X1: {1660 Parachain: UNIQUE_CHAIN,1661 },1662 },1663 },1664 };16651666 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16671668 console.log('3. Set UNQ payment for XCM execution on Astar');1669 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);16701671 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1672 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1673 });1674 });16751676 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1677 const destination = {1678 V2: {1679 parents: 1,1680 interior: {1681 X1: {1682 Parachain: ASTAR_CHAIN,1683 },1684 },1685 },1686 };16871688 const beneficiary = {1689 V2: {1690 parents: 0,1691 interior: {1692 X1: {1693 AccountId32: {1694 network: 'Any',1695 id: randomAccount.addressRaw,1696 },1697 },1698 },1699 },1700 };17011702 const assets = {1703 V2: [1704 {1705 id: {1706 Concrete: {1707 parents: 0,1708 interior: 'Here',1709 },1710 },1711 fun: {1712 Fungible: unqToAstarTransferred,1713 },1714 },1715 ],1716 };17171718 // Initial balance is 100 UNQ1719 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1720 console.log(`Initial balance is: ${balanceBefore}`);17211722 const feeAssetItem = 0;1723 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');17241725 // Balance after reserve transfer is less than 901726 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1727 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1728 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1729 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17301731 await usingAstarPlaygrounds(astarUrl, async (helper) => {1732 await helper.wait.newBlocks(3);1733 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1734 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17351736 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1737 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);17381739 expect(xcUNQbalance).to.eq(unqToAstarArrived);1740 // Astar balance does not changed1741 expect(astarBalance).to.eq(astarInitialBalance);1742 });1743 });17441745 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1746 await usingAstarPlaygrounds(astarUrl, async (helper) => {1747 const destination = {1748 V2: {1749 parents: 1,1750 interior: {1751 X1: {1752 Parachain: UNIQUE_CHAIN,1753 },1754 },1755 },1756 };17571758 const beneficiary = {1759 V2: {1760 parents: 0,1761 interior: {1762 X1: {1763 AccountId32: {1764 network: 'Any',1765 id: randomAccount.addressRaw,1766 },1767 },1768 },1769 },1770 };17711772 const assets = {1773 V2: [1774 {1775 id: {1776 Concrete: {1777 parents: 1,1778 interior: {1779 X1: {1780 Parachain: UNIQUE_CHAIN,1781 },1782 },1783 },1784 },1785 fun: {1786 Fungible: unqFromAstarTransfered,1787 },1788 },1789 ],1790 };17911792 // Initial balance is 1 ASTR1793 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1794 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1795 expect(balanceASTRbefore).to.eq(astarInitialBalance);17961797 const feeAssetItem = 0;1798 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1799 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);18001801 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1802 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1803 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);18041805 // Assert: xcUNQ balance correctly decreased1806 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1807 // Assert: ASTR balance is 0.996...1808 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1809 });18101811 await helper.wait.newBlocks(3);1812 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1813 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1814 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1815 });18161817 itSub('Astar can send only up to its balance', async ({helper}) => {1818 // set Astar's sovereign account's balance1819 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1820 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1821 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);18221823 const moreThanAstarHas = astarBalance * 2n;18241825 let targetAccountBalance = 0n;1826 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18271828 const uniqueMultilocation = {1829 V2: {1830 parents: 1,1831 interior: {1832 X1: {Parachain: UNIQUE_CHAIN},1833 },1834 },1835 };18361837 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1838 targetAccount.addressRaw,1839 {1840 Concrete: {1841 parents: 0,1842 interior: 'Here',1843 },1844 },1845 moreThanAstarHas,1846 );18471848 let maliciousXcmProgramSent: any;1849 const maxWaitBlocks = 3;18501851 // Try to trick Unique1852 await usingAstarPlaygrounds(astarUrl, async (helper) => {1853 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18541855 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1856 });18571858 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1859 && event.outcome.isFailedToTransactAsset);18601861 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1862 expect(targetAccountBalance).to.be.equal(0n);18631864 // But Astar still can send the correct amount1865 const validTransferAmount = astarBalance / 2n;1866 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1867 targetAccount.addressRaw,1868 {1869 Concrete: {1870 parents: 0,1871 interior: 'Here',1872 },1873 },1874 validTransferAmount,1875 );18761877 await usingAstarPlaygrounds(astarUrl, async (helper) => {1878 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1879 });18801881 await helper.wait.newBlocks(maxWaitBlocks);18821883 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1884 expect(targetAccountBalance).to.be.equal(validTransferAmount);1885 });18861887 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1888 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1889 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18901891 const uniqueMultilocation = {1892 V2: {1893 parents: 1,1894 interior: {1895 X1: {1896 Parachain: UNIQUE_CHAIN,1897 },1898 },1899 },1900 };19011902 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1903 targetAccount.addressRaw,1904 {1905 Concrete: {1906 parents: 1,1907 interior: {1908 X1: {1909 Parachain: UNIQUE_CHAIN,1910 },1911 },1912 },1913 },1914 testAmount,1915 );19161917 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1918 targetAccount.addressRaw,1919 {1920 Concrete: {1921 parents: 0,1922 interior: 'Here',1923 },1924 },1925 testAmount,1926 );19271928 let maliciousXcmProgramFullIdSent: any;1929 let maliciousXcmProgramHereIdSent: any;1930 const maxWaitBlocks = 3;19311932 // Try to trick Unique using full UNQ identification1933 await usingAstarPlaygrounds(astarUrl, async (helper) => {1934 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);19351936 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1937 });19381939 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1940 && event.outcome.isUntrustedReserveLocation);19411942 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1943 expect(accountBalance).to.be.equal(0n);19441945 // Try to trick Unique using shortened UNQ identification1946 await usingAstarPlaygrounds(astarUrl, async (helper) => {1947 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19481949 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1950 });19511952 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1953 && event.outcome.isUntrustedReserveLocation);19541955 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1956 expect(accountBalance).to.be.equal(0n);1957 });1958});