git.delta.rocks / unique-network / refs/commits / cafc9078ea8d

difftreelog

fix XCM tests

Daniel Shiposha2023-10-23parent: #05427f7.patch.diff
in: master

6 files changed

modifiedjs-packages/playgrounds/types.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/types.xcm.ts
+++ b/js-packages/playgrounds/types.xcm.ts
@@ -27,10 +27,3 @@
     conviction: number,
   },
 }
-
-export interface IForeignAssetMetadata {
-  name?: number | Uint8Array,
-  symbol?: string,
-  decimals?: number,
-  minimalBalance?: bigint,
-}
\ No newline at end of file
modifiedjs-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -2,7 +2,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from './unique.js';
 import type {ILogger, TSigner, TSubstrateAccount} from './types.js';
-import type {AcalaAssetMetadata, DemocracyStandardAccountVote, IForeignAssetMetadata, MoonbeamAssetInfo} from './types.xcm.js';
+import type {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.xcm.js';
 
 
 export class XcmChainHelper extends ChainHelperBase {
@@ -104,22 +104,17 @@
 }
 
 export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
-  async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
+  async register(signer: TSigner, location: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
     await this.helper.executeExtrinsic(
       signer,
-      'api.tx.foreignAssets.registerForeignAsset',
-      [ownerAddress, location, metadata],
+      'api.tx.foreignAssets.forceRegisterForeignAsset',
+      [location, this.helper.util.str2vec(name), tokenPrefix, mode],
       true,
     );
   }
 
-  async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
-    await this.helper.executeExtrinsic(
-      signer,
-      'api.tx.foreignAssets.updateForeignAsset',
-      [foreignAssetId, location, metadata],
-      true,
-    );
+  async foreignCollectionId(location: any) {
+    return (await this.helper.callRpc('api.query.foreignAssets.foreignReserveLocationToCollection', [location])).toJSON();
   }
 }
 
@@ -256,6 +251,10 @@
     await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
   }
 
+  async assetInfo(assetId: number | bigint) {
+    return (await this.helper.callRpc('api.query.assets.asset', [assetId])).toJSON();
+  }
+
   async account(assetId: string | number | bigint, address: string) {
     const accountAsset = (
       await this.helper.callRpc('api.query.assets.account', [assetId, address])
modifiedjs-packages/tests/xcm/xcm.types.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcm.types.ts
+++ b/js-packages/tests/xcm/xcm.types.ts
@@ -505,4 +505,26 @@
       await expectFailedToTransact(helper, messageSent);
     });
   }
+
+  async registerRelayNativeTokenOnUnique(alice: IKeyringPair) {
+    return await usingPlaygrounds(async (helper) => {
+      const relayLocation = {
+        parents: 1,
+        interior: 'Here',
+      };
+
+      const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayLocation);
+      if(relayCollectionId == null) {
+        const name = 'Relay Tokens';
+        const tokenPrefix = 'xDOT';
+        const decimals = 10;
+        await helper.getSudo().foreignAssets.register(alice, relayLocation, name, tokenPrefix, {Fungible: decimals});
+
+        return await helper.foreignAssets.foreignCollectionId(relayLocation);
+      } else {
+        console.log('Relay foreign collection is already registered');
+        return relayCollectionId;
+      }
+    });
+  }
 }
modifiedjs-packages/tests/xcm/xcmOpal.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcmOpal.test.ts
+++ b/js-packages/tests/xcm/xcmOpal.test.ts
@@ -17,6 +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';
 
 const STATEMINE_CHAIN = +(process.env.RELAY_WESTMINT_ID || 1000);
 const UNIQUE_CHAIN = +(process.env.RELAY_OPAL_ID || 2095);
@@ -25,11 +26,11 @@
 const westmintUrl = config.westmintUrl;
 
 const STATEMINE_PALLET_INSTANCE = 50;
-const ASSET_ID = 100;
-const ASSET_METADATA_DECIMALS = 18;
-const ASSET_METADATA_NAME = 'USDT';
-const ASSET_METADATA_DESCRIPTION = 'USDT';
-const ASSET_METADATA_MINIMAL_BALANCE = 1n;
+const USDT_ASSET_ID = 100;
+const USDT_ASSET_METADATA_DECIMALS = 18;
+const USDT_ASSET_METADATA_NAME = 'USDT';
+const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';
+const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;
 
 const RELAY_DECIMALS = 12;
 const WESTMINT_DECIMALS = 12;
@@ -37,7 +38,9 @@
 const TRANSFER_AMOUNT = 1_000_000_000_000_000_000n;
 
 // 10,000.00 (ten thousands) USDT
-const ASSET_AMOUNT = 1_000_000_000_000_000_000_000n;
+const USDT_ASSET_AMOUNT = 1_000_000_000_000_000_000_000n;
+
+const testHelper = new XcmTestHelper('opal');
 
 describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => {
   let alice: IKeyringPair;
@@ -57,57 +60,78 @@
   let balanceBobRelayTokenBefore: bigint;
   let balanceBobRelayTokenAfter: bigint;
 
+  let usdtCollectionId: number;
+  let relayCollectionId: number;
 
   before(async () => {
     await usingPlaygrounds(async (_helper, privateKey) => {
       alice = await privateKey('//Alice');
       bob = await privateKey('//Bob'); // funds donor
+
+      relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);
     });
 
     await usingWestmintPlaygrounds(westmintUrl, async (helper) => {
-      // 350.00 (three hundred fifty) DOT
-      const fundingAmount = 3_500_000_000_000n;
+      const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);
+      if(assetInfo == null) {
+        await helper.assets.create(
+          alice,
+          USDT_ASSET_ID,
+          alice.address,
+          USDT_ASSET_METADATA_MINIMAL_BALANCE,
+        );
+        await helper.assets.setMetadata(
+          alice,
+          USDT_ASSET_ID,
+          USDT_ASSET_METADATA_NAME,
+          USDT_ASSET_METADATA_DESCRIPTION,
+          USDT_ASSET_METADATA_DECIMALS,
+        );
+      } else {
+        console.log('The USDT asset is already registered on AssetHub');
+      }
 
-      await helper.assets.create(alice, ASSET_ID, alice.address, ASSET_METADATA_MINIMAL_BALANCE);
-      await helper.assets.setMetadata(alice, ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS);
-      await helper.assets.mint(alice, ASSET_ID, alice.address, ASSET_AMOUNT);
+      await helper.assets.mint(
+        alice,
+        USDT_ASSET_ID,
+        alice.address,
+        USDT_ASSET_AMOUNT,
+      );
+
+      const sovereignFundingAmount = 3_500_000_000n;
 
       // funding parachain sovereing account (Parachain: 2095)
       const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);
-      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, fundingAmount);
+      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);
     });
-
 
     await usingPlaygrounds(async (helper) => {
       const location = {
-        V2: {
-          parents: 1,
-          interior: {X3: [
-            {
-              Parachain: STATEMINE_CHAIN,
-            },
-            {
-              PalletInstance: STATEMINE_PALLET_INSTANCE,
-            },
-            {
-              GeneralIndex: ASSET_ID,
-            },
-          ]},
-        },
+        parents: 1,
+        interior: {X3: [
+          {
+            Parachain: STATEMINE_CHAIN,
+          },
+          {
+            PalletInstance: STATEMINE_PALLET_INSTANCE,
+          },
+          {
+            GeneralIndex: USDT_ASSET_ID,
+          },
+        ]},
       };
 
-      const metadata =
-      {
-        name: ASSET_ID,
-        symbol: ASSET_METADATA_NAME,
-        decimals: ASSET_METADATA_DECIMALS,
-        minimalBalance: ASSET_METADATA_MINIMAL_BALANCE,
-      };
-      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
+      if(await helper.foreignAssets.foreignCollectionId(location) == null) {
+        const tokenPrefix = USDT_ASSET_METADATA_NAME;
+        await helper.getSudo().foreignAssets.register(alice, location, 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);
     });
 
-
     // Providing the relay currency to the unique sender account
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
       const destination = {
@@ -189,7 +213,7 @@
                       PalletInstance: STATEMINE_PALLET_INSTANCE,
                     },
                     {
-                      GeneralIndex: ASSET_ID,
+                      GeneralIndex: USDT_ASSET_ID,
                     },
                   ]},
               },
@@ -216,19 +240,17 @@
       expect(balanceStmnBefore > balanceStmnAfter).to.be.true;
 
     });
-
 
     // ensure that asset has been delivered
     await helper.wait.newBlocks(3);
 
-    // expext collection id will be with id 1
-    const free = await helper.ft.getBalance(1, {Substrate: alice.address});
+    const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});
 
     balanceOpalAfter = await helper.balance.getSubstrate(alice.address);
 
     console.log(
       '[Westmint -> Opal] transaction fees on Opal: %s USDT',
-      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, ASSET_METADATA_DECIMALS),
+      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),
     );
     console.log(
       '[Westmint -> Opal] transaction fees on Opal: %s OPL',
@@ -261,16 +283,11 @@
 
     const currencies: [any, bigint][] = [
       [
-        {
-          ForeignAssetId: 0,
-        },
-        //10_000_000_000_000_000n,
+        usdtCollectionId,
         TRANSFER_AMOUNT,
       ],
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         400_000_000_000_000n,
       ],
     ];
@@ -288,7 +305,7 @@
 
       // The USDT token never paid fees. Its amount not changed from begin value.
       // Also check that xcm transfer has been succeeded
-      expect((await helper.assets.account(ASSET_ID, alice.address))! == ASSET_AMOUNT).to.be.true;
+      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;
     });
   });
 
@@ -296,7 +313,7 @@
     const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;
 
     balanceBobBefore = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     // Providing the relay currency to the unique sender account
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
@@ -345,7 +362,7 @@
     await helper.wait.newBlocks(3);
 
     balanceBobAfter = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
     console.log(
@@ -383,9 +400,7 @@
 
     const currencies: any = [
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         50_000_000_000_000_000n,
       ],
     ];
modifiedjs-packages/tests/xcm/xcmQuartz.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcmQuartz.test.ts
+++ b/js-packages/tests/xcm/xcmQuartz.test.ts
@@ -19,9 +19,8 @@
 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';
 
-
 const STATEMINE_PALLET_INSTANCE = 50;
 
 const TRANSFER_AMOUNT = 2000000000000000000000000n;
@@ -39,6 +38,8 @@
 
 const SAFE_XCM_VERSION = 2;
 
+const testHelper = new XcmTestHelper('quartz');
+
 describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {
   let alice: IKeyringPair;
   let bob: IKeyringPair;
@@ -57,6 +58,8 @@
   let balanceBobRelayTokenBefore: bigint;
   let balanceBobRelayTokenAfter: bigint;
 
+  let usdtCollectionId: number;
+  let relayCollectionId: number;
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
@@ -65,6 +68,8 @@
 
       // Set the default version to wrap the first message to other chains.
       await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+
+      relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);
     });
 
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
@@ -74,21 +79,25 @@
     });
 
     await usingStateminePlaygrounds(statemineUrl, async (helper) => {
-      const sovereignFundingAmount = 3_500_000_000n;
+      const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);
+      if(assetInfo == null) {
+        await helper.assets.create(
+          alice,
+          USDT_ASSET_ID,
+          alice.address,
+          USDT_ASSET_METADATA_MINIMAL_BALANCE,
+        );
+        await helper.assets.setMetadata(
+          alice,
+          USDT_ASSET_ID,
+          USDT_ASSET_METADATA_NAME,
+          USDT_ASSET_METADATA_DESCRIPTION,
+          USDT_ASSET_METADATA_DECIMALS,
+        );
+      } else {
+        console.log('The USDT asset is already registered on AssetHub');
+      }
 
-      await helper.assets.create(
-        alice,
-        USDT_ASSET_ID,
-        alice.address,
-        USDT_ASSET_METADATA_MINIMAL_BALANCE,
-      );
-      await helper.assets.setMetadata(
-        alice,
-        USDT_ASSET_ID,
-        USDT_ASSET_METADATA_NAME,
-        USDT_ASSET_METADATA_DESCRIPTION,
-        USDT_ASSET_METADATA_DECIMALS,
-      );
       await helper.assets.mint(
         alice,
         USDT_ASSET_ID,
@@ -96,6 +105,8 @@
         USDT_ASSET_AMOUNT,
       );
 
+      const sovereignFundingAmount = 3_500_000_000n;
+
       // funding parachain sovereing account on Statemine(t).
       // The sovereign account should be created before any action
       // (the assets pallet on Statemine(t) check if the sovereign account exists)
@@ -106,31 +117,29 @@
 
     await usingPlaygrounds(async (helper) => {
       const location = {
-        V2: {
-          parents: 1,
-          interior: {X3: [
-            {
-              Parachain: STATEMINE_CHAIN,
-            },
-            {
-              PalletInstance: STATEMINE_PALLET_INSTANCE,
-            },
-            {
-              GeneralIndex: USDT_ASSET_ID,
-            },
-          ]},
-        },
+        parents: 1,
+        interior: {X3: [
+          {
+            Parachain: STATEMINE_CHAIN,
+          },
+          {
+            PalletInstance: STATEMINE_PALLET_INSTANCE,
+          },
+          {
+            GeneralIndex: USDT_ASSET_ID,
+          },
+        ]},
       };
 
-      const metadata =
-      {
-        name: USDT_ASSET_ID,
-        symbol: USDT_ASSET_METADATA_NAME,
-        decimals: USDT_ASSET_METADATA_DECIMALS,
-        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,
-      };
-      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
+      if(await helper.foreignAssets.foreignCollectionId(location) == null) {
+        const tokenPrefix = USDT_ASSET_METADATA_NAME;
+        await helper.getSudo().foreignAssets.register(alice, location, 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);
     });
 
 
@@ -248,8 +257,7 @@
     // ensure that asset has been delivered
     await helper.wait.newBlocks(3);
 
-    // expext collection id will be with id 1
-    const free = await helper.ft.getBalance(1, {Substrate: alice.address});
+    const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});
 
     balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);
 
@@ -288,15 +296,11 @@
     const relayFee = 400_000_000_000_000n;
     const currencies: [any, bigint][] = [
       [
-        {
-          ForeignAssetId: 0,
-        },
+        usdtCollectionId,
         TRANSFER_AMOUNT,
       ],
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         relayFee,
       ],
     ];
@@ -321,7 +325,7 @@
 
   itSub('Should connect and send Relay token to Quartz', async ({helper}) => {
     balanceBobBefore = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
       const destination = {
@@ -369,7 +373,7 @@
     await helper.wait.newBlocks(3);
 
     balanceBobAfter = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
     const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;
@@ -409,9 +413,7 @@
 
     const currencies: any = [
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         TRANSFER_AMOUNT_RELAY,
       ],
     ];
modifiedjs-packages/tests/xcm/xcmUnique.test.tsdiffbeforeafterboth
before · js-packages/tests/xcm/xcmUnique.test.ts
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';232425const 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;5253describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {54  let alice: IKeyringPair;55  let bob: IKeyringPair;5657  let balanceStmnBefore: bigint;58  let balanceStmnAfter: bigint;5960  let balanceUniqueBefore: bigint;61  let balanceUniqueAfter: bigint;62  let balanceUniqueFinal: bigint;6364  let balanceBobBefore: bigint;65  let balanceBobAfter: bigint;66  let balanceBobFinal: bigint;6768  let balanceBobRelayTokenBefore: bigint;69  let balanceBobRelayTokenAfter: bigint;707172  before(async () => {73    await usingPlaygrounds(async (helper, privateKey) => {74      alice = await privateKey('//Alice');75      bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor7677      // Set the default version to wrap the first message to other chains.78      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);79    });8081    await usingRelayPlaygrounds(relayUrl, async (helper) => {82      // Fund accounts on Statemint83      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);84      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);85    });8687    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {88      const sovereignFundingAmount = 3_500_000_000n;8990      await helper.assets.create(91        alice,92        USDT_ASSET_ID,93        alice.address,94        USDT_ASSET_METADATA_MINIMAL_BALANCE,95      );96      await helper.assets.setMetadata(97        alice,98        USDT_ASSET_ID,99        USDT_ASSET_METADATA_NAME,100        USDT_ASSET_METADATA_DESCRIPTION,101        USDT_ASSET_METADATA_DECIMALS,102      );103      await helper.assets.mint(104        alice,105        USDT_ASSET_ID,106        alice.address,107        USDT_ASSET_AMOUNT,108      );109110      // funding parachain sovereing account on Statemint.111      // The sovereign account should be created before any action112      // (the assets pallet on Statemint check if the sovereign account exists)113      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);114      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);115    });116117118    await usingPlaygrounds(async (helper) => {119      const location = {120        V2: {121          parents: 1,122          interior: {X3: [123            {124              Parachain: STATEMINT_CHAIN,125            },126            {127              PalletInstance: STATEMINT_PALLET_INSTANCE,128            },129            {130              GeneralIndex: USDT_ASSET_ID,131            },132          ]},133        },134      };135136      const metadata =137      {138        name: USDT_ASSET_ID,139        symbol: USDT_ASSET_METADATA_NAME,140        decimals: USDT_ASSET_METADATA_DECIMALS,141        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,142      };143      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);144      balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);145    });146147148    // Providing the relay currency to the unique sender account149    // (fee for USDT XCM are paid in relay tokens)150    await usingRelayPlaygrounds(relayUrl, async (helper) => {151      const destination = {152        V2: {153          parents: 0,154          interior: {X1: {155            Parachain: UNIQUE_CHAIN,156          },157          },158        }};159160      const beneficiary = {161        V2: {162          parents: 0,163          interior: {X1: {164            AccountId32: {165              network: 'Any',166              id: alice.addressRaw,167            },168          }},169        },170      };171172      const assets = {173        V2: [174          {175            id: {176              Concrete: {177                parents: 0,178                interior: 'Here',179              },180            },181            fun: {182              Fungible: TRANSFER_AMOUNT_RELAY,183            },184          },185        ],186      };187188      const feeAssetItem = 0;189190      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');191    });192193  });194195  itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {196    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {197      const dest = {198        V2: {199          parents: 1,200          interior: {X1: {201            Parachain: UNIQUE_CHAIN,202          },203          },204        }};205206      const beneficiary = {207        V2: {208          parents: 0,209          interior: {X1: {210            AccountId32: {211              network: 'Any',212              id: alice.addressRaw,213            },214          }},215        },216      };217218      const assets = {219        V2: [220          {221            id: {222              Concrete: {223                parents: 0,224                interior: {225                  X2: [226                    {227                      PalletInstance: STATEMINT_PALLET_INSTANCE,228                    },229                    {230                      GeneralIndex: USDT_ASSET_ID,231                    },232                  ]},233              },234            },235            fun: {236              Fungible: TRANSFER_AMOUNT,237            },238          },239        ],240      };241242      const feeAssetItem = 0;243244      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);245      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');246247      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);248249      // common good parachain take commission in it native token250      console.log(251        '[Statemint -> Unique] transaction fees on Statemint: %s WND',252        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),253      );254      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;255256    });257258259    // ensure that asset has been delivered260    await helper.wait.newBlocks(3);261262    // expext collection id will be with id 1263    const free = await helper.ft.getBalance(1, {Substrate: alice.address});264265    balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);266267    console.log(268      '[Statemint -> Unique] transaction fees on Unique: %s USDT',269      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),270    );271    console.log(272      '[Statemint -> Unique] transaction fees on Unique: %s UNQ',273      helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),274    );275    // commission has not paid in USDT token276    expect(free).to.be.equal(TRANSFER_AMOUNT);277    // ... and parachain native token278    expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;279  });280281  itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {282    const destination = {283      V2: {284        parents: 1,285        interior: {X2: [286          {287            Parachain: STATEMINT_CHAIN,288          },289          {290            AccountId32: {291              network: 'Any',292              id: alice.addressRaw,293            },294          },295        ]},296      },297    };298299    const relayFee = 400_000_000_000_000n;300    const currencies: [any, bigint][] = [301      [302        {303          ForeignAssetId: 0,304        },305        TRANSFER_AMOUNT,306      ],307      [308        {309          NativeAssetId: 'Parent',310        },311        relayFee,312      ],313    ];314315    const feeItem = 1;316317    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');318319    // the commission has been paid in parachain native token320    balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);321    console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));322    expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;323324    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {325      await helper.wait.newBlocks(3);326327      // The USDT token never paid fees. Its amount not changed from begin value.328      // Also check that xcm transfer has been succeeded329      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;330    });331  });332333  itSub('Should connect and send Relay token to Unique', async ({helper}) => {334    balanceBobBefore = await helper.balance.getSubstrate(bob.address);335    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});336337    await usingRelayPlaygrounds(relayUrl, async (helper) => {338      const destination = {339        V2: {340          parents: 0,341          interior: {X1: {342            Parachain: UNIQUE_CHAIN,343          },344          },345        }};346347      const beneficiary = {348        V2: {349          parents: 0,350          interior: {X1: {351            AccountId32: {352              network: 'Any',353              id: bob.addressRaw,354            },355          }},356        },357      };358359      const assets = {360        V2: [361          {362            id: {363              Concrete: {364                parents: 0,365                interior: 'Here',366              },367            },368            fun: {369              Fungible: TRANSFER_AMOUNT_RELAY,370            },371          },372        ],373      };374375      const feeAssetItem = 0;376377      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');378    });379380    await helper.wait.newBlocks(3);381382    balanceBobAfter = await helper.balance.getSubstrate(bob.address);383    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});384385    const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;386    const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;387    console.log(388      '[Relay (Westend) -> Unique] transaction fees: %s UNQ',389      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),390    );391    console.log(392      '[Relay (Westend) -> Unique] transaction fees: %s WND',393      helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),394    );395    console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);396    expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;397    expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;398  });399400  itSub('Should connect and send Relay token back', async ({helper}) => {401    let relayTokenBalanceBefore: bigint;402    let relayTokenBalanceAfter: bigint;403    await usingRelayPlaygrounds(relayUrl, async (helper) => {404      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);405    });406407    const destination = {408      V2: {409        parents: 1,410        interior: {411          X1:{412            AccountId32: {413              network: 'Any',414              id: bob.addressRaw,415            },416          },417        },418      },419    };420421    const currencies: any = [422      [423        {424          NativeAssetId: 'Parent',425        },426        TRANSFER_AMOUNT_RELAY,427      ],428    ];429430    const feeItem = 0;431432    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');433434    balanceBobFinal = await helper.balance.getSubstrate(bob.address);435    console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));436437    await usingRelayPlaygrounds(relayUrl, async (helper) => {438      await helper.wait.newBlocks(10);439      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);440441      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;442      console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));443      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;444    });445  });446});447448describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {449  let alice: IKeyringPair;450  let randomAccount: IKeyringPair;451452  let balanceUniqueTokenInit: bigint;453  let balanceUniqueTokenMiddle: bigint;454  let balanceUniqueTokenFinal: bigint;455  let balanceAcalaTokenInit: bigint;456  let balanceAcalaTokenMiddle: bigint;457  let balanceAcalaTokenFinal: bigint;458  let balanceUniqueForeignTokenInit: bigint;459  let balanceUniqueForeignTokenMiddle: bigint;460  let balanceUniqueForeignTokenFinal: bigint;461462  // computed by a test transfer from prod Unique to prod Acala.463  // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48464  // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)465  const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;466  const acalaEps = 8n * 10n ** 16n;467468  let acalaBackwardTransferAmount: bigint;469470  before(async () => {471    await usingPlaygrounds(async (helper, privateKey) => {472      alice = await privateKey('//Alice');473      [randomAccount] = await helper.arrange.createAccounts([0n], alice);474475      // Set the default version to wrap the first message to other chains.476      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);477    });478479    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {480      const destination = {481        V2: {482          parents: 1,483          interior: {484            X1: {485              Parachain: UNIQUE_CHAIN,486            },487          },488        },489      };490491      const metadata = {492        name: 'Unique Network',493        symbol: 'UNQ',494        decimals: 18,495        minimalBalance: 1250000000000000000n,496      };497      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>498        hexToString(v.toJSON()['symbol'])) as string[];499500      if(!assets.includes('UNQ')) {501        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);502      } else {503        console.log('UNQ token already registered on Acala assetRegistry pallet');504      }505      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);506      balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);507      balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});508    });509510    await usingPlaygrounds(async (helper) => {511      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);512      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);513    });514  });515516  itSub('Should connect and send UNQ to Acala', async ({helper}) => {517518    const destination = {519      V2: {520        parents: 1,521        interior: {522          X1: {523            Parachain: ACALA_CHAIN,524          },525        },526      },527    };528529    const beneficiary = {530      V2: {531        parents: 0,532        interior: {533          X1: {534            AccountId32: {535              network: 'Any',536              id: randomAccount.addressRaw,537            },538          },539        },540      },541    };542543    const assets = {544      V2: [545        {546          id: {547            Concrete: {548              parents: 0,549              interior: 'Here',550            },551          },552          fun: {553            Fungible: TRANSFER_AMOUNT,554          },555        },556      ],557    };558559    const feeAssetItem = 0;560561    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');562    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);563564    const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;565    console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));566    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;567568    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {569      await helper.wait.newBlocks(3);570571      balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});572      balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);573574      const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;575      const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;576      acalaBackwardTransferAmount = unqIncomeTransfer;577578      const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;579580      console.log(581        '[Unique -> Acala] transaction fees on Acala: %s ACA',582        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),583      );584      console.log(585        '[Unique -> Acala] transaction fees on Acala: %s UNQ',586        helper.util.bigIntToDecimals(acaUnqFees),587      );588      console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));589      expect(acaFees == 0n).to.be.true;590591      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;592593      expect(594        bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,595        'Acala took different income fee, check the Acala foreign asset config',596      ).to.be.true;597    });598  });599600  itSub('Should connect to Acala and send UNQ back', async ({helper}) => {601    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {602      const destination = {603        V2: {604          parents: 1,605          interior: {606            X2: [607              {Parachain: UNIQUE_CHAIN},608              {609                AccountId32: {610                  network: 'Any',611                  id: randomAccount.addressRaw,612                },613              },614            ],615          },616        },617      };618619      const id = {620        ForeignAsset: 0,621      };622623      await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');624      balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);625      balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);626627      const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;628      const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;629630      console.log(631        '[Acala -> Unique] transaction fees on Acala: %s ACA',632        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),633      );634      console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));635636      expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;637      expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;638    });639640    await helper.wait.newBlocks(3);641642    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);643    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;644    expect(actuallyDelivered > 0).to.be.true;645646    console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));647648    const unqFees = acalaBackwardTransferAmount - actuallyDelivered;649    console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));650    expect(unqFees == 0n).to.be.true;651  });652653  itSub('Acala can send only up to its balance', async ({helper}) => {654    // set Acala's sovereign account's balance655    const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);656    const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);657    await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);658659    const moreThanAcalaHas = acalaBalance * 2n;660661    let targetAccountBalance = 0n;662    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);663664    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(665      targetAccount.addressRaw,666      {667        Concrete: {668          parents: 0,669          interior: 'Here',670        },671      },672      moreThanAcalaHas,673    );674675    let maliciousXcmProgramSent: any;676    const maxWaitBlocks = 3;677678    // Try to trick Unique679    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {680      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);681682      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);683    });684685    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash686        && event.outcome.isFailedToTransactAsset);687688    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);689    expect(targetAccountBalance).to.be.equal(0n);690691    // But Acala still can send the correct amount692    const validTransferAmount = acalaBalance / 2n;693    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(694      targetAccount.addressRaw,695      {696        Concrete: {697          parents: 0,698          interior: 'Here',699        },700      },701      validTransferAmount,702    );703704    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {705      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);706    });707708    await helper.wait.newBlocks(maxWaitBlocks);709710    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);711    expect(targetAccountBalance).to.be.equal(validTransferAmount);712  });713714  itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {715    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);716    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);717718    const uniqueMultilocation = {719      V2: {720        parents: 1,721        interior: {722          X1: {723            Parachain: UNIQUE_CHAIN,724          },725        },726      },727    };728729    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(730      targetAccount.addressRaw,731      uniqueAssetId,732      testAmount,733    );734735    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(736      targetAccount.addressRaw,737      {738        Concrete: {739          parents: 0,740          interior: 'Here',741        },742      },743      testAmount,744    );745746    let maliciousXcmProgramFullIdSent: any;747    let maliciousXcmProgramHereIdSent: any;748    const maxWaitBlocks = 3;749750    // Try to trick Unique using full UNQ identification751    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {752      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);753754      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);755    });756757    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash758        && event.outcome.isUntrustedReserveLocation);759760    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);761    expect(accountBalance).to.be.equal(0n);762763    // Try to trick Unique using shortened UNQ identification764    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {765      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);766767      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);768    });769770    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash771        && event.outcome.isUntrustedReserveLocation);772773    accountBalance = await helper.balance.getSubstrate(targetAccount.address);774    expect(accountBalance).to.be.equal(0n);775  });776});777778describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {779  let alice: IKeyringPair;780  let randomAccount: IKeyringPair;781  let unqFees: bigint;782  let balanceUniqueTokenInit: bigint;783  let balanceUniqueTokenMiddle: bigint;784  let balanceUniqueTokenFinal: bigint;785  const maxWaitBlocks = 6;786787  before(async () => {788    await usingPlaygrounds(async (helper, privateKey) => {789      alice = await privateKey('//Alice');790      [randomAccount] = await helper.arrange.createAccounts([0n], alice);791792      // Set the default version to wrap the first message to other chains.793      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);794    });795796    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {797      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))798        .toJSON() as [])799        .map(nToBigInt).length != 0;800      /*801      Check whether the Unique token has been added802      to the whitelist, since an error will occur803      if it is added again. Needed for debugging804      when this test is run multiple times.805      */806      if(isWhitelisted) {807        console.log('UNQ token is already whitelisted on Polkadex');808      } else {809        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);810      }811812      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);813    });814815    await usingPlaygrounds(async (helper) => {816      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);817      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);818    });819  });820821  itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {822823    const destination = {824      V2: {825        parents: 1,826        interior: {827          X1: {828            Parachain: POLKADEX_CHAIN,829          },830        },831      },832    };833834    const beneficiary = {835      V2: {836        parents: 0,837        interior: {838          X1: {839            AccountId32: {840              network: 'Any',841              id: randomAccount.addressRaw,842            },843          },844        },845      },846    };847848    const assets = {849      V2: [850        {851          id: {852            Concrete: {853              parents: 0,854              interior: 'Here',855            },856          },857          fun: {858            Fungible: TRANSFER_AMOUNT,859          },860        },861      ],862    };863864    const feeAssetItem = 0;865866    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');867    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);868    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);869870    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;871    console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));872    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;873874    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {875      /*876        Since only the parachain part of the Polkadex877        infrastructure is launched (without their878        solochain validators), processing incoming879        assets will lead to an error.880        This error indicates that the Polkadex chain881        received a message from the Unique network,882        since the hash is being checked to ensure883        it matches what was sent.884      */885      await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);886    });887  });888889890  itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {891892    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(893      randomAccount.addressRaw,894      uniqueAssetId,895      TRANSFER_AMOUNT,896    );897898    let xcmProgramSent: any;899900901    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {902      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);903904      xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);905    });906907    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);908909    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);910911    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);912  });913914  itSub('Polkadex can send only up to its balance', async ({helper}) => {915    const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);916    const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);917    await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);918    const moreThanPolkadexHas = 2n * polkadexBalance;919920    const targetAccount = helper.arrange.createEmptyAccount();921922    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(923      targetAccount.addressRaw,924      {925        Concrete: {926          parents: 0,927          interior: 'Here',928        },929      },930      moreThanPolkadexHas,931    );932933    let maliciousXcmProgramSent: any;934935936    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {937      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);938939      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);940    });941942    await expectFailedToTransact(helper, maliciousXcmProgramSent);943944    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);945    expect(targetAccountBalance).to.be.equal(0n);946  });947948  itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {949    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);950    const targetAccount = helper.arrange.createEmptyAccount();951952    const uniqueMultilocation = {953      V2: {954        parents: 1,955        interior: {956          X1: {957            Parachain: UNIQUE_CHAIN,958          },959        },960      },961    };962963    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(964      targetAccount.addressRaw,965      uniqueAssetId,966      testAmount,967    );968969    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(970      targetAccount.addressRaw,971      {972        Concrete: {973          parents: 0,974          interior: 'Here',975        },976      },977      testAmount,978    );979980    let maliciousXcmProgramFullIdSent: any;981    let maliciousXcmProgramHereIdSent: any;982    const maxWaitBlocks = 3;983984    // Try to trick Unique using full UNQ identification985    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {986      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);987988      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);989    });990991    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);992993    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);994    expect(accountBalance).to.be.equal(0n);995996    // Try to trick Unique using shortened UNQ identification997    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {998      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);9991000      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1001    });10021003    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10041005    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1006    expect(accountBalance).to.be.equal(0n);1007  });1008});10091010// These tests are relevant only when1011// the the corresponding foreign assets are not registered1012describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1013  let alice: IKeyringPair;1014  let alith: IKeyringPair;10151016  const testAmount = 100_000_000_000n;1017  let uniqueParachainJunction;1018  let uniqueAccountJunction;10191020  let uniqueParachainMultilocation: any;1021  let uniqueAccountMultilocation: any;1022  let uniqueCombinedMultilocation: any;1023  let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210241025  let messageSent: any;10261027  const maxWaitBlocks = 3;10281029  before(async () => {1030    await usingPlaygrounds(async (helper, privateKey) => {1031      alice = await privateKey('//Alice');10321033      uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1034      uniqueAccountJunction = {1035        AccountId32: {1036          network: 'Any',1037          id: alice.addressRaw,1038        },1039      };10401041      uniqueParachainMultilocation = {1042        V2: {1043          parents: 1,1044          interior: {1045            X1: uniqueParachainJunction,1046          },1047        },1048      };10491050      uniqueAccountMultilocation = {1051        V2: {1052          parents: 0,1053          interior: {1054            X1: uniqueAccountJunction,1055          },1056        },1057      };10581059      uniqueCombinedMultilocation = {1060        V2: {1061          parents: 1,1062          interior: {1063            X2: [uniqueParachainJunction, uniqueAccountJunction],1064          },1065        },1066      };10671068      uniqueCombinedMultilocationAcala = {1069        V2: {1070          parents: 1,1071          interior: {1072            X2: [uniqueParachainJunction, uniqueAccountJunction],1073          },1074        },1075      };10761077      // Set the default version to wrap the first message to other chains.1078      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1079    });10801081    // eslint-disable-next-line require-await1082    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1083      alith = helper.account.alithAccount();1084    });1085  });1086108710881089  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1090    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1091      const id = {1092        Token: 'ACA',1093      };1094      const destination = uniqueCombinedMultilocationAcala;1095      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');10961097      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1098    });10991100    await expectFailedToTransact(helper, messageSent);1101  });11021103  itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1104    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1105      const id = 'SelfReserve';1106      const destination = uniqueCombinedMultilocation;1107      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11081109      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1110    });11111112    await expectFailedToTransact(helper, messageSent);1113  });11141115  itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1116    await usingAstarPlaygrounds(astarUrl, async (helper) => {1117      const destinationParachain = uniqueParachainMultilocation;1118      const beneficiary = uniqueAccountMultilocation;1119      const assets = {1120        V2: [{1121          id: {1122            Concrete: {1123              parents: 0,1124              interior: 'Here',1125            },1126          },1127          fun: {1128            Fungible: testAmount,1129          },1130        }],1131      };1132      const feeAssetItem = 0;11331134      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1135        destinationParachain,1136        beneficiary,1137        assets,1138        feeAssetItem,1139      ]);11401141      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1142    });11431144    await expectFailedToTransact(helper, messageSent);1145  });11461147  itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11481149    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1150      helper.arrange.createEmptyAccount().addressRaw,1151      {1152        Concrete: {1153          parents: 1,1154          interior: {1155            X1: {1156              Parachain: POLKADEX_CHAIN,1157            },1158          },1159        },1160      },1161      testAmount,1162    );11631164    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1165      await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1166      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1167    });11681169    await expectFailedToTransact(helper, messageSent);1170  });1171});11721173describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1174  // Unique constants1175  let alice: IKeyringPair;1176  let uniqueAssetLocation;11771178  let randomAccountUnique: IKeyringPair;1179  let randomAccountMoonbeam: IKeyringPair;11801181  // Moonbeam constants1182  let assetId: string;11831184  const uniqueAssetMetadata = {1185    name: 'xcUnique',1186    symbol: 'xcUNQ',1187    decimals: 18,1188    isFrozen: false,1189    minimalBalance: 1n,1190  };11911192  let balanceUniqueTokenInit: bigint;1193  let balanceUniqueTokenMiddle: bigint;1194  let balanceUniqueTokenFinal: bigint;1195  let balanceForeignUnqTokenInit: bigint;1196  let balanceForeignUnqTokenMiddle: bigint;1197  let balanceForeignUnqTokenFinal: bigint;1198  let balanceGlmrTokenInit: bigint;1199  let balanceGlmrTokenMiddle: bigint;1200  let balanceGlmrTokenFinal: bigint;12011202  before(async () => {1203    await usingPlaygrounds(async (helper, privateKey) => {1204      alice = await privateKey('//Alice');1205      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12061207      balanceForeignUnqTokenInit = 0n;12081209      // Set the default version to wrap the first message to other chains.1210      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1211    });12121213    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1214      const alithAccount = helper.account.alithAccount();1215      const baltatharAccount = helper.account.baltatharAccount();1216      const dorothyAccount = helper.account.dorothyAccount();12171218      randomAccountMoonbeam = helper.account.create();12191220      // >>> Sponsoring Dorothy >>>1221      console.log('Sponsoring Dorothy.......');1222      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1223      console.log('Sponsoring Dorothy.......DONE');1224      // <<< Sponsoring Dorothy <<<12251226      uniqueAssetLocation = {1227        XCM: {1228          parents: 1,1229          interior: {X1: {Parachain: UNIQUE_CHAIN}},1230        },1231      };1232      const existentialDeposit = 1n;1233      const isSufficient = true;1234      const unitsPerSecond = 1n;1235      const numAssetsWeightHint = 0;12361237      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1238        console.log('Unique asset is already registered on MoonBeam');1239      } else {1240        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1241          location: uniqueAssetLocation,1242          metadata: uniqueAssetMetadata,1243          existentialDeposit,1244          isSufficient,1245          unitsPerSecond,1246          numAssetsWeightHint,1247        });12481249        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12501251        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1252      }12531254      // >>> Acquire Unique AssetId Info on Moonbeam >>>1255      console.log('Acquire Unique AssetId Info on Moonbeam.......');12561257      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1258      console.log('UNQ asset ID is %s', assetId);1259      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1260      // >>> Acquire Unique AssetId Info on Moonbeam >>>12611262      // >>> Sponsoring random Account >>>1263      console.log('Sponsoring random Account.......');1264      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1265      console.log('Sponsoring random Account.......DONE');1266      // <<< Sponsoring random Account <<<12671268      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1269    });12701271    await usingPlaygrounds(async (helper) => {1272      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1273      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1274    });1275  });12761277  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1278    const currencyId = {1279      NativeAssetId: 'Here',1280    };1281    const dest = {1282      V2: {1283        parents: 1,1284        interior: {1285          X2: [1286            {Parachain: MOONBEAM_CHAIN},1287            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1288          ],1289        },1290      },1291    };1292    const amount = TRANSFER_AMOUNT;12931294    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');12951296    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1297    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;12981299    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1300    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1301    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13021303    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1304      await helper.wait.newBlocks(3);1305      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13061307      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1308      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1309      expect(glmrFees == 0n).to.be.true;13101311      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13121313      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1314      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1315      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1316    });1317  });13181319  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1320    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1321      const asset = {1322        V2: {1323          id: {1324            Concrete: {1325              parents: 1,1326              interior: {1327                X1: {Parachain: UNIQUE_CHAIN},1328              },1329            },1330          },1331          fun: {1332            Fungible: TRANSFER_AMOUNT,1333          },1334        },1335      };1336      const destination = {1337        V2: {1338          parents: 1,1339          interior: {1340            X2: [1341              {Parachain: UNIQUE_CHAIN},1342              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1343            ],1344          },1345        },1346      };13471348      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13491350      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13511352      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1353      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1354      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13551356      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13571358      expect(unqRandomAccountAsset).to.be.null;13591360      balanceForeignUnqTokenFinal = 0n;13611362      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1363      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1364      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1365    });13661367    await helper.wait.newBlocks(3);13681369    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1370    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1371    expect(actuallyDelivered > 0).to.be.true;13721373    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13741375    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1376    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1377    expect(unqFees == 0n).to.be.true;1378  });13791380  itSub('Moonbeam can send only up to its balance', async ({helper}) => {1381    // set Moonbeam's sovereign account's balance1382    const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1383    const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1384    await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);13851386    const moreThanMoonbeamHas = moonbeamBalance * 2n;13871388    let targetAccountBalance = 0n;1389    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);13901391    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1392      targetAccount.addressRaw,1393      {1394        Concrete: {1395          parents: 0,1396          interior: 'Here',1397        },1398      },1399      moreThanMoonbeamHas,1400    );14011402    let maliciousXcmProgramSent: any;1403    const maxWaitBlocks = 3;14041405    // Try to trick Unique1406    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1407      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14081409      // Needed to bypass the call filter.1410      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1411      await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14121413      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1414    });14151416    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1417        && event.outcome.isFailedToTransactAsset);14181419    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1420    expect(targetAccountBalance).to.be.equal(0n);14211422    // But Moonbeam still can send the correct amount1423    const validTransferAmount = moonbeamBalance / 2n;1424    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1425      targetAccount.addressRaw,1426      {1427        Concrete: {1428          parents: 0,1429          interior: 'Here',1430        },1431      },1432      validTransferAmount,1433    );14341435    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1436      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14371438      // Needed to bypass the call filter.1439      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1440      await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1441    });14421443    await helper.wait.newBlocks(maxWaitBlocks);14441445    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1446    expect(targetAccountBalance).to.be.equal(validTransferAmount);1447  });14481449  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1450    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1451    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14521453    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1454      targetAccount.addressRaw,1455      uniqueAssetId,1456      testAmount,1457    );14581459    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1460      targetAccount.addressRaw,1461      {1462        Concrete: {1463          parents: 0,1464          interior: 'Here',1465        },1466      },1467      testAmount,1468    );14691470    let maliciousXcmProgramFullIdSent: any;1471    let maliciousXcmProgramHereIdSent: any;1472    const maxWaitBlocks = 3;14731474    // Try to trick Unique using full UNQ identification1475    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1476      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);14771478      // Needed to bypass the call filter.1479      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1480      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);14811482      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1483    });14841485    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1486        && event.outcome.isUntrustedReserveLocation);14871488    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1489    expect(accountBalance).to.be.equal(0n);14901491    // Try to trick Unique using shortened UNQ identification1492    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1493      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);14941495      // Needed to bypass the call filter.1496      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1497      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);14981499      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1500    });15011502    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1503        && event.outcome.isUntrustedReserveLocation);15041505    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1506    expect(accountBalance).to.be.equal(0n);1507  });1508});15091510describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1511  let alice: IKeyringPair;1512  let randomAccount: IKeyringPair;15131514  const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar1515  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar15161517  // Unique -> Astar1518  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1519  const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar1520  const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1521  const unqToAstarArrived = 9_962_196_000_000_000_000n; // 9.962 ... UNQ, Astar takes a commision in foreign tokens15221523  // Astar -> Unique1524  const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1525  const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.962_219_600_000_000_000n UNQ15261527  let balanceAfterUniqueToAstarXCM: bigint;15281529  before(async () => {1530    await usingPlaygrounds(async (helper, privateKey) => {1531      alice = await privateKey('//Alice');1532      [randomAccount] = await helper.arrange.createAccounts([100n], alice);1533      console.log('randomAccount', randomAccount.address);15341535      // Set the default version to wrap the first message to other chains.1536      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1537    });15381539    await usingAstarPlaygrounds(astarUrl, async (helper) => {1540      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1541        console.log('1. Create foreign asset and metadata');1542        await helper.getSudo().assets.forceCreate(1543          alice,1544          UNQ_ASSET_ID_ON_ASTAR,1545          alice.address,1546          UNQ_MINIMAL_BALANCE_ON_ASTAR,1547        );15481549        await helper.assets.setMetadata(1550          alice,1551          UNQ_ASSET_ID_ON_ASTAR,1552          'Unique Network',1553          'UNQ',1554          Number(UNQ_DECIMALS),1555        );15561557        console.log('2. Register asset location on Astar');1558        const assetLocation = {1559          V2: {1560            parents: 1,1561            interior: {1562              X1: {1563                Parachain: UNIQUE_CHAIN,1564              },1565            },1566          },1567        };15681569        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);15701571        console.log('3. Set UNQ payment for XCM execution on Astar');1572        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1573      } else {1574        console.log('UNQ is already registered on Astar');1575      }1576      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1577      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1578    });1579  });15801581  itSub('Should connect and send UNQ to Astar', async ({helper}) => {1582    const destination = {1583      V2: {1584        parents: 1,1585        interior: {1586          X1: {1587            Parachain: ASTAR_CHAIN,1588          },1589        },1590      },1591    };15921593    const beneficiary = {1594      V2: {1595        parents: 0,1596        interior: {1597          X1: {1598            AccountId32: {1599              network: 'Any',1600              id: randomAccount.addressRaw,1601            },1602          },1603        },1604      },1605    };16061607    const assets = {1608      V2: [1609        {1610          id: {1611            Concrete: {1612              parents: 0,1613              interior: 'Here',1614            },1615          },1616          fun: {1617            Fungible: unqToAstarTransferred,1618          },1619        },1620      ],1621    };16221623    // Initial balance is 100 UNQ1624    const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1625    console.log(`Initial balance is: ${balanceBefore}`);16261627    const feeAssetItem = 0;1628    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16291630    // Balance after reserve transfer is less than 901631    balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1632    console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1633    console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1634    expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16351636    await usingAstarPlaygrounds(astarUrl, async (helper) => {1637      await helper.wait.newBlocks(3);1638      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1639      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16401641      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1642      console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);16431644      expect(xcUNQbalance).to.eq(unqToAstarArrived);1645      // Astar balance does not changed1646      expect(astarBalance).to.eq(astarInitialBalance);1647    });1648  });16491650  itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1651    await usingAstarPlaygrounds(astarUrl, async (helper) => {1652      const destination = {1653        V2: {1654          parents: 1,1655          interior: {1656            X1: {1657              Parachain: UNIQUE_CHAIN,1658            },1659          },1660        },1661      };16621663      const beneficiary = {1664        V2: {1665          parents: 0,1666          interior: {1667            X1: {1668              AccountId32: {1669                network: 'Any',1670                id: randomAccount.addressRaw,1671              },1672            },1673          },1674        },1675      };16761677      const assets = {1678        V2: [1679          {1680            id: {1681              Concrete: {1682                parents: 1,1683                interior: {1684                  X1: {1685                    Parachain: UNIQUE_CHAIN,1686                  },1687                },1688              },1689            },1690            fun: {1691              Fungible: unqFromAstarTransfered,1692            },1693          },1694        ],1695      };16961697      // Initial balance is 1 ASTR1698      const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1699      console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1700      expect(balanceASTRbefore).to.eq(astarInitialBalance);17011702      const feeAssetItem = 0;1703      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1704      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17051706      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1707      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1708      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17091710      // Assert: xcUNQ balance correctly decreased1711      expect(xcUNQbalance).to.eq(unqOnAstarLeft);1712      // Assert: ASTR balance is 0.996...1713      expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1714    });17151716    await helper.wait.newBlocks(3);1717    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1718    console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1719    expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1720  });17211722  itSub('Astar can send only up to its balance', async ({helper}) => {1723    // set Astar's sovereign account's balance1724    const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1725    const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1726    await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17271728    const moreThanAstarHas = astarBalance * 2n;17291730    let targetAccountBalance = 0n;1731    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17321733    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1734      targetAccount.addressRaw,1735      {1736        Concrete: {1737          parents: 0,1738          interior: 'Here',1739        },1740      },1741      moreThanAstarHas,1742    );17431744    let maliciousXcmProgramSent: any;1745    const maxWaitBlocks = 3;17461747    // Try to trick Unique1748    await usingAstarPlaygrounds(astarUrl, async (helper) => {1749      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17501751      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1752    });17531754    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1755        && event.outcome.isFailedToTransactAsset);17561757    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1758    expect(targetAccountBalance).to.be.equal(0n);17591760    // But Astar still can send the correct amount1761    const validTransferAmount = astarBalance / 2n;1762    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1763      targetAccount.addressRaw,1764      {1765        Concrete: {1766          parents: 0,1767          interior: 'Here',1768        },1769      },1770      validTransferAmount,1771    );17721773    await usingAstarPlaygrounds(astarUrl, async (helper) => {1774      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1775    });17761777    await helper.wait.newBlocks(maxWaitBlocks);17781779    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1780    expect(targetAccountBalance).to.be.equal(validTransferAmount);1781  });17821783  itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1784    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1785    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);17861787    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1788      targetAccount.addressRaw,1789      uniqueAssetId,1790      testAmount,1791    );17921793    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1794      targetAccount.addressRaw,1795      {1796        Concrete: {1797          parents: 0,1798          interior: 'Here',1799        },1800      },1801      testAmount,1802    );18031804    let maliciousXcmProgramFullIdSent: any;1805    let maliciousXcmProgramHereIdSent: any;1806    const maxWaitBlocks = 3;18071808    // Try to trick Unique using full UNQ identification1809    await usingAstarPlaygrounds(astarUrl, async (helper) => {1810      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18111812      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1813    });18141815    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1816        && event.outcome.isUntrustedReserveLocation);18171818    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1819    expect(accountBalance).to.be.equal(0n);18201821    // Try to trick Unique using shortened UNQ identification1822    await usingAstarPlaygrounds(astarUrl, async (helper) => {1823      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18241825      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1826    });18271828    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1829        && event.outcome.isUntrustedReserveLocation);18301831    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1832    expect(accountBalance).to.be.equal(0n);1833  });1834});
after · js-packages/tests/xcm/xcmUnique.test.ts
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';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 = {1282      NativeAssetId: 'Here',1283    };1284    const dest = {1285      V2: {1286        parents: 1,1287        interior: {1288          X2: [1289            {Parachain: MOONBEAM_CHAIN},1290            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1291          ],1292        },1293      },1294    };1295    const amount = TRANSFER_AMOUNT;12961297    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');12981299    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1300    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13011302    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1303    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1304    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13051306    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1307      await helper.wait.newBlocks(3);1308      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13091310      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1311      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1312      expect(glmrFees == 0n).to.be.true;13131314      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13151316      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1317      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1318      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1319    });1320  });13211322  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1323    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1324      const asset = {1325        V2: {1326          id: {1327            Concrete: {1328              parents: 1,1329              interior: {1330                X1: {Parachain: UNIQUE_CHAIN},1331              },1332            },1333          },1334          fun: {1335            Fungible: TRANSFER_AMOUNT,1336          },1337        },1338      };1339      const destination = {1340        V2: {1341          parents: 1,1342          interior: {1343            X2: [1344              {Parachain: UNIQUE_CHAIN},1345              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1346            ],1347          },1348        },1349      };13501351      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13521353      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13541355      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1356      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1357      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13581359      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13601361      expect(unqRandomAccountAsset).to.be.null;13621363      balanceForeignUnqTokenFinal = 0n;13641365      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1366      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1367      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1368    });13691370    await helper.wait.newBlocks(3);13711372    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1373    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1374    expect(actuallyDelivered > 0).to.be.true;13751376    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13771378    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1379    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1380    expect(unqFees == 0n).to.be.true;1381  });13821383  itSub('Moonbeam can send only up to its balance', async ({helper}) => {1384    // set Moonbeam's sovereign account's balance1385    const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1386    const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1387    await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);13881389    const moreThanMoonbeamHas = moonbeamBalance * 2n;13901391    let targetAccountBalance = 0n;1392    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);13931394    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1395      targetAccount.addressRaw,1396      {1397        Concrete: {1398          parents: 0,1399          interior: 'Here',1400        },1401      },1402      moreThanMoonbeamHas,1403    );14041405    let maliciousXcmProgramSent: any;1406    const maxWaitBlocks = 3;14071408    // Try to trick Unique1409    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1410      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14111412      // Needed to bypass the call filter.1413      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1414      await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14151416      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1417    });14181419    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1420        && event.outcome.isFailedToTransactAsset);14211422    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1423    expect(targetAccountBalance).to.be.equal(0n);14241425    // But Moonbeam still can send the correct amount1426    const validTransferAmount = moonbeamBalance / 2n;1427    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1428      targetAccount.addressRaw,1429      {1430        Concrete: {1431          parents: 0,1432          interior: 'Here',1433        },1434      },1435      validTransferAmount,1436    );14371438    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1439      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14401441      // Needed to bypass the call filter.1442      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1443      await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1444    });14451446    await helper.wait.newBlocks(maxWaitBlocks);14471448    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1449    expect(targetAccountBalance).to.be.equal(validTransferAmount);1450  });14511452  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1453    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1454    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14551456    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1457      targetAccount.addressRaw,1458      uniqueAssetId,1459      testAmount,1460    );14611462    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1463      targetAccount.addressRaw,1464      {1465        Concrete: {1466          parents: 0,1467          interior: 'Here',1468        },1469      },1470      testAmount,1471    );14721473    let maliciousXcmProgramFullIdSent: any;1474    let maliciousXcmProgramHereIdSent: any;1475    const maxWaitBlocks = 3;14761477    // Try to trick Unique using full UNQ identification1478    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1479      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);14801481      // Needed to bypass the call filter.1482      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1483      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);14841485      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1486    });14871488    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1489        && event.outcome.isUntrustedReserveLocation);14901491    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1492    expect(accountBalance).to.be.equal(0n);14931494    // Try to trick Unique using shortened UNQ identification1495    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1496      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);14971498      // Needed to bypass the call filter.1499      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1500      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15011502      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1503    });15041505    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1506        && event.outcome.isUntrustedReserveLocation);15071508    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1509    expect(accountBalance).to.be.equal(0n);1510  });1511});15121513describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1514  let alice: IKeyringPair;1515  let randomAccount: IKeyringPair;15161517  const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar1518  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar15191520  // Unique -> Astar1521  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1522  const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar1523  const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1524  const unqToAstarArrived = 9_962_196_000_000_000_000n; // 9.962 ... UNQ, Astar takes a commision in foreign tokens15251526  // Astar -> Unique1527  const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1528  const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.962_219_600_000_000_000n UNQ15291530  let balanceAfterUniqueToAstarXCM: bigint;15311532  before(async () => {1533    await usingPlaygrounds(async (helper, privateKey) => {1534      alice = await privateKey('//Alice');1535      [randomAccount] = await helper.arrange.createAccounts([100n], alice);1536      console.log('randomAccount', randomAccount.address);15371538      // Set the default version to wrap the first message to other chains.1539      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1540    });15411542    await usingAstarPlaygrounds(astarUrl, async (helper) => {1543      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1544        console.log('1. Create foreign asset and metadata');1545        await helper.getSudo().assets.forceCreate(1546          alice,1547          UNQ_ASSET_ID_ON_ASTAR,1548          alice.address,1549          UNQ_MINIMAL_BALANCE_ON_ASTAR,1550        );15511552        await helper.assets.setMetadata(1553          alice,1554          UNQ_ASSET_ID_ON_ASTAR,1555          'Unique Network',1556          'UNQ',1557          Number(UNQ_DECIMALS),1558        );15591560        console.log('2. Register asset location on Astar');1561        const assetLocation = {1562          V2: {1563            parents: 1,1564            interior: {1565              X1: {1566                Parachain: UNIQUE_CHAIN,1567              },1568            },1569          },1570        };15711572        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);15731574        console.log('3. Set UNQ payment for XCM execution on Astar');1575        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1576      } else {1577        console.log('UNQ is already registered on Astar');1578      }1579      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1580      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1581    });1582  });15831584  itSub('Should connect and send UNQ to Astar', async ({helper}) => {1585    const destination = {1586      V2: {1587        parents: 1,1588        interior: {1589          X1: {1590            Parachain: ASTAR_CHAIN,1591          },1592        },1593      },1594    };15951596    const beneficiary = {1597      V2: {1598        parents: 0,1599        interior: {1600          X1: {1601            AccountId32: {1602              network: 'Any',1603              id: randomAccount.addressRaw,1604            },1605          },1606        },1607      },1608    };16091610    const assets = {1611      V2: [1612        {1613          id: {1614            Concrete: {1615              parents: 0,1616              interior: 'Here',1617            },1618          },1619          fun: {1620            Fungible: unqToAstarTransferred,1621          },1622        },1623      ],1624    };16251626    // Initial balance is 100 UNQ1627    const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1628    console.log(`Initial balance is: ${balanceBefore}`);16291630    const feeAssetItem = 0;1631    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16321633    // Balance after reserve transfer is less than 901634    balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1635    console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1636    console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1637    expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16381639    await usingAstarPlaygrounds(astarUrl, async (helper) => {1640      await helper.wait.newBlocks(3);1641      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1642      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16431644      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1645      console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);16461647      expect(xcUNQbalance).to.eq(unqToAstarArrived);1648      // Astar balance does not changed1649      expect(astarBalance).to.eq(astarInitialBalance);1650    });1651  });16521653  itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1654    await usingAstarPlaygrounds(astarUrl, async (helper) => {1655      const destination = {1656        V2: {1657          parents: 1,1658          interior: {1659            X1: {1660              Parachain: UNIQUE_CHAIN,1661            },1662          },1663        },1664      };16651666      const beneficiary = {1667        V2: {1668          parents: 0,1669          interior: {1670            X1: {1671              AccountId32: {1672                network: 'Any',1673                id: randomAccount.addressRaw,1674              },1675            },1676          },1677        },1678      };16791680      const assets = {1681        V2: [1682          {1683            id: {1684              Concrete: {1685                parents: 1,1686                interior: {1687                  X1: {1688                    Parachain: UNIQUE_CHAIN,1689                  },1690                },1691              },1692            },1693            fun: {1694              Fungible: unqFromAstarTransfered,1695            },1696          },1697        ],1698      };16991700      // Initial balance is 1 ASTR1701      const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1702      console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1703      expect(balanceASTRbefore).to.eq(astarInitialBalance);17041705      const feeAssetItem = 0;1706      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1707      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17081709      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1710      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1711      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17121713      // Assert: xcUNQ balance correctly decreased1714      expect(xcUNQbalance).to.eq(unqOnAstarLeft);1715      // Assert: ASTR balance is 0.996...1716      expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1717    });17181719    await helper.wait.newBlocks(3);1720    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1721    console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1722    expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1723  });17241725  itSub('Astar can send only up to its balance', async ({helper}) => {1726    // set Astar's sovereign account's balance1727    const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1728    const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1729    await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17301731    const moreThanAstarHas = astarBalance * 2n;17321733    let targetAccountBalance = 0n;1734    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17351736    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1737      targetAccount.addressRaw,1738      {1739        Concrete: {1740          parents: 0,1741          interior: 'Here',1742        },1743      },1744      moreThanAstarHas,1745    );17461747    let maliciousXcmProgramSent: any;1748    const maxWaitBlocks = 3;17491750    // Try to trick Unique1751    await usingAstarPlaygrounds(astarUrl, async (helper) => {1752      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17531754      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1755    });17561757    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1758        && event.outcome.isFailedToTransactAsset);17591760    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1761    expect(targetAccountBalance).to.be.equal(0n);17621763    // But Astar still can send the correct amount1764    const validTransferAmount = astarBalance / 2n;1765    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1766      targetAccount.addressRaw,1767      {1768        Concrete: {1769          parents: 0,1770          interior: 'Here',1771        },1772      },1773      validTransferAmount,1774    );17751776    await usingAstarPlaygrounds(astarUrl, async (helper) => {1777      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1778    });17791780    await helper.wait.newBlocks(maxWaitBlocks);17811782    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1783    expect(targetAccountBalance).to.be.equal(validTransferAmount);1784  });17851786  itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1787    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1788    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);17891790    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1791      targetAccount.addressRaw,1792      uniqueAssetId,1793      testAmount,1794    );17951796    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1797      targetAccount.addressRaw,1798      {1799        Concrete: {1800          parents: 0,1801          interior: 'Here',1802        },1803      },1804      testAmount,1805    );18061807    let maliciousXcmProgramFullIdSent: any;1808    let maliciousXcmProgramHereIdSent: any;1809    const maxWaitBlocks = 3;18101811    // Try to trick Unique using full UNQ identification1812    await usingAstarPlaygrounds(astarUrl, async (helper) => {1813      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18141815      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1816    });18171818    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1819        && event.outcome.isUntrustedReserveLocation);18201821    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1822    expect(accountBalance).to.be.equal(0n);18231824    // Try to trick Unique using shortened UNQ identification1825    await usingAstarPlaygrounds(astarUrl, async (helper) => {1826      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18271828      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1829    });18301831    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1832        && event.outcome.isUntrustedReserveLocation);18331834    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1835    expect(accountBalance).to.be.equal(0n);1836  });1837});