difftreelog
fix xcm tests
in: master
5 files changed
js-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -104,17 +104,17 @@
}
export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
- async register(signer: TSigner, location: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
+ async register(signer: TSigner, assetId: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
await this.helper.executeExtrinsic(
signer,
'api.tx.foreignAssets.forceRegisterForeignAsset',
- [location, this.helper.util.str2vec(name), tokenPrefix, mode],
+ [assetId, this.helper.util.str2vec(name), tokenPrefix, mode],
true,
);
}
- async foreignCollectionId(location: any) {
- return (await this.helper.callRpc('api.query.foreignAssets.foreignReserveLocationToCollection', [location])).toJSON();
+ async foreignCollectionId(assetId: any) {
+ return (await this.helper.callRpc('api.query.foreignAssets.foreignAssetToCollection', [assetId])).toJSON();
}
}
js-packages/tests/xcm/xcm.types.tsdiffbeforeafterboth--- a/js-packages/tests/xcm/xcm.types.ts
+++ b/js-packages/tests/xcm/xcm.types.ts
@@ -512,15 +512,16 @@
parents: 1,
interior: 'Here',
};
+ const relayAssetId = {Concrete: relayLocation};
- const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayLocation);
+ const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayAssetId);
if(relayCollectionId == null) {
const name = 'Relay Tokens';
const tokenPrefix = 'xDOT';
const decimals = 10;
- await helper.getSudo().foreignAssets.register(alice, relayLocation, name, tokenPrefix, {Fungible: decimals});
+ await helper.getSudo().foreignAssets.register(alice, relayAssetId, name, tokenPrefix, {Fungible: decimals});
- return await helper.foreignAssets.foreignCollectionId(relayLocation);
+ return await helper.foreignAssets.foreignCollectionId(relayAssetId);
} else {
console.log('Relay foreign collection is already registered');
return relayCollectionId;
js-packages/tests/xcm/xcmOpal.test.tsdiffbeforeafterboth--- a/js-packages/tests/xcm/xcmOpal.test.ts
+++ b/js-packages/tests/xcm/xcmOpal.test.ts
@@ -17,7 +17,7 @@
import type {IKeyringPair} from '@polkadot/types/types';
import config from '../config.js';
import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '../util/index.js';
-import {XcmTestHelper} from './xcm.types';
+import {XcmTestHelper} from './xcm.types.js';
const STATEMINE_CHAIN = +(process.env.RELAY_WESTMINT_ID || 1000);
const UNIQUE_CHAIN = +(process.env.RELAY_OPAL_ID || 2095);
@@ -120,16 +120,23 @@
},
]},
};
+ const assetId = {Concrete: location};
- if(await helper.foreignAssets.foreignCollectionId(location) == null) {
+ if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {
const tokenPrefix = USDT_ASSET_METADATA_NAME;
- await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});
+ await helper.getSudo().foreignAssets.register(
+ alice,
+ assetId,
+ USDT_ASSET_METADATA_NAME,
+ tokenPrefix,
+ {Fungible: USDT_ASSET_METADATA_DECIMALS},
+ );
} else {
console.log('Foreign collection is already registered on Opal');
}
balanceOpalBefore = await helper.balance.getSubstrate(alice.address);
- usdtCollectionId = await helper.foreignAssets.foreignCollectionId(location);
+ usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);
});
// Providing the relay currency to the unique sender account
js-packages/tests/xcm/xcmQuartz.test.tsdiffbeforeafterboth--- a/js-packages/tests/xcm/xcmQuartz.test.ts
+++ b/js-packages/tests/xcm/xcmQuartz.test.ts
@@ -19,7 +19,7 @@
import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';
import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types.js';
import {hexToString} from '@polkadot/util';
-import {XcmTestHelper} from './xcm.types';
+import {XcmTestHelper} from './xcm.types.js';
const STATEMINE_PALLET_INSTANCE = 50;
@@ -130,16 +130,17 @@
},
]},
};
+ const assetId = {Concrete: location};
- if(await helper.foreignAssets.foreignCollectionId(location) == null) {
+ if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {
const tokenPrefix = USDT_ASSET_METADATA_NAME;
- await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});
+ await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});
} else {
console.log('Foreign collection is already registered on Quartz');
}
balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);
- usdtCollectionId = await helper.foreignAssets.foreignCollectionId(location);
+ usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);
});
js-packages/tests/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 type {IKeyringPair} from '@polkadot/types/types';18import config from '../config.js';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util/index.js';20import {Event} from '@unique/playgrounds/unique.dev.js';21import {hexToString, nToBigInt} from '@polkadot/util';22import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js';23import {XcmTestHelper} from './xcm.types';2425const STATEMINT_PALLET_INSTANCE = 50;2627const relayUrl = config.relayUrl;28const statemintUrl = config.statemintUrl;29const acalaUrl = config.acalaUrl;30const moonbeamUrl = config.moonbeamUrl;31const astarUrl = config.astarUrl;32const polkadexUrl = config.polkadexUrl;3334const RELAY_DECIMALS = 12;35const STATEMINT_DECIMALS = 12;36const ACALA_DECIMALS = 12;37const ASTAR_DECIMALS = 18n;38const UNQ_DECIMALS = 18n;3940const TRANSFER_AMOUNT = 2000000000000000000000000n;4142const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4344const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4546const USDT_ASSET_ID = 100;47const USDT_ASSET_METADATA_DECIMALS = 18;48const USDT_ASSET_METADATA_NAME = 'USDT';49const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';50const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;51const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5253const testHelper = new XcmTestHelper('unique');5455describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {56 let alice: IKeyringPair;57 let bob: IKeyringPair;5859 let balanceStmnBefore: bigint;60 let balanceStmnAfter: bigint;6162 let balanceUniqueBefore: bigint;63 let balanceUniqueAfter: bigint;64 let balanceUniqueFinal: bigint;6566 let balanceBobBefore: bigint;67 let balanceBobAfter: bigint;68 let balanceBobFinal: bigint;6970 let balanceBobRelayTokenBefore: bigint;71 let balanceBobRelayTokenAfter: bigint;7273 let usdtCollectionId: number;74 let relayCollectionId: number;7576 before(async () => {77 await usingPlaygrounds(async (helper, privateKey) => {78 alice = await privateKey('//Alice');79 bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor8081 // Set the default version to wrap the first message to other chains.82 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);8384 relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);85 });8687 await usingRelayPlaygrounds(relayUrl, async (helper) => {88 // Fund accounts on Statemint89 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);90 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);91 });9293 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {94 const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);95 if(assetInfo == null) {96 await helper.assets.create(97 alice,98 USDT_ASSET_ID,99 alice.address,100 USDT_ASSET_METADATA_MINIMAL_BALANCE,101 );102 await helper.assets.setMetadata(103 alice,104 USDT_ASSET_ID,105 USDT_ASSET_METADATA_NAME,106 USDT_ASSET_METADATA_DESCRIPTION,107 USDT_ASSET_METADATA_DECIMALS,108 );109 } else {110 console.log('The USDT asset is already registered on AssetHub');111 }112113 await helper.assets.mint(114 alice,115 USDT_ASSET_ID,116 alice.address,117 USDT_ASSET_AMOUNT,118 );119120 const sovereignFundingAmount = 3_500_000_000n;121122 // funding parachain sovereing account on Statemint.123 // The sovereign account should be created before any action124 // (the assets pallet on Statemint check if the sovereign account exists)125 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);126 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);127 });128129130 await usingPlaygrounds(async (helper) => {131 const location = {132 parents: 1,133 interior: {X3: [134 {135 Parachain: STATEMINT_CHAIN,136 },137 {138 PalletInstance: STATEMINT_PALLET_INSTANCE,139 },140 {141 GeneralIndex: USDT_ASSET_ID,142 },143 ]},144 };145146 if(await helper.foreignAssets.foreignCollectionId(location) == null) {147 const tokenPrefix = USDT_ASSET_METADATA_NAME;148 await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});149 } else {150 console.log('Foreign collection is already registered on Unique');151 }152153 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);154 usdtCollectionId = await helper.foreignAssets.foreignCollectionId(location);155 });156157158 // Providing the relay currency to the unique sender account159 // (fee for USDT XCM are paid in relay tokens)160 await usingRelayPlaygrounds(relayUrl, async (helper) => {161 const destination = {162 V2: {163 parents: 0,164 interior: {X1: {165 Parachain: UNIQUE_CHAIN,166 },167 },168 }};169170 const beneficiary = {171 V2: {172 parents: 0,173 interior: {X1: {174 AccountId32: {175 network: 'Any',176 id: alice.addressRaw,177 },178 }},179 },180 };181182 const assets = {183 V2: [184 {185 id: {186 Concrete: {187 parents: 0,188 interior: 'Here',189 },190 },191 fun: {192 Fungible: TRANSFER_AMOUNT_RELAY,193 },194 },195 ],196 };197198 const feeAssetItem = 0;199200 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');201 });202203 });204205 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {206 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {207 const dest = {208 V2: {209 parents: 1,210 interior: {X1: {211 Parachain: UNIQUE_CHAIN,212 },213 },214 }};215216 const beneficiary = {217 V2: {218 parents: 0,219 interior: {X1: {220 AccountId32: {221 network: 'Any',222 id: alice.addressRaw,223 },224 }},225 },226 };227228 const assets = {229 V2: [230 {231 id: {232 Concrete: {233 parents: 0,234 interior: {235 X2: [236 {237 PalletInstance: STATEMINT_PALLET_INSTANCE,238 },239 {240 GeneralIndex: USDT_ASSET_ID,241 },242 ]},243 },244 },245 fun: {246 Fungible: TRANSFER_AMOUNT,247 },248 },249 ],250 };251252 const feeAssetItem = 0;253254 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);255 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');256257 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);258259 // common good parachain take commission in it native token260 console.log(261 '[Statemint -> Unique] transaction fees on Statemint: %s WND',262 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),263 );264 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;265266 });267268269 // ensure that asset has been delivered270 await helper.wait.newBlocks(3);271272 const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});273274 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);275276 console.log(277 '[Statemint -> Unique] transaction fees on Unique: %s USDT',278 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),279 );280 console.log(281 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',282 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),283 );284 // commission has not paid in USDT token285 expect(free).to.be.equal(TRANSFER_AMOUNT);286 // ... and parachain native token287 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;288 });289290 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {291 const destination = {292 V2: {293 parents: 1,294 interior: {X2: [295 {296 Parachain: STATEMINT_CHAIN,297 },298 {299 AccountId32: {300 network: 'Any',301 id: alice.addressRaw,302 },303 },304 ]},305 },306 };307308 const relayFee = 400_000_000_000_000n;309 const currencies: [any, bigint][] = [310 [311 usdtCollectionId,312 TRANSFER_AMOUNT,313 ],314 [315 relayCollectionId,316 relayFee,317 ],318 ];319320 const feeItem = 1;321322 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');323324 // the commission has been paid in parachain native token325 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);326 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));327 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;328329 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {330 await helper.wait.newBlocks(3);331332 // The USDT token never paid fees. Its amount not changed from begin value.333 // Also check that xcm transfer has been succeeded334 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;335 });336 });337338 itSub('Should connect and send Relay token to Unique', async ({helper}) => {339 balanceBobBefore = await helper.balance.getSubstrate(bob.address);340 balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});341342 await usingRelayPlaygrounds(relayUrl, async (helper) => {343 const destination = {344 V2: {345 parents: 0,346 interior: {X1: {347 Parachain: UNIQUE_CHAIN,348 },349 },350 }};351352 const beneficiary = {353 V2: {354 parents: 0,355 interior: {X1: {356 AccountId32: {357 network: 'Any',358 id: bob.addressRaw,359 },360 }},361 },362 };363364 const assets = {365 V2: [366 {367 id: {368 Concrete: {369 parents: 0,370 interior: 'Here',371 },372 },373 fun: {374 Fungible: TRANSFER_AMOUNT_RELAY,375 },376 },377 ],378 };379380 const feeAssetItem = 0;381382 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');383 });384385 await helper.wait.newBlocks(3);386387 balanceBobAfter = await helper.balance.getSubstrate(bob.address);388 balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});389390 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;391 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;392 console.log(393 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',394 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),395 );396 console.log(397 '[Relay (Westend) -> Unique] transaction fees: %s WND',398 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),399 );400 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);401 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;402 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;403 });404405 itSub('Should connect and send Relay token back', async ({helper}) => {406 let relayTokenBalanceBefore: bigint;407 let relayTokenBalanceAfter: bigint;408 await usingRelayPlaygrounds(relayUrl, async (helper) => {409 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);410 });411412 const destination = {413 V2: {414 parents: 1,415 interior: {416 X1:{417 AccountId32: {418 network: 'Any',419 id: bob.addressRaw,420 },421 },422 },423 },424 };425426 const currencies: any = [427 [428 relayCollectionId,429 TRANSFER_AMOUNT_RELAY,430 ],431 ];432433 const feeItem = 0;434435 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');436437 balanceBobFinal = await helper.balance.getSubstrate(bob.address);438 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));439440 await usingRelayPlaygrounds(relayUrl, async (helper) => {441 await helper.wait.newBlocks(10);442 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);443444 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;445 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));446 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;447 });448 });449});450451describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {452 let alice: IKeyringPair;453 let randomAccount: IKeyringPair;454455 let balanceUniqueTokenInit: bigint;456 let balanceUniqueTokenMiddle: bigint;457 let balanceUniqueTokenFinal: bigint;458 let balanceAcalaTokenInit: bigint;459 let balanceAcalaTokenMiddle: bigint;460 let balanceAcalaTokenFinal: bigint;461 let balanceUniqueForeignTokenInit: bigint;462 let balanceUniqueForeignTokenMiddle: bigint;463 let balanceUniqueForeignTokenFinal: bigint;464465 // computed by a test transfer from prod Unique to prod Acala.466 // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48467 // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)468 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;469 const acalaEps = 8n * 10n ** 16n;470471 let acalaBackwardTransferAmount: bigint;472473 before(async () => {474 await usingPlaygrounds(async (helper, privateKey) => {475 alice = await privateKey('//Alice');476 [randomAccount] = await helper.arrange.createAccounts([0n], alice);477478 // Set the default version to wrap the first message to other chains.479 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);480 });481482 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {483 const destination = {484 V2: {485 parents: 1,486 interior: {487 X1: {488 Parachain: UNIQUE_CHAIN,489 },490 },491 },492 };493494 const metadata = {495 name: 'Unique Network',496 symbol: 'UNQ',497 decimals: 18,498 minimalBalance: 1250000000000000000n,499 };500 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>501 hexToString(v.toJSON()['symbol'])) as string[];502503 if(!assets.includes('UNQ')) {504 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);505 } else {506 console.log('UNQ token already registered on Acala assetRegistry pallet');507 }508 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);509 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);510 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});511 });512513 await usingPlaygrounds(async (helper) => {514 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);515 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);516 });517 });518519 itSub('Should connect and send UNQ to Acala', async ({helper}) => {520521 const destination = {522 V2: {523 parents: 1,524 interior: {525 X1: {526 Parachain: ACALA_CHAIN,527 },528 },529 },530 };531532 const beneficiary = {533 V2: {534 parents: 0,535 interior: {536 X1: {537 AccountId32: {538 network: 'Any',539 id: randomAccount.addressRaw,540 },541 },542 },543 },544 };545546 const assets = {547 V2: [548 {549 id: {550 Concrete: {551 parents: 0,552 interior: 'Here',553 },554 },555 fun: {556 Fungible: TRANSFER_AMOUNT,557 },558 },559 ],560 };561562 const feeAssetItem = 0;563564 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');565 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);566567 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;568 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));569 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;570571 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {572 await helper.wait.newBlocks(3);573574 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});575 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);576577 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;578 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;579 acalaBackwardTransferAmount = unqIncomeTransfer;580581 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;582583 console.log(584 '[Unique -> Acala] transaction fees on Acala: %s ACA',585 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),586 );587 console.log(588 '[Unique -> Acala] transaction fees on Acala: %s UNQ',589 helper.util.bigIntToDecimals(acaUnqFees),590 );591 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));592 expect(acaFees == 0n).to.be.true;593594 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;595596 expect(597 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,598 'Acala took different income fee, check the Acala foreign asset config',599 ).to.be.true;600 });601 });602603 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {604 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {605 const destination = {606 V2: {607 parents: 1,608 interior: {609 X2: [610 {Parachain: UNIQUE_CHAIN},611 {612 AccountId32: {613 network: 'Any',614 id: randomAccount.addressRaw,615 },616 },617 ],618 },619 },620 };621622 const id = {623 ForeignAsset: 0,624 };625626 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');627 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);628 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);629630 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;631 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;632633 console.log(634 '[Acala -> Unique] transaction fees on Acala: %s ACA',635 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),636 );637 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));638639 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;640 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;641 });642643 await helper.wait.newBlocks(3);644645 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);646 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;647 expect(actuallyDelivered > 0).to.be.true;648649 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));650651 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;652 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));653 expect(unqFees == 0n).to.be.true;654 });655656 itSub('Acala can send only up to its balance', async ({helper}) => {657 // set Acala's sovereign account's balance658 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);659 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);660 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);661662 const moreThanAcalaHas = acalaBalance * 2n;663664 let targetAccountBalance = 0n;665 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);666667 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(668 targetAccount.addressRaw,669 {670 Concrete: {671 parents: 0,672 interior: 'Here',673 },674 },675 moreThanAcalaHas,676 );677678 let maliciousXcmProgramSent: any;679 const maxWaitBlocks = 3;680681 // Try to trick Unique682 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {683 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);684685 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);686 });687688 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash689 && event.outcome.isFailedToTransactAsset);690691 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);692 expect(targetAccountBalance).to.be.equal(0n);693694 // But Acala still can send the correct amount695 const validTransferAmount = acalaBalance / 2n;696 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(697 targetAccount.addressRaw,698 {699 Concrete: {700 parents: 0,701 interior: 'Here',702 },703 },704 validTransferAmount,705 );706707 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {708 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);709 });710711 await helper.wait.newBlocks(maxWaitBlocks);712713 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);714 expect(targetAccountBalance).to.be.equal(validTransferAmount);715 });716717 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {718 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);719 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);720721 const uniqueMultilocation = {722 V2: {723 parents: 1,724 interior: {725 X1: {726 Parachain: UNIQUE_CHAIN,727 },728 },729 },730 };731732 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(733 targetAccount.addressRaw,734 uniqueAssetId,735 testAmount,736 );737738 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(739 targetAccount.addressRaw,740 {741 Concrete: {742 parents: 0,743 interior: 'Here',744 },745 },746 testAmount,747 );748749 let maliciousXcmProgramFullIdSent: any;750 let maliciousXcmProgramHereIdSent: any;751 const maxWaitBlocks = 3;752753 // Try to trick Unique using full UNQ identification754 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {755 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);756757 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);758 });759760 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash761 && event.outcome.isUntrustedReserveLocation);762763 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);764 expect(accountBalance).to.be.equal(0n);765766 // Try to trick Unique using shortened UNQ identification767 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {768 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);769770 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);771 });772773 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash774 && event.outcome.isUntrustedReserveLocation);775776 accountBalance = await helper.balance.getSubstrate(targetAccount.address);777 expect(accountBalance).to.be.equal(0n);778 });779});780781describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {782 let alice: IKeyringPair;783 let randomAccount: IKeyringPair;784 let unqFees: bigint;785 let balanceUniqueTokenInit: bigint;786 let balanceUniqueTokenMiddle: bigint;787 let balanceUniqueTokenFinal: bigint;788 const maxWaitBlocks = 6;789790 before(async () => {791 await usingPlaygrounds(async (helper, privateKey) => {792 alice = await privateKey('//Alice');793 [randomAccount] = await helper.arrange.createAccounts([0n], alice);794795 // Set the default version to wrap the first message to other chains.796 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);797 });798799 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {800 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))801 .toJSON() as [])802 .map(nToBigInt).length != 0;803 /*804 Check whether the Unique token has been added805 to the whitelist, since an error will occur806 if it is added again. Needed for debugging807 when this test is run multiple times.808 */809 if(isWhitelisted) {810 console.log('UNQ token is already whitelisted on Polkadex');811 } else {812 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);813 }814815 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);816 });817818 await usingPlaygrounds(async (helper) => {819 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);820 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);821 });822 });823824 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {825826 const destination = {827 V2: {828 parents: 1,829 interior: {830 X1: {831 Parachain: POLKADEX_CHAIN,832 },833 },834 },835 };836837 const beneficiary = {838 V2: {839 parents: 0,840 interior: {841 X1: {842 AccountId32: {843 network: 'Any',844 id: randomAccount.addressRaw,845 },846 },847 },848 },849 };850851 const assets = {852 V2: [853 {854 id: {855 Concrete: {856 parents: 0,857 interior: 'Here',858 },859 },860 fun: {861 Fungible: TRANSFER_AMOUNT,862 },863 },864 ],865 };866867 const feeAssetItem = 0;868869 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');870 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);871 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);872873 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;874 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));875 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;876877 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {878 /*879 Since only the parachain part of the Polkadex880 infrastructure is launched (without their881 solochain validators), processing incoming882 assets will lead to an error.883 This error indicates that the Polkadex chain884 received a message from the Unique network,885 since the hash is being checked to ensure886 it matches what was sent.887 */888 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);889 });890 });891892893 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {894895 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(896 randomAccount.addressRaw,897 uniqueAssetId,898 TRANSFER_AMOUNT,899 );900901 let xcmProgramSent: any;902903904 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {905 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);906907 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);908 });909910 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);911912 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);913914 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);915 });916917 itSub('Polkadex can send only up to its balance', async ({helper}) => {918 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);919 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);920 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);921 const moreThanPolkadexHas = 2n * polkadexBalance;922923 const targetAccount = helper.arrange.createEmptyAccount();924925 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(926 targetAccount.addressRaw,927 {928 Concrete: {929 parents: 0,930 interior: 'Here',931 },932 },933 moreThanPolkadexHas,934 );935936 let maliciousXcmProgramSent: any;937938939 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {940 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);941942 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);943 });944945 await expectFailedToTransact(helper, maliciousXcmProgramSent);946947 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);948 expect(targetAccountBalance).to.be.equal(0n);949 });950951 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {952 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);953 const targetAccount = helper.arrange.createEmptyAccount();954955 const uniqueMultilocation = {956 V2: {957 parents: 1,958 interior: {959 X1: {960 Parachain: UNIQUE_CHAIN,961 },962 },963 },964 };965966 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(967 targetAccount.addressRaw,968 uniqueAssetId,969 testAmount,970 );971972 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(973 targetAccount.addressRaw,974 {975 Concrete: {976 parents: 0,977 interior: 'Here',978 },979 },980 testAmount,981 );982983 let maliciousXcmProgramFullIdSent: any;984 let maliciousXcmProgramHereIdSent: any;985 const maxWaitBlocks = 3;986987 // Try to trick Unique using full UNQ identification988 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {989 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);990991 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);992 });993994 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);995996 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);997 expect(accountBalance).to.be.equal(0n);998999 // Try to trick Unique using shortened UNQ identification1000 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1001 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10021003 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1004 });10051006 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10071008 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1009 expect(accountBalance).to.be.equal(0n);1010 });1011});10121013// These tests are relevant only when1014// the the corresponding foreign assets are not registered1015describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1016 let alice: IKeyringPair;1017 let alith: IKeyringPair;10181019 const testAmount = 100_000_000_000n;1020 let uniqueParachainJunction;1021 let uniqueAccountJunction;10221023 let uniqueParachainMultilocation: any;1024 let uniqueAccountMultilocation: any;1025 let uniqueCombinedMultilocation: any;1026 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210271028 let messageSent: any;10291030 const maxWaitBlocks = 3;10311032 before(async () => {1033 await usingPlaygrounds(async (helper, privateKey) => {1034 alice = await privateKey('//Alice');10351036 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1037 uniqueAccountJunction = {1038 AccountId32: {1039 network: 'Any',1040 id: alice.addressRaw,1041 },1042 };10431044 uniqueParachainMultilocation = {1045 V2: {1046 parents: 1,1047 interior: {1048 X1: uniqueParachainJunction,1049 },1050 },1051 };10521053 uniqueAccountMultilocation = {1054 V2: {1055 parents: 0,1056 interior: {1057 X1: uniqueAccountJunction,1058 },1059 },1060 };10611062 uniqueCombinedMultilocation = {1063 V2: {1064 parents: 1,1065 interior: {1066 X2: [uniqueParachainJunction, uniqueAccountJunction],1067 },1068 },1069 };10701071 uniqueCombinedMultilocationAcala = {1072 V2: {1073 parents: 1,1074 interior: {1075 X2: [uniqueParachainJunction, uniqueAccountJunction],1076 },1077 },1078 };10791080 // Set the default version to wrap the first message to other chains.1081 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1082 });10831084 // eslint-disable-next-line require-await1085 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1086 alith = helper.account.alithAccount();1087 });1088 });1089109010911092 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1093 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1094 const id = {1095 Token: 'ACA',1096 };1097 const destination = uniqueCombinedMultilocationAcala;1098 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');10991100 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1101 });11021103 await expectFailedToTransact(helper, messageSent);1104 });11051106 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1107 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1108 const id = 'SelfReserve';1109 const destination = uniqueCombinedMultilocation;1110 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11111112 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1113 });11141115 await expectFailedToTransact(helper, messageSent);1116 });11171118 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1119 await usingAstarPlaygrounds(astarUrl, async (helper) => {1120 const destinationParachain = uniqueParachainMultilocation;1121 const beneficiary = uniqueAccountMultilocation;1122 const assets = {1123 V2: [{1124 id: {1125 Concrete: {1126 parents: 0,1127 interior: 'Here',1128 },1129 },1130 fun: {1131 Fungible: testAmount,1132 },1133 }],1134 };1135 const feeAssetItem = 0;11361137 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1138 destinationParachain,1139 beneficiary,1140 assets,1141 feeAssetItem,1142 ]);11431144 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1145 });11461147 await expectFailedToTransact(helper, messageSent);1148 });11491150 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11511152 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1153 helper.arrange.createEmptyAccount().addressRaw,1154 {1155 Concrete: {1156 parents: 1,1157 interior: {1158 X1: {1159 Parachain: POLKADEX_CHAIN,1160 },1161 },1162 },1163 },1164 testAmount,1165 );11661167 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1168 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1169 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1170 });11711172 await expectFailedToTransact(helper, messageSent);1173 });1174});11751176describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1177 // Unique constants1178 let alice: IKeyringPair;1179 let uniqueAssetLocation;11801181 let randomAccountUnique: IKeyringPair;1182 let randomAccountMoonbeam: IKeyringPair;11831184 // Moonbeam constants1185 let assetId: string;11861187 const uniqueAssetMetadata = {1188 name: 'xcUnique',1189 symbol: 'xcUNQ',1190 decimals: 18,1191 isFrozen: false,1192 minimalBalance: 1n,1193 };11941195 let balanceUniqueTokenInit: bigint;1196 let balanceUniqueTokenMiddle: bigint;1197 let balanceUniqueTokenFinal: bigint;1198 let balanceForeignUnqTokenInit: bigint;1199 let balanceForeignUnqTokenMiddle: bigint;1200 let balanceForeignUnqTokenFinal: bigint;1201 let balanceGlmrTokenInit: bigint;1202 let balanceGlmrTokenMiddle: bigint;1203 let balanceGlmrTokenFinal: bigint;12041205 before(async () => {1206 await usingPlaygrounds(async (helper, privateKey) => {1207 alice = await privateKey('//Alice');1208 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12091210 balanceForeignUnqTokenInit = 0n;12111212 // Set the default version to wrap the first message to other chains.1213 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1214 });12151216 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1217 const alithAccount = helper.account.alithAccount();1218 const baltatharAccount = helper.account.baltatharAccount();1219 const dorothyAccount = helper.account.dorothyAccount();12201221 randomAccountMoonbeam = helper.account.create();12221223 // >>> Sponsoring Dorothy >>>1224 console.log('Sponsoring Dorothy.......');1225 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1226 console.log('Sponsoring Dorothy.......DONE');1227 // <<< Sponsoring Dorothy <<<12281229 uniqueAssetLocation = {1230 XCM: {1231 parents: 1,1232 interior: {X1: {Parachain: UNIQUE_CHAIN}},1233 },1234 };1235 const existentialDeposit = 1n;1236 const isSufficient = true;1237 const unitsPerSecond = 1n;1238 const numAssetsWeightHint = 0;12391240 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1241 console.log('Unique asset is already registered on MoonBeam');1242 } else {1243 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1244 location: uniqueAssetLocation,1245 metadata: uniqueAssetMetadata,1246 existentialDeposit,1247 isSufficient,1248 unitsPerSecond,1249 numAssetsWeightHint,1250 });12511252 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12531254 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1255 }12561257 // >>> Acquire Unique AssetId Info on Moonbeam >>>1258 console.log('Acquire Unique AssetId Info on Moonbeam.......');12591260 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1261 console.log('UNQ asset ID is %s', assetId);1262 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1263 // >>> Acquire Unique AssetId Info on Moonbeam >>>12641265 // >>> Sponsoring random Account >>>1266 console.log('Sponsoring random Account.......');1267 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1268 console.log('Sponsoring random Account.......DONE');1269 // <<< Sponsoring random Account <<<12701271 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1272 });12731274 await usingPlaygrounds(async (helper) => {1275 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1276 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1277 });1278 });12791280 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1281 const currencyId = 0;1282 const dest = {1283 V2: {1284 parents: 1,1285 interior: {1286 X2: [1287 {Parachain: MOONBEAM_CHAIN},1288 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1289 ],1290 },1291 },1292 };1293 const amount = TRANSFER_AMOUNT;12941295 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');12961297 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1298 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;12991300 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1301 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1302 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13031304 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1305 await helper.wait.newBlocks(3);1306 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13071308 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1309 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1310 expect(glmrFees == 0n).to.be.true;13111312 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13131314 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1315 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1316 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1317 });1318 });13191320 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1321 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1322 const asset = {1323 V2: {1324 id: {1325 Concrete: {1326 parents: 1,1327 interior: {1328 X1: {Parachain: UNIQUE_CHAIN},1329 },1330 },1331 },1332 fun: {1333 Fungible: TRANSFER_AMOUNT,1334 },1335 },1336 };1337 const destination = {1338 V2: {1339 parents: 1,1340 interior: {1341 X2: [1342 {Parachain: UNIQUE_CHAIN},1343 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1344 ],1345 },1346 },1347 };13481349 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13501351 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13521353 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1354 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1355 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13561357 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13581359 expect(unqRandomAccountAsset).to.be.null;13601361 balanceForeignUnqTokenFinal = 0n;13621363 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1364 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1365 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1366 });13671368 await helper.wait.newBlocks(3);13691370 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1371 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1372 expect(actuallyDelivered > 0).to.be.true;13731374 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13751376 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1377 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1378 expect(unqFees == 0n).to.be.true;1379 });13801381 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1382 // set Moonbeam's sovereign account's balance1383 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1384 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1385 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);13861387 const moreThanMoonbeamHas = moonbeamBalance * 2n;13881389 let targetAccountBalance = 0n;1390 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);13911392 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1393 targetAccount.addressRaw,1394 {1395 Concrete: {1396 parents: 0,1397 interior: 'Here',1398 },1399 },1400 moreThanMoonbeamHas,1401 );14021403 let maliciousXcmProgramSent: any;1404 const maxWaitBlocks = 3;14051406 // Try to trick Unique1407 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1408 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14091410 // Needed to bypass the call filter.1411 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1412 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14131414 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1415 });14161417 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1418 && event.outcome.isFailedToTransactAsset);14191420 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1421 expect(targetAccountBalance).to.be.equal(0n);14221423 // But Moonbeam still can send the correct amount1424 const validTransferAmount = moonbeamBalance / 2n;1425 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1426 targetAccount.addressRaw,1427 {1428 Concrete: {1429 parents: 0,1430 interior: 'Here',1431 },1432 },1433 validTransferAmount,1434 );14351436 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1437 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14381439 // Needed to bypass the call filter.1440 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1441 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1442 });14431444 await helper.wait.newBlocks(maxWaitBlocks);14451446 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1447 expect(targetAccountBalance).to.be.equal(validTransferAmount);1448 });14491450 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1451 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1452 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14531454 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1455 targetAccount.addressRaw,1456 uniqueAssetId,1457 testAmount,1458 );14591460 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1461 targetAccount.addressRaw,1462 {1463 Concrete: {1464 parents: 0,1465 interior: 'Here',1466 },1467 },1468 testAmount,1469 );14701471 let maliciousXcmProgramFullIdSent: any;1472 let maliciousXcmProgramHereIdSent: any;1473 const maxWaitBlocks = 3;14741475 // Try to trick Unique using full UNQ identification1476 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1477 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);14781479 // Needed to bypass the call filter.1480 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1481 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);14821483 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1484 });14851486 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1487 && event.outcome.isUntrustedReserveLocation);14881489 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1490 expect(accountBalance).to.be.equal(0n);14911492 // Try to trick Unique using shortened UNQ identification1493 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1494 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);14951496 // Needed to bypass the call filter.1497 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1498 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);14991500 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1501 });15021503 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1504 && event.outcome.isUntrustedReserveLocation);15051506 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1507 expect(accountBalance).to.be.equal(0n);1508 });1509});15101511describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1512 let alice: IKeyringPair;1513 let randomAccount: IKeyringPair;15141515 const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar1516 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar15171518 // Unique -> Astar1519 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1520 const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar1521 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1522 const unqToAstarArrived = 9_962_196_000_000_000_000n; // 9.962 ... UNQ, Astar takes a commision in foreign tokens15231524 // Astar -> Unique1525 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1526 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.962_219_600_000_000_000n UNQ15271528 let balanceAfterUniqueToAstarXCM: bigint;15291530 before(async () => {1531 await usingPlaygrounds(async (helper, privateKey) => {1532 alice = await privateKey('//Alice');1533 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1534 console.log('randomAccount', randomAccount.address);15351536 // Set the default version to wrap the first message to other chains.1537 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1538 });15391540 await usingAstarPlaygrounds(astarUrl, async (helper) => {1541 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1542 console.log('1. Create foreign asset and metadata');1543 await helper.getSudo().assets.forceCreate(1544 alice,1545 UNQ_ASSET_ID_ON_ASTAR,1546 alice.address,1547 UNQ_MINIMAL_BALANCE_ON_ASTAR,1548 );15491550 await helper.assets.setMetadata(1551 alice,1552 UNQ_ASSET_ID_ON_ASTAR,1553 'Unique Network',1554 'UNQ',1555 Number(UNQ_DECIMALS),1556 );15571558 console.log('2. Register asset location on Astar');1559 const assetLocation = {1560 V2: {1561 parents: 1,1562 interior: {1563 X1: {1564 Parachain: UNIQUE_CHAIN,1565 },1566 },1567 },1568 };15691570 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);15711572 console.log('3. Set UNQ payment for XCM execution on Astar');1573 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1574 } else {1575 console.log('UNQ is already registered on Astar');1576 }1577 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1578 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1579 });1580 });15811582 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1583 const destination = {1584 V2: {1585 parents: 1,1586 interior: {1587 X1: {1588 Parachain: ASTAR_CHAIN,1589 },1590 },1591 },1592 };15931594 const beneficiary = {1595 V2: {1596 parents: 0,1597 interior: {1598 X1: {1599 AccountId32: {1600 network: 'Any',1601 id: randomAccount.addressRaw,1602 },1603 },1604 },1605 },1606 };16071608 const assets = {1609 V2: [1610 {1611 id: {1612 Concrete: {1613 parents: 0,1614 interior: 'Here',1615 },1616 },1617 fun: {1618 Fungible: unqToAstarTransferred,1619 },1620 },1621 ],1622 };16231624 // Initial balance is 100 UNQ1625 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1626 console.log(`Initial balance is: ${balanceBefore}`);16271628 const feeAssetItem = 0;1629 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16301631 // Balance after reserve transfer is less than 901632 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1633 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1634 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1635 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16361637 await usingAstarPlaygrounds(astarUrl, async (helper) => {1638 await helper.wait.newBlocks(3);1639 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1640 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16411642 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1643 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);16441645 expect(xcUNQbalance).to.eq(unqToAstarArrived);1646 // Astar balance does not changed1647 expect(astarBalance).to.eq(astarInitialBalance);1648 });1649 });16501651 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1652 await usingAstarPlaygrounds(astarUrl, async (helper) => {1653 const destination = {1654 V2: {1655 parents: 1,1656 interior: {1657 X1: {1658 Parachain: UNIQUE_CHAIN,1659 },1660 },1661 },1662 };16631664 const beneficiary = {1665 V2: {1666 parents: 0,1667 interior: {1668 X1: {1669 AccountId32: {1670 network: 'Any',1671 id: randomAccount.addressRaw,1672 },1673 },1674 },1675 },1676 };16771678 const assets = {1679 V2: [1680 {1681 id: {1682 Concrete: {1683 parents: 1,1684 interior: {1685 X1: {1686 Parachain: UNIQUE_CHAIN,1687 },1688 },1689 },1690 },1691 fun: {1692 Fungible: unqFromAstarTransfered,1693 },1694 },1695 ],1696 };16971698 // Initial balance is 1 ASTR1699 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1700 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1701 expect(balanceASTRbefore).to.eq(astarInitialBalance);17021703 const feeAssetItem = 0;1704 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1705 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17061707 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1708 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1709 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17101711 // Assert: xcUNQ balance correctly decreased1712 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1713 // Assert: ASTR balance is 0.996...1714 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1715 });17161717 await helper.wait.newBlocks(3);1718 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1719 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1720 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1721 });17221723 itSub('Astar can send only up to its balance', async ({helper}) => {1724 // set Astar's sovereign account's balance1725 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1726 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1727 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17281729 const moreThanAstarHas = astarBalance * 2n;17301731 let targetAccountBalance = 0n;1732 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17331734 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1735 targetAccount.addressRaw,1736 {1737 Concrete: {1738 parents: 0,1739 interior: 'Here',1740 },1741 },1742 moreThanAstarHas,1743 );17441745 let maliciousXcmProgramSent: any;1746 const maxWaitBlocks = 3;17471748 // Try to trick Unique1749 await usingAstarPlaygrounds(astarUrl, async (helper) => {1750 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17511752 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1753 });17541755 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1756 && event.outcome.isFailedToTransactAsset);17571758 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1759 expect(targetAccountBalance).to.be.equal(0n);17601761 // But Astar still can send the correct amount1762 const validTransferAmount = astarBalance / 2n;1763 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1764 targetAccount.addressRaw,1765 {1766 Concrete: {1767 parents: 0,1768 interior: 'Here',1769 },1770 },1771 validTransferAmount,1772 );17731774 await usingAstarPlaygrounds(astarUrl, async (helper) => {1775 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1776 });17771778 await helper.wait.newBlocks(maxWaitBlocks);17791780 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1781 expect(targetAccountBalance).to.be.equal(validTransferAmount);1782 });17831784 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1785 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1786 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);17871788 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1789 targetAccount.addressRaw,1790 uniqueAssetId,1791 testAmount,1792 );17931794 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1795 targetAccount.addressRaw,1796 {1797 Concrete: {1798 parents: 0,1799 interior: 'Here',1800 },1801 },1802 testAmount,1803 );18041805 let maliciousXcmProgramFullIdSent: any;1806 let maliciousXcmProgramHereIdSent: any;1807 const maxWaitBlocks = 3;18081809 // Try to trick Unique using full UNQ identification1810 await usingAstarPlaygrounds(astarUrl, async (helper) => {1811 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18121813 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1814 });18151816 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1817 && event.outcome.isUntrustedReserveLocation);18181819 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1820 expect(accountBalance).to.be.equal(0n);18211822 // Try to trick Unique using shortened UNQ identification1823 await usingAstarPlaygrounds(astarUrl, async (helper) => {1824 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18251826 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1827 });18281829 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1830 && event.outcome.isUntrustedReserveLocation);18311832 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1833 expect(accountBalance).to.be.equal(0n);1834 });1835});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 type {IKeyringPair} from '@polkadot/types/types';18import config from '../config.js';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util/index.js';20import {Event} from '@unique/playgrounds/unique.dev.js';21import {hexToString, nToBigInt} from '@polkadot/util';22import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js';23import {XcmTestHelper} from './xcm.types.js';2425const STATEMINT_PALLET_INSTANCE = 50;2627const relayUrl = config.relayUrl;28const statemintUrl = config.statemintUrl;29const acalaUrl = config.acalaUrl;30const moonbeamUrl = config.moonbeamUrl;31const astarUrl = config.astarUrl;32const polkadexUrl = config.polkadexUrl;3334const RELAY_DECIMALS = 12;35const STATEMINT_DECIMALS = 12;36const ACALA_DECIMALS = 12;37const ASTAR_DECIMALS = 18n;38const UNQ_DECIMALS = 18n;3940const TRANSFER_AMOUNT = 2000000000000000000000000n;4142const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4344const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4546const USDT_ASSET_ID = 100;47const USDT_ASSET_METADATA_DECIMALS = 18;48const USDT_ASSET_METADATA_NAME = 'USDT';49const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';50const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;51const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5253const testHelper = new XcmTestHelper('unique');5455describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {56 let alice: IKeyringPair;57 let bob: IKeyringPair;5859 let balanceStmnBefore: bigint;60 let balanceStmnAfter: bigint;6162 let balanceUniqueBefore: bigint;63 let balanceUniqueAfter: bigint;64 let balanceUniqueFinal: bigint;6566 let balanceBobBefore: bigint;67 let balanceBobAfter: bigint;68 let balanceBobFinal: bigint;6970 let balanceBobRelayTokenBefore: bigint;71 let balanceBobRelayTokenAfter: bigint;7273 let usdtCollectionId: number;74 let relayCollectionId: number;7576 before(async () => {77 await usingPlaygrounds(async (helper, privateKey) => {78 alice = await privateKey('//Alice');79 bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor8081 // Set the default version to wrap the first message to other chains.82 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);8384 relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);85 });8687 await usingRelayPlaygrounds(relayUrl, async (helper) => {88 // Fund accounts on Statemint89 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);90 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);91 });9293 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {94 const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);95 if(assetInfo == null) {96 await helper.assets.create(97 alice,98 USDT_ASSET_ID,99 alice.address,100 USDT_ASSET_METADATA_MINIMAL_BALANCE,101 );102 await helper.assets.setMetadata(103 alice,104 USDT_ASSET_ID,105 USDT_ASSET_METADATA_NAME,106 USDT_ASSET_METADATA_DESCRIPTION,107 USDT_ASSET_METADATA_DECIMALS,108 );109 } else {110 console.log('The USDT asset is already registered on AssetHub');111 }112113 await helper.assets.mint(114 alice,115 USDT_ASSET_ID,116 alice.address,117 USDT_ASSET_AMOUNT,118 );119120 const sovereignFundingAmount = 3_500_000_000n;121122 // funding parachain sovereing account on Statemint.123 // The sovereign account should be created before any action124 // (the assets pallet on Statemint check if the sovereign account exists)125 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);126 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);127 });128129130 await usingPlaygrounds(async (helper) => {131 const location = {132 parents: 1,133 interior: {X3: [134 {135 Parachain: STATEMINT_CHAIN,136 },137 {138 PalletInstance: STATEMINT_PALLET_INSTANCE,139 },140 {141 GeneralIndex: USDT_ASSET_ID,142 },143 ]},144 };145 const assetId = {Concrete: location};146147 if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {148 const tokenPrefix = USDT_ASSET_METADATA_NAME;149 await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});150 } else {151 console.log('Foreign collection is already registered on Unique');152 }153154 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);155 usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);156 });157158159 // Providing the relay currency to the unique sender account160 // (fee for USDT XCM are paid in relay tokens)161 await usingRelayPlaygrounds(relayUrl, async (helper) => {162 const destination = {163 V2: {164 parents: 0,165 interior: {X1: {166 Parachain: UNIQUE_CHAIN,167 },168 },169 }};170171 const beneficiary = {172 V2: {173 parents: 0,174 interior: {X1: {175 AccountId32: {176 network: 'Any',177 id: alice.addressRaw,178 },179 }},180 },181 };182183 const assets = {184 V2: [185 {186 id: {187 Concrete: {188 parents: 0,189 interior: 'Here',190 },191 },192 fun: {193 Fungible: TRANSFER_AMOUNT_RELAY,194 },195 },196 ],197 };198199 const feeAssetItem = 0;200201 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');202 });203204 });205206 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {207 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {208 const dest = {209 V2: {210 parents: 1,211 interior: {X1: {212 Parachain: UNIQUE_CHAIN,213 },214 },215 }};216217 const beneficiary = {218 V2: {219 parents: 0,220 interior: {X1: {221 AccountId32: {222 network: 'Any',223 id: alice.addressRaw,224 },225 }},226 },227 };228229 const assets = {230 V2: [231 {232 id: {233 Concrete: {234 parents: 0,235 interior: {236 X2: [237 {238 PalletInstance: STATEMINT_PALLET_INSTANCE,239 },240 {241 GeneralIndex: USDT_ASSET_ID,242 },243 ]},244 },245 },246 fun: {247 Fungible: TRANSFER_AMOUNT,248 },249 },250 ],251 };252253 const feeAssetItem = 0;254255 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);256 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');257258 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);259260 // common good parachain take commission in it native token261 console.log(262 '[Statemint -> Unique] transaction fees on Statemint: %s WND',263 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),264 );265 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;266267 });268269270 // ensure that asset has been delivered271 await helper.wait.newBlocks(3);272273 const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});274275 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);276277 console.log(278 '[Statemint -> Unique] transaction fees on Unique: %s USDT',279 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),280 );281 console.log(282 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',283 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),284 );285 // commission has not paid in USDT token286 expect(free).to.be.equal(TRANSFER_AMOUNT);287 // ... and parachain native token288 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;289 });290291 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {292 const destination = {293 V2: {294 parents: 1,295 interior: {X2: [296 {297 Parachain: STATEMINT_CHAIN,298 },299 {300 AccountId32: {301 network: 'Any',302 id: alice.addressRaw,303 },304 },305 ]},306 },307 };308309 const relayFee = 400_000_000_000_000n;310 const currencies: [any, bigint][] = [311 [312 usdtCollectionId,313 TRANSFER_AMOUNT,314 ],315 [316 relayCollectionId,317 relayFee,318 ],319 ];320321 const feeItem = 1;322323 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');324325 // the commission has been paid in parachain native token326 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);327 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));328 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;329330 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {331 await helper.wait.newBlocks(3);332333 // The USDT token never paid fees. Its amount not changed from begin value.334 // Also check that xcm transfer has been succeeded335 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;336 });337 });338339 itSub('Should connect and send Relay token to Unique', async ({helper}) => {340 balanceBobBefore = await helper.balance.getSubstrate(bob.address);341 balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});342343 await usingRelayPlaygrounds(relayUrl, async (helper) => {344 const destination = {345 V2: {346 parents: 0,347 interior: {X1: {348 Parachain: UNIQUE_CHAIN,349 },350 },351 }};352353 const beneficiary = {354 V2: {355 parents: 0,356 interior: {X1: {357 AccountId32: {358 network: 'Any',359 id: bob.addressRaw,360 },361 }},362 },363 };364365 const assets = {366 V2: [367 {368 id: {369 Concrete: {370 parents: 0,371 interior: 'Here',372 },373 },374 fun: {375 Fungible: TRANSFER_AMOUNT_RELAY,376 },377 },378 ],379 };380381 const feeAssetItem = 0;382383 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');384 });385386 await helper.wait.newBlocks(3);387388 balanceBobAfter = await helper.balance.getSubstrate(bob.address);389 balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});390391 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;392 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;393 console.log(394 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',395 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),396 );397 console.log(398 '[Relay (Westend) -> Unique] transaction fees: %s WND',399 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),400 );401 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);402 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;403 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;404 });405406 itSub('Should connect and send Relay token back', async ({helper}) => {407 let relayTokenBalanceBefore: bigint;408 let relayTokenBalanceAfter: bigint;409 await usingRelayPlaygrounds(relayUrl, async (helper) => {410 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);411 });412413 const destination = {414 V2: {415 parents: 1,416 interior: {417 X1:{418 AccountId32: {419 network: 'Any',420 id: bob.addressRaw,421 },422 },423 },424 },425 };426427 const currencies: any = [428 [429 relayCollectionId,430 TRANSFER_AMOUNT_RELAY,431 ],432 ];433434 const feeItem = 0;435436 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438 balanceBobFinal = await helper.balance.getSubstrate(bob.address);439 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441 await usingRelayPlaygrounds(relayUrl, async (helper) => {442 await helper.wait.newBlocks(10);443 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448 });449 });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {453 let alice: IKeyringPair;454 let randomAccount: IKeyringPair;455456 let balanceUniqueTokenInit: bigint;457 let balanceUniqueTokenMiddle: bigint;458 let balanceUniqueTokenFinal: bigint;459 let balanceAcalaTokenInit: bigint;460 let balanceAcalaTokenMiddle: bigint;461 let balanceAcalaTokenFinal: bigint;462 let balanceUniqueForeignTokenInit: bigint;463 let balanceUniqueForeignTokenMiddle: bigint;464 let balanceUniqueForeignTokenFinal: bigint;465466 // computed by a test transfer from prod Unique to prod Acala.467 // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48468 // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)469 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;470 const acalaEps = 8n * 10n ** 16n;471472 let acalaBackwardTransferAmount: bigint;473474 before(async () => {475 await usingPlaygrounds(async (helper, privateKey) => {476 alice = await privateKey('//Alice');477 [randomAccount] = await helper.arrange.createAccounts([0n], alice);478479 // Set the default version to wrap the first message to other chains.480 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481 });482483 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {484 const destination = {485 V2: {486 parents: 1,487 interior: {488 X1: {489 Parachain: UNIQUE_CHAIN,490 },491 },492 },493 };494495 const metadata = {496 name: 'Unique Network',497 symbol: 'UNQ',498 decimals: 18,499 minimalBalance: 1250000000000000000n,500 };501 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>502 hexToString(v.toJSON()['symbol'])) as string[];503504 if(!assets.includes('UNQ')) {505 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);506 } else {507 console.log('UNQ token already registered on Acala assetRegistry pallet');508 }509 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);510 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);511 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});512 });513514 await usingPlaygrounds(async (helper) => {515 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);516 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);517 });518 });519520 itSub('Should connect and send UNQ to Acala', async ({helper}) => {521522 const destination = {523 V2: {524 parents: 1,525 interior: {526 X1: {527 Parachain: ACALA_CHAIN,528 },529 },530 },531 };532533 const beneficiary = {534 V2: {535 parents: 0,536 interior: {537 X1: {538 AccountId32: {539 network: 'Any',540 id: randomAccount.addressRaw,541 },542 },543 },544 },545 };546547 const assets = {548 V2: [549 {550 id: {551 Concrete: {552 parents: 0,553 interior: 'Here',554 },555 },556 fun: {557 Fungible: TRANSFER_AMOUNT,558 },559 },560 ],561 };562563 const feeAssetItem = 0;564565 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');566 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);567568 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;569 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));570 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;571572 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {573 await helper.wait.newBlocks(3);574575 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});576 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);577578 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;579 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;580 acalaBackwardTransferAmount = unqIncomeTransfer;581582 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;583584 console.log(585 '[Unique -> Acala] transaction fees on Acala: %s ACA',586 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),587 );588 console.log(589 '[Unique -> Acala] transaction fees on Acala: %s UNQ',590 helper.util.bigIntToDecimals(acaUnqFees),591 );592 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));593 expect(acaFees == 0n).to.be.true;594595 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;596597 expect(598 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,599 'Acala took different income fee, check the Acala foreign asset config',600 ).to.be.true;601 });602 });603604 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {605 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {606 const destination = {607 V2: {608 parents: 1,609 interior: {610 X2: [611 {Parachain: UNIQUE_CHAIN},612 {613 AccountId32: {614 network: 'Any',615 id: randomAccount.addressRaw,616 },617 },618 ],619 },620 },621 };622623 const id = {624 ForeignAsset: 0,625 };626627 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');628 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);629 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);630631 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;632 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;633634 console.log(635 '[Acala -> Unique] transaction fees on Acala: %s ACA',636 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),637 );638 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));639640 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;641 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;642 });643644 await helper.wait.newBlocks(3);645646 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);647 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;648 expect(actuallyDelivered > 0).to.be.true;649650 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));651652 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;653 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));654 expect(unqFees == 0n).to.be.true;655 });656657 itSub('Acala can send only up to its balance', async ({helper}) => {658 // set Acala's sovereign account's balance659 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);660 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);661 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);662663 const moreThanAcalaHas = acalaBalance * 2n;664665 let targetAccountBalance = 0n;666 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);667668 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(669 targetAccount.addressRaw,670 {671 Concrete: {672 parents: 0,673 interior: 'Here',674 },675 },676 moreThanAcalaHas,677 );678679 let maliciousXcmProgramSent: any;680 const maxWaitBlocks = 3;681682 // Try to trick Unique683 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {684 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);685686 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);687 });688689 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash690 && event.outcome.isFailedToTransactAsset);691692 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);693 expect(targetAccountBalance).to.be.equal(0n);694695 // But Acala still can send the correct amount696 const validTransferAmount = acalaBalance / 2n;697 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(698 targetAccount.addressRaw,699 {700 Concrete: {701 parents: 0,702 interior: 'Here',703 },704 },705 validTransferAmount,706 );707708 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {709 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);710 });711712 await helper.wait.newBlocks(maxWaitBlocks);713714 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);715 expect(targetAccountBalance).to.be.equal(validTransferAmount);716 });717718 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {719 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);720 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);721722 const uniqueMultilocation = {723 V2: {724 parents: 1,725 interior: {726 X1: {727 Parachain: UNIQUE_CHAIN,728 },729 },730 },731 };732733 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(734 targetAccount.addressRaw,735 uniqueAssetId,736 testAmount,737 );738739 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(740 targetAccount.addressRaw,741 {742 Concrete: {743 parents: 0,744 interior: 'Here',745 },746 },747 testAmount,748 );749750 let maliciousXcmProgramFullIdSent: any;751 let maliciousXcmProgramHereIdSent: any;752 const maxWaitBlocks = 3;753754 // Try to trick Unique using full UNQ identification755 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {756 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);757758 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);759 });760761 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash762 && event.outcome.isUntrustedReserveLocation);763764 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);765 expect(accountBalance).to.be.equal(0n);766767 // Try to trick Unique using shortened UNQ identification768 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {769 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);770771 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);772 });773774 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash775 && event.outcome.isUntrustedReserveLocation);776777 accountBalance = await helper.balance.getSubstrate(targetAccount.address);778 expect(accountBalance).to.be.equal(0n);779 });780});781782describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {783 let alice: IKeyringPair;784 let randomAccount: IKeyringPair;785 let unqFees: bigint;786 let balanceUniqueTokenInit: bigint;787 let balanceUniqueTokenMiddle: bigint;788 let balanceUniqueTokenFinal: bigint;789 const maxWaitBlocks = 6;790791 before(async () => {792 await usingPlaygrounds(async (helper, privateKey) => {793 alice = await privateKey('//Alice');794 [randomAccount] = await helper.arrange.createAccounts([0n], alice);795796 // Set the default version to wrap the first message to other chains.797 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);798 });799800 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {801 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))802 .toJSON() as [])803 .map(nToBigInt).length != 0;804 /*805 Check whether the Unique token has been added806 to the whitelist, since an error will occur807 if it is added again. Needed for debugging808 when this test is run multiple times.809 */810 if(isWhitelisted) {811 console.log('UNQ token is already whitelisted on Polkadex');812 } else {813 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);814 }815816 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);817 });818819 await usingPlaygrounds(async (helper) => {820 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);821 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);822 });823 });824825 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {826827 const destination = {828 V2: {829 parents: 1,830 interior: {831 X1: {832 Parachain: POLKADEX_CHAIN,833 },834 },835 },836 };837838 const beneficiary = {839 V2: {840 parents: 0,841 interior: {842 X1: {843 AccountId32: {844 network: 'Any',845 id: randomAccount.addressRaw,846 },847 },848 },849 },850 };851852 const assets = {853 V2: [854 {855 id: {856 Concrete: {857 parents: 0,858 interior: 'Here',859 },860 },861 fun: {862 Fungible: TRANSFER_AMOUNT,863 },864 },865 ],866 };867868 const feeAssetItem = 0;869870 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');871 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);872 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);873874 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;875 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));876 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;877878 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {879 /*880 Since only the parachain part of the Polkadex881 infrastructure is launched (without their882 solochain validators), processing incoming883 assets will lead to an error.884 This error indicates that the Polkadex chain885 received a message from the Unique network,886 since the hash is being checked to ensure887 it matches what was sent.888 */889 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);890 });891 });892893894 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {895896 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(897 randomAccount.addressRaw,898 uniqueAssetId,899 TRANSFER_AMOUNT,900 );901902 let xcmProgramSent: any;903904905 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {906 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);907908 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);909 });910911 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);912913 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);914915 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);916 });917918 itSub('Polkadex can send only up to its balance', async ({helper}) => {919 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);920 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);921 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);922 const moreThanPolkadexHas = 2n * polkadexBalance;923924 const targetAccount = helper.arrange.createEmptyAccount();925926 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(927 targetAccount.addressRaw,928 {929 Concrete: {930 parents: 0,931 interior: 'Here',932 },933 },934 moreThanPolkadexHas,935 );936937 let maliciousXcmProgramSent: any;938939940 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {941 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);942943 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);944 });945946 await expectFailedToTransact(helper, maliciousXcmProgramSent);947948 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);949 expect(targetAccountBalance).to.be.equal(0n);950 });951952 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {953 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);954 const targetAccount = helper.arrange.createEmptyAccount();955956 const uniqueMultilocation = {957 V2: {958 parents: 1,959 interior: {960 X1: {961 Parachain: UNIQUE_CHAIN,962 },963 },964 },965 };966967 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(968 targetAccount.addressRaw,969 uniqueAssetId,970 testAmount,971 );972973 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(974 targetAccount.addressRaw,975 {976 Concrete: {977 parents: 0,978 interior: 'Here',979 },980 },981 testAmount,982 );983984 let maliciousXcmProgramFullIdSent: any;985 let maliciousXcmProgramHereIdSent: any;986 const maxWaitBlocks = 3;987988 // Try to trick Unique using full UNQ identification989 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {990 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);991992 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);993 });994995 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);996997 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);998 expect(accountBalance).to.be.equal(0n);9991000 // Try to trick Unique using shortened UNQ identification1001 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1002 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10031004 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1005 });10061007 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10081009 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1010 expect(accountBalance).to.be.equal(0n);1011 });1012});10131014// These tests are relevant only when1015// the the corresponding foreign assets are not registered1016describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1017 let alice: IKeyringPair;1018 let alith: IKeyringPair;10191020 const testAmount = 100_000_000_000n;1021 let uniqueParachainJunction;1022 let uniqueAccountJunction;10231024 let uniqueParachainMultilocation: any;1025 let uniqueAccountMultilocation: any;1026 let uniqueCombinedMultilocation: any;1027 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210281029 let messageSent: any;10301031 const maxWaitBlocks = 3;10321033 before(async () => {1034 await usingPlaygrounds(async (helper, privateKey) => {1035 alice = await privateKey('//Alice');10361037 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1038 uniqueAccountJunction = {1039 AccountId32: {1040 network: 'Any',1041 id: alice.addressRaw,1042 },1043 };10441045 uniqueParachainMultilocation = {1046 V2: {1047 parents: 1,1048 interior: {1049 X1: uniqueParachainJunction,1050 },1051 },1052 };10531054 uniqueAccountMultilocation = {1055 V2: {1056 parents: 0,1057 interior: {1058 X1: uniqueAccountJunction,1059 },1060 },1061 };10621063 uniqueCombinedMultilocation = {1064 V2: {1065 parents: 1,1066 interior: {1067 X2: [uniqueParachainJunction, uniqueAccountJunction],1068 },1069 },1070 };10711072 uniqueCombinedMultilocationAcala = {1073 V2: {1074 parents: 1,1075 interior: {1076 X2: [uniqueParachainJunction, uniqueAccountJunction],1077 },1078 },1079 };10801081 // Set the default version to wrap the first message to other chains.1082 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1083 });10841085 // eslint-disable-next-line require-await1086 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1087 alith = helper.account.alithAccount();1088 });1089 });1090109110921093 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1094 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1095 const id = {1096 Token: 'ACA',1097 };1098 const destination = uniqueCombinedMultilocationAcala;1099 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11001101 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1102 });11031104 await expectFailedToTransact(helper, messageSent);1105 });11061107 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1108 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1109 const id = 'SelfReserve';1110 const destination = uniqueCombinedMultilocation;1111 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11121113 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1114 });11151116 await expectFailedToTransact(helper, messageSent);1117 });11181119 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1120 await usingAstarPlaygrounds(astarUrl, async (helper) => {1121 const destinationParachain = uniqueParachainMultilocation;1122 const beneficiary = uniqueAccountMultilocation;1123 const assets = {1124 V2: [{1125 id: {1126 Concrete: {1127 parents: 0,1128 interior: 'Here',1129 },1130 },1131 fun: {1132 Fungible: testAmount,1133 },1134 }],1135 };1136 const feeAssetItem = 0;11371138 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1139 destinationParachain,1140 beneficiary,1141 assets,1142 feeAssetItem,1143 ]);11441145 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1146 });11471148 await expectFailedToTransact(helper, messageSent);1149 });11501151 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11521153 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1154 helper.arrange.createEmptyAccount().addressRaw,1155 {1156 Concrete: {1157 parents: 1,1158 interior: {1159 X1: {1160 Parachain: POLKADEX_CHAIN,1161 },1162 },1163 },1164 },1165 testAmount,1166 );11671168 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1169 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1170 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1171 });11721173 await expectFailedToTransact(helper, messageSent);1174 });1175});11761177describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1178 // Unique constants1179 let alice: IKeyringPair;1180 let uniqueAssetLocation;11811182 let randomAccountUnique: IKeyringPair;1183 let randomAccountMoonbeam: IKeyringPair;11841185 // Moonbeam constants1186 let assetId: string;11871188 const uniqueAssetMetadata = {1189 name: 'xcUnique',1190 symbol: 'xcUNQ',1191 decimals: 18,1192 isFrozen: false,1193 minimalBalance: 1n,1194 };11951196 let balanceUniqueTokenInit: bigint;1197 let balanceUniqueTokenMiddle: bigint;1198 let balanceUniqueTokenFinal: bigint;1199 let balanceForeignUnqTokenInit: bigint;1200 let balanceForeignUnqTokenMiddle: bigint;1201 let balanceForeignUnqTokenFinal: bigint;1202 let balanceGlmrTokenInit: bigint;1203 let balanceGlmrTokenMiddle: bigint;1204 let balanceGlmrTokenFinal: bigint;12051206 before(async () => {1207 await usingPlaygrounds(async (helper, privateKey) => {1208 alice = await privateKey('//Alice');1209 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12101211 balanceForeignUnqTokenInit = 0n;12121213 // Set the default version to wrap the first message to other chains.1214 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1215 });12161217 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1218 const alithAccount = helper.account.alithAccount();1219 const baltatharAccount = helper.account.baltatharAccount();1220 const dorothyAccount = helper.account.dorothyAccount();12211222 randomAccountMoonbeam = helper.account.create();12231224 // >>> Sponsoring Dorothy >>>1225 console.log('Sponsoring Dorothy.......');1226 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1227 console.log('Sponsoring Dorothy.......DONE');1228 // <<< Sponsoring Dorothy <<<12291230 uniqueAssetLocation = {1231 XCM: {1232 parents: 1,1233 interior: {X1: {Parachain: UNIQUE_CHAIN}},1234 },1235 };1236 const existentialDeposit = 1n;1237 const isSufficient = true;1238 const unitsPerSecond = 1n;1239 const numAssetsWeightHint = 0;12401241 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1242 console.log('Unique asset is already registered on MoonBeam');1243 } else {1244 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1245 location: uniqueAssetLocation,1246 metadata: uniqueAssetMetadata,1247 existentialDeposit,1248 isSufficient,1249 unitsPerSecond,1250 numAssetsWeightHint,1251 });12521253 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12541255 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1256 }12571258 // >>> Acquire Unique AssetId Info on Moonbeam >>>1259 console.log('Acquire Unique AssetId Info on Moonbeam.......');12601261 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1262 console.log('UNQ asset ID is %s', assetId);1263 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1264 // >>> Acquire Unique AssetId Info on Moonbeam >>>12651266 // >>> Sponsoring random Account >>>1267 console.log('Sponsoring random Account.......');1268 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1269 console.log('Sponsoring random Account.......DONE');1270 // <<< Sponsoring random Account <<<12711272 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1273 });12741275 await usingPlaygrounds(async (helper) => {1276 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1277 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1278 });1279 });12801281 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1282 const currencyId = 0;1283 const dest = {1284 V2: {1285 parents: 1,1286 interior: {1287 X2: [1288 {Parachain: MOONBEAM_CHAIN},1289 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1290 ],1291 },1292 },1293 };1294 const amount = TRANSFER_AMOUNT;12951296 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');12971298 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1299 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13001301 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1302 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1303 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13041305 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1306 await helper.wait.newBlocks(3);1307 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13081309 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1310 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1311 expect(glmrFees == 0n).to.be.true;13121313 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13141315 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1316 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1317 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1318 });1319 });13201321 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1322 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1323 const asset = {1324 V2: {1325 id: {1326 Concrete: {1327 parents: 1,1328 interior: {1329 X1: {Parachain: UNIQUE_CHAIN},1330 },1331 },1332 },1333 fun: {1334 Fungible: TRANSFER_AMOUNT,1335 },1336 },1337 };1338 const destination = {1339 V2: {1340 parents: 1,1341 interior: {1342 X2: [1343 {Parachain: UNIQUE_CHAIN},1344 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1345 ],1346 },1347 },1348 };13491350 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13511352 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13531354 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1355 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1356 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13571358 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13591360 expect(unqRandomAccountAsset).to.be.null;13611362 balanceForeignUnqTokenFinal = 0n;13631364 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1365 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1366 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1367 });13681369 await helper.wait.newBlocks(3);13701371 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1372 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1373 expect(actuallyDelivered > 0).to.be.true;13741375 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13761377 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1378 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1379 expect(unqFees == 0n).to.be.true;1380 });13811382 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1383 // set Moonbeam's sovereign account's balance1384 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1385 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1386 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);13871388 const moreThanMoonbeamHas = moonbeamBalance * 2n;13891390 let targetAccountBalance = 0n;1391 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);13921393 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1394 targetAccount.addressRaw,1395 {1396 Concrete: {1397 parents: 0,1398 interior: 'Here',1399 },1400 },1401 moreThanMoonbeamHas,1402 );14031404 let maliciousXcmProgramSent: any;1405 const maxWaitBlocks = 3;14061407 // Try to trick Unique1408 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1409 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14101411 // Needed to bypass the call filter.1412 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1413 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14141415 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1416 });14171418 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1419 && event.outcome.isFailedToTransactAsset);14201421 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1422 expect(targetAccountBalance).to.be.equal(0n);14231424 // But Moonbeam still can send the correct amount1425 const validTransferAmount = moonbeamBalance / 2n;1426 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1427 targetAccount.addressRaw,1428 {1429 Concrete: {1430 parents: 0,1431 interior: 'Here',1432 },1433 },1434 validTransferAmount,1435 );14361437 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1438 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14391440 // Needed to bypass the call filter.1441 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1442 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1443 });14441445 await helper.wait.newBlocks(maxWaitBlocks);14461447 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1448 expect(targetAccountBalance).to.be.equal(validTransferAmount);1449 });14501451 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1452 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1453 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14541455 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1456 targetAccount.addressRaw,1457 uniqueAssetId,1458 testAmount,1459 );14601461 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1462 targetAccount.addressRaw,1463 {1464 Concrete: {1465 parents: 0,1466 interior: 'Here',1467 },1468 },1469 testAmount,1470 );14711472 let maliciousXcmProgramFullIdSent: any;1473 let maliciousXcmProgramHereIdSent: any;1474 const maxWaitBlocks = 3;14751476 // Try to trick Unique using full UNQ identification1477 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1478 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);14791480 // Needed to bypass the call filter.1481 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1482 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);14831484 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1485 });14861487 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1488 && event.outcome.isUntrustedReserveLocation);14891490 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1491 expect(accountBalance).to.be.equal(0n);14921493 // Try to trick Unique using shortened UNQ identification1494 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1495 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);14961497 // Needed to bypass the call filter.1498 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1499 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15001501 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1502 });15031504 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1505 && event.outcome.isUntrustedReserveLocation);15061507 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1508 expect(accountBalance).to.be.equal(0n);1509 });1510});15111512describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1513 let alice: IKeyringPair;1514 let randomAccount: IKeyringPair;15151516 const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar1517 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar15181519 // Unique -> Astar1520 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1521 const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar1522 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1523 const unqToAstarArrived = 9_962_196_000_000_000_000n; // 9.962 ... UNQ, Astar takes a commision in foreign tokens15241525 // Astar -> Unique1526 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1527 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.962_219_600_000_000_000n UNQ15281529 let balanceAfterUniqueToAstarXCM: bigint;15301531 before(async () => {1532 await usingPlaygrounds(async (helper, privateKey) => {1533 alice = await privateKey('//Alice');1534 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1535 console.log('randomAccount', randomAccount.address);15361537 // Set the default version to wrap the first message to other chains.1538 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1539 });15401541 await usingAstarPlaygrounds(astarUrl, async (helper) => {1542 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1543 console.log('1. Create foreign asset and metadata');1544 await helper.getSudo().assets.forceCreate(1545 alice,1546 UNQ_ASSET_ID_ON_ASTAR,1547 alice.address,1548 UNQ_MINIMAL_BALANCE_ON_ASTAR,1549 );15501551 await helper.assets.setMetadata(1552 alice,1553 UNQ_ASSET_ID_ON_ASTAR,1554 'Unique Network',1555 'UNQ',1556 Number(UNQ_DECIMALS),1557 );15581559 console.log('2. Register asset location on Astar');1560 const assetLocation = {1561 V2: {1562 parents: 1,1563 interior: {1564 X1: {1565 Parachain: UNIQUE_CHAIN,1566 },1567 },1568 },1569 };15701571 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);15721573 console.log('3. Set UNQ payment for XCM execution on Astar');1574 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1575 } else {1576 console.log('UNQ is already registered on Astar');1577 }1578 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1579 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1580 });1581 });15821583 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1584 const destination = {1585 V2: {1586 parents: 1,1587 interior: {1588 X1: {1589 Parachain: ASTAR_CHAIN,1590 },1591 },1592 },1593 };15941595 const beneficiary = {1596 V2: {1597 parents: 0,1598 interior: {1599 X1: {1600 AccountId32: {1601 network: 'Any',1602 id: randomAccount.addressRaw,1603 },1604 },1605 },1606 },1607 };16081609 const assets = {1610 V2: [1611 {1612 id: {1613 Concrete: {1614 parents: 0,1615 interior: 'Here',1616 },1617 },1618 fun: {1619 Fungible: unqToAstarTransferred,1620 },1621 },1622 ],1623 };16241625 // Initial balance is 100 UNQ1626 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1627 console.log(`Initial balance is: ${balanceBefore}`);16281629 const feeAssetItem = 0;1630 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16311632 // Balance after reserve transfer is less than 901633 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1634 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1635 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1636 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16371638 await usingAstarPlaygrounds(astarUrl, async (helper) => {1639 await helper.wait.newBlocks(3);1640 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1641 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16421643 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1644 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);16451646 expect(xcUNQbalance).to.eq(unqToAstarArrived);1647 // Astar balance does not changed1648 expect(astarBalance).to.eq(astarInitialBalance);1649 });1650 });16511652 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1653 await usingAstarPlaygrounds(astarUrl, async (helper) => {1654 const destination = {1655 V2: {1656 parents: 1,1657 interior: {1658 X1: {1659 Parachain: UNIQUE_CHAIN,1660 },1661 },1662 },1663 };16641665 const beneficiary = {1666 V2: {1667 parents: 0,1668 interior: {1669 X1: {1670 AccountId32: {1671 network: 'Any',1672 id: randomAccount.addressRaw,1673 },1674 },1675 },1676 },1677 };16781679 const assets = {1680 V2: [1681 {1682 id: {1683 Concrete: {1684 parents: 1,1685 interior: {1686 X1: {1687 Parachain: UNIQUE_CHAIN,1688 },1689 },1690 },1691 },1692 fun: {1693 Fungible: unqFromAstarTransfered,1694 },1695 },1696 ],1697 };16981699 // Initial balance is 1 ASTR1700 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1701 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1702 expect(balanceASTRbefore).to.eq(astarInitialBalance);17031704 const feeAssetItem = 0;1705 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1706 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17071708 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1709 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1710 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17111712 // Assert: xcUNQ balance correctly decreased1713 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1714 // Assert: ASTR balance is 0.996...1715 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1716 });17171718 await helper.wait.newBlocks(3);1719 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1720 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1721 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1722 });17231724 itSub('Astar can send only up to its balance', async ({helper}) => {1725 // set Astar's sovereign account's balance1726 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1727 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1728 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17291730 const moreThanAstarHas = astarBalance * 2n;17311732 let targetAccountBalance = 0n;1733 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17341735 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1736 targetAccount.addressRaw,1737 {1738 Concrete: {1739 parents: 0,1740 interior: 'Here',1741 },1742 },1743 moreThanAstarHas,1744 );17451746 let maliciousXcmProgramSent: any;1747 const maxWaitBlocks = 3;17481749 // Try to trick Unique1750 await usingAstarPlaygrounds(astarUrl, async (helper) => {1751 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17521753 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1754 });17551756 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1757 && event.outcome.isFailedToTransactAsset);17581759 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1760 expect(targetAccountBalance).to.be.equal(0n);17611762 // But Astar still can send the correct amount1763 const validTransferAmount = astarBalance / 2n;1764 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1765 targetAccount.addressRaw,1766 {1767 Concrete: {1768 parents: 0,1769 interior: 'Here',1770 },1771 },1772 validTransferAmount,1773 );17741775 await usingAstarPlaygrounds(astarUrl, async (helper) => {1776 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1777 });17781779 await helper.wait.newBlocks(maxWaitBlocks);17801781 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1782 expect(targetAccountBalance).to.be.equal(validTransferAmount);1783 });17841785 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1786 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1787 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);17881789 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1790 targetAccount.addressRaw,1791 uniqueAssetId,1792 testAmount,1793 );17941795 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1796 targetAccount.addressRaw,1797 {1798 Concrete: {1799 parents: 0,1800 interior: 'Here',1801 },1802 },1803 testAmount,1804 );18051806 let maliciousXcmProgramFullIdSent: any;1807 let maliciousXcmProgramHereIdSent: any;1808 const maxWaitBlocks = 3;18091810 // Try to trick Unique using full UNQ identification1811 await usingAstarPlaygrounds(astarUrl, async (helper) => {1812 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18131814 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1815 });18161817 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1818 && event.outcome.isUntrustedReserveLocation);18191820 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1821 expect(accountBalance).to.be.equal(0n);18221823 // Try to trick Unique using shortened UNQ identification1824 await usingAstarPlaygrounds(astarUrl, async (helper) => {1825 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18261827 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1828 });18291830 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1831 && event.outcome.isUntrustedReserveLocation);18321833 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1834 expect(accountBalance).to.be.equal(0n);1835 });1836});