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

difftreelog

refactor xcm Unq tests

PraetorP2023-09-26parent: #10b8180.patch.diff
in: master

3 files changed

modifiedtests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -17,98 +17,24 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import config from '../config';
 import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';
-import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import {Event} from '../util/playgrounds/unique.dev';
 import {nToBigInt} from '@polkadot/util';
 import {hexToString} from '@polkadot/util';
-
-const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
-const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);
-const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);
-const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
-const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
-
-
+import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
 
-const acalaUrl = config.acalaUrl;
-const moonbeamUrl = config.moonbeamUrl;
-const astarUrl = config.astarUrl;
-const polkadexUrl = config.polkadexUrl;
 
-const ASTAR_DECIMALS = 18n;
-const UNQ_DECIMALS = 18n;
-
 const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
 const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
 const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
 const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
 const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
-
-const SAFE_XCM_VERSION = 2;
-const maxWaitBlocks = 6;
-
-const uniqueMultilocation = {
-  V2: {
-    parents: 1,
-    interior: {
-      X1: {
-        Parachain: UNIQUE_CHAIN,
-      },
-    },
-  },
-};
 
 let balanceUniqueTokenInit: bigint;
 let balanceUniqueTokenMiddle: bigint;
 let balanceUniqueTokenFinal: bigint;
 let unqFees: bigint;
-
-const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
-  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
-        && event.outcome.isFailedToTransactAsset);
-};
-const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {
-  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
-         && event.outcome.isUntrustedReserveLocation);
-};
 
-const NETWORKS = {
-  acala: usingAcalaPlaygrounds,
-  astar: usingAstarPlaygrounds,
-  polkadex: usingPolkadexPlaygrounds,
-  moonbeam: usingMoonbeamPlaygrounds,
-} as const;
 
-function mapToChainId(networkName: keyof typeof NETWORKS) {
-  switch (networkName) {
-    case 'acala':
-      return ACALA_CHAIN;
-    case 'astar':
-      return ASTAR_CHAIN;
-    case 'moonbeam':
-      return MOONBEAM_CHAIN;
-    case 'polkadex':
-      return POLKADEX_CHAIN;
-  }
-}
-
-function mapToChainUrl(networkName: keyof typeof NETWORKS): string {
-  switch (networkName) {
-    case 'acala':
-      return acalaUrl;
-    case 'astar':
-      return astarUrl;
-    case 'moonbeam':
-      return moonbeamUrl;
-    case 'polkadex':
-      return polkadexUrl;
-  }
-}
-
-function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {
-  return NETWORKS[name];
-}
-
-
 async function genericSendUnqTo(
   networkName: keyof typeof NETWORKS,
   randomAccount: IKeyringPair,
@@ -227,13 +153,13 @@
 
     await targetPlayground(networkUrl, async (helper) => {
       if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, xcmProgram);
+        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);
         xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       } else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, xcmProgram]);
+        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);
         // Needed to bypass the call filter.
         const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal('sending MoonBeam -> Unique via XCM program', batchCall);
+        await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
         xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       }
     });
@@ -279,13 +205,13 @@
 
     await targetPlayground(networkUrl, async (helper) => {
       if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgram);
+        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);
         maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       } else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);
+        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
         // Needed to bypass the call filter.
         const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal('sending MoonBeam -> Unique via XCM program', batchCall);
+        await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
         maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       }
     });
@@ -338,16 +264,15 @@
     // Try to trick Unique using full UNQ identification
     await targetPlayground(networkUrl, async (helper) => {
       if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgramFullId);
+        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
         maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       }
       // Moonbeam case
       else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);
-
+        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
         // Needed to bypass the call filter.
         const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);
+        await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);
 
         maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       }
@@ -362,15 +287,14 @@
     // Try to trick Unique using shortened UNQ identification
     await targetPlayground(networkUrl, async (helper) => {
       if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgramHereId);
+        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
         maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       }
       else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);
-
+        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
         // Needed to bypass the call filter.
         const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);
+        await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);
 
         maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       }
@@ -405,19 +329,17 @@
     );
     await targetPlayground(networkUrl, async (helper) => {
       if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueMultilocation, maliciousXcmProgramFullId);
+        await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
         messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       } else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);
-
+        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
         // Needed to bypass the call filter.
         const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal('sending native tokens to the Unique via fast democracy', batchCall);
+        await helper.fastDemocracy.executeProposal(`${netwokrName} sending native tokens to the Unique via fast democracy`, batchCall);
 
         messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
       }
     });
-
     await expectFailedToTransact(helper, messageSent);
   });
 }
@@ -492,17 +414,6 @@
 describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {
   let alice: IKeyringPair;
   let randomAccount: IKeyringPair;
-
-  const uniqueAssetId = {
-    Concrete: {
-      parents: 1,
-      interior: {
-        X1: {
-          Parachain: UNIQUE_CHAIN,
-        },
-      },
-    },
-  };
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
@@ -523,7 +434,9 @@
       if it is added again. Needed for debugging
       when this test is run multiple times.
       */
-      if(!isWhitelisted) {
+      if(isWhitelisted) {
+        console.log('UNQ token is already whitelisted on Polkadex');
+      } else {
         await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);
       }
 
@@ -639,7 +552,7 @@
       const numAssetsWeightHint = 0;
 
       if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {
-        console.log('Unique asset already registered');
+        console.log('Unique asset already registered on Moonbeam');
       } else {
         const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
           location: uniqueAssetLocation,
@@ -750,6 +663,8 @@
 
         console.log('3. Set UNQ payment for XCM execution on Astar');
         await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+      } else {
+        console.log('UNQ is already registered on Astar');
       }
       console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');
       await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);
addedtests/src/xcm/xcm.types.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/xcm/xcm.types.ts
@@ -0,0 +1,85 @@
+import {usingAcalaPlaygrounds, usingAstarPlaygrounds, usingMoonbeamPlaygrounds, usingPolkadexPlaygrounds} from '../util';
+import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import config from '../config';
+
+export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
+export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
+export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);
+export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);
+export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
+export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
+
+export const acalaUrl = config.acalaUrl;
+export const moonbeamUrl = config.moonbeamUrl;
+export const astarUrl = config.astarUrl;
+export const polkadexUrl = config.polkadexUrl;
+
+export const SAFE_XCM_VERSION = 3;
+
+export const maxWaitBlocks = 6;
+
+
+export const ASTAR_DECIMALS = 18n;
+export const UNQ_DECIMALS = 18n;
+
+export const uniqueMultilocation = {
+  parents: 1,
+  interior: {
+    X1: {
+      Parachain: UNIQUE_CHAIN,
+    },
+  },
+};
+export const uniqueVersionedMultilocation = {
+  V3: uniqueMultilocation,
+};
+
+export const uniqueAssetId = {
+  Concrete: uniqueMultilocation,
+};
+
+export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
+  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
+        && event.outcome.isFailedToTransactAsset);
+};
+export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {
+  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
+         && event.outcome.isUntrustedReserveLocation);
+};
+
+export const NETWORKS = {
+  acala: usingAcalaPlaygrounds,
+  astar: usingAstarPlaygrounds,
+  polkadex: usingPolkadexPlaygrounds,
+  moonbeam: usingMoonbeamPlaygrounds,
+} as const;
+
+export function mapToChainId(networkName: keyof typeof NETWORKS) {
+  switch (networkName) {
+    case 'acala':
+      return ACALA_CHAIN;
+    case 'astar':
+      return ASTAR_CHAIN;
+    case 'moonbeam':
+      return MOONBEAM_CHAIN;
+    case 'polkadex':
+      return POLKADEX_CHAIN;
+  }
+}
+
+export function mapToChainUrl(networkName: keyof typeof NETWORKS): string {
+  switch (networkName) {
+    case 'acala':
+      return acalaUrl;
+    case 'astar':
+      return astarUrl;
+    case 'moonbeam':
+      return moonbeamUrl;
+    case 'polkadex':
+      return polkadexUrl;
+  }
+}
+
+export function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {
+  return NETWORKS[name];
+}
\ No newline at end of file
modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
before · tests/src/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 {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';21import {hexToString, nToBigInt} from '@polkadot/util';2223const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);24const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);25const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);26const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);27const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);28const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);2930const STATEMINT_PALLET_INSTANCE = 50;3132const relayUrl = config.relayUrl;33const statemintUrl = config.statemintUrl;34const acalaUrl = config.acalaUrl;35const moonbeamUrl = config.moonbeamUrl;36const astarUrl = config.astarUrl;37const polkadexUrl = config.polkadexUrl;3839const RELAY_DECIMALS = 12;40const STATEMINT_DECIMALS = 12;41const ACALA_DECIMALS = 12;42const ASTAR_DECIMALS = 18n;43const UNQ_DECIMALS = 18n;4445const TRANSFER_AMOUNT = 2000000000000000000000000n;4647const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4849const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;5051const USDT_ASSET_ID = 100;52const USDT_ASSET_METADATA_DECIMALS = 18;53const USDT_ASSET_METADATA_NAME = 'USDT';54const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';55const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;56const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5758const SAFE_XCM_VERSION = 2;59const maxWaitBlocks = 6;6061const uniqueMultilocation = {62  V2: {63    parents: 1,64    interior: {65      X1: {66        Parachain: UNIQUE_CHAIN,67      },68    },69  },70};7172const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {73  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash74        && event.outcome.isFailedToTransactAsset);75};76const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {77  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash78         && event.outcome.isUntrustedReserveLocation);79};80describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {81  let alice: IKeyringPair;82  let bob: IKeyringPair;8384  let balanceStmnBefore: bigint;85  let balanceStmnAfter: bigint;8687  let balanceUniqueBefore: bigint;88  let balanceUniqueAfter: bigint;89  let balanceUniqueFinal: bigint;9091  let balanceBobBefore: bigint;92  let balanceBobAfter: bigint;93  let balanceBobFinal: bigint;9495  let balanceBobRelayTokenBefore: bigint;96  let balanceBobRelayTokenAfter: bigint;979899  before(async () => {100    await usingPlaygrounds(async (helper, privateKey) => {101      alice = await privateKey('//Alice');102      bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor103104      // Set the default version to wrap the first message to other chains.105      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);106    });107108    await usingRelayPlaygrounds(relayUrl, async (helper) => {109      // Fund accounts on Statemint110      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);111      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);112    });113114    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {115      const sovereignFundingAmount = 3_500_000_000n;116117      await helper.assets.create(118        alice,119        USDT_ASSET_ID,120        alice.address,121        USDT_ASSET_METADATA_MINIMAL_BALANCE,122      );123      await helper.assets.setMetadata(124        alice,125        USDT_ASSET_ID,126        USDT_ASSET_METADATA_NAME,127        USDT_ASSET_METADATA_DESCRIPTION,128        USDT_ASSET_METADATA_DECIMALS,129      );130      await helper.assets.mint(131        alice,132        USDT_ASSET_ID,133        alice.address,134        USDT_ASSET_AMOUNT,135      );136137      // funding parachain sovereing account on Statemint.138      // The sovereign account should be created before any action139      // (the assets pallet on Statemint check if the sovereign account exists)140      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);141      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);142    });143144145    await usingPlaygrounds(async (helper) => {146      const location = {147        V2: {148          parents: 1,149          interior: {X3: [150            {151              Parachain: STATEMINT_CHAIN,152            },153            {154              PalletInstance: STATEMINT_PALLET_INSTANCE,155            },156            {157              GeneralIndex: USDT_ASSET_ID,158            },159          ]},160        },161      };162163      const metadata =164      {165        name: USDT_ASSET_ID,166        symbol: USDT_ASSET_METADATA_NAME,167        decimals: USDT_ASSET_METADATA_DECIMALS,168        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,169      };170      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);171      balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);172    });173174175    // Providing the relay currency to the unique sender account176    // (fee for USDT XCM are paid in relay tokens)177    await usingRelayPlaygrounds(relayUrl, async (helper) => {178      const destination = {179        V2: {180          parents: 0,181          interior: {X1: {182            Parachain: UNIQUE_CHAIN,183          },184          },185        }};186187      const beneficiary = {188        V2: {189          parents: 0,190          interior: {X1: {191            AccountId32: {192              network: 'Any',193              id: alice.addressRaw,194            },195          }},196        },197      };198199      const assets = {200        V2: [201          {202            id: {203              Concrete: {204                parents: 0,205                interior: 'Here',206              },207            },208            fun: {209              Fungible: TRANSFER_AMOUNT_RELAY,210            },211          },212        ],213      };214215      const feeAssetItem = 0;216217      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');218    });219220  });221222  itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {223    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {224      const dest = {225        V2: {226          parents: 1,227          interior: {X1: {228            Parachain: UNIQUE_CHAIN,229          },230          },231        }};232233      const beneficiary = {234        V2: {235          parents: 0,236          interior: {X1: {237            AccountId32: {238              network: 'Any',239              id: alice.addressRaw,240            },241          }},242        },243      };244245      const assets = {246        V2: [247          {248            id: {249              Concrete: {250                parents: 0,251                interior: {252                  X2: [253                    {254                      PalletInstance: STATEMINT_PALLET_INSTANCE,255                    },256                    {257                      GeneralIndex: USDT_ASSET_ID,258                    },259                  ]},260              },261            },262            fun: {263              Fungible: TRANSFER_AMOUNT,264            },265          },266        ],267      };268269      const feeAssetItem = 0;270271      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);272      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');273274      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);275276      // common good parachain take commission in it native token277      console.log(278        '[Statemint -> Unique] transaction fees on Statemint: %s WND',279        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),280      );281      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;282283    });284285286    // ensure that asset has been delivered287    await helper.wait.newBlocks(3);288289    // expext collection id will be with id 1290    const free = await helper.ft.getBalance(1, {Substrate: alice.address});291292    balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);293294    console.log(295      '[Statemint -> Unique] transaction fees on Unique: %s USDT',296      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),297    );298    console.log(299      '[Statemint -> Unique] transaction fees on Unique: %s UNQ',300      helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),301    );302    // commission has not paid in USDT token303    expect(free).to.be.equal(TRANSFER_AMOUNT);304    // ... and parachain native token305    expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;306  });307308  itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {309    const destination = {310      V2: {311        parents: 1,312        interior: {X2: [313          {314            Parachain: STATEMINT_CHAIN,315          },316          {317            AccountId32: {318              network: 'Any',319              id: alice.addressRaw,320            },321          },322        ]},323      },324    };325326    const relayFee = 400_000_000_000_000n;327    const currencies: [any, bigint][] = [328      [329        {330          ForeignAssetId: 0,331        },332        TRANSFER_AMOUNT,333      ],334      [335        {336          NativeAssetId: 'Parent',337        },338        relayFee,339      ],340    ];341342    const feeItem = 1;343344    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');345346    // the commission has been paid in parachain native token347    balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);348    console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));349    expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;350351    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {352      await helper.wait.newBlocks(3);353354      // The USDT token never paid fees. Its amount not changed from begin value.355      // Also check that xcm transfer has been succeeded356      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;357    });358  });359360  itSub('Should connect and send Relay token to Unique', async ({helper}) => {361    balanceBobBefore = await helper.balance.getSubstrate(bob.address);362    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});363364    await usingRelayPlaygrounds(relayUrl, async (helper) => {365      const destination = {366        V2: {367          parents: 0,368          interior: {X1: {369            Parachain: UNIQUE_CHAIN,370          },371          },372        }};373374      const beneficiary = {375        V2: {376          parents: 0,377          interior: {X1: {378            AccountId32: {379              network: 'Any',380              id: bob.addressRaw,381            },382          }},383        },384      };385386      const assets = {387        V2: [388          {389            id: {390              Concrete: {391                parents: 0,392                interior: 'Here',393              },394            },395            fun: {396              Fungible: TRANSFER_AMOUNT_RELAY,397            },398          },399        ],400      };401402      const feeAssetItem = 0;403404      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');405    });406407    await helper.wait.newBlocks(3);408409    balanceBobAfter = await helper.balance.getSubstrate(bob.address);410    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});411412    const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;413    const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;414    console.log(415      '[Relay (Westend) -> Unique] transaction fees: %s UNQ',416      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),417    );418    console.log(419      '[Relay (Westend) -> Unique] transaction fees: %s WND',420      helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),421    );422    console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);423    expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;424    expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;425  });426427  itSub('Should connect and send Relay token back', async ({helper}) => {428    let relayTokenBalanceBefore: bigint;429    let relayTokenBalanceAfter: bigint;430    await usingRelayPlaygrounds(relayUrl, async (helper) => {431      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);432    });433434    const destination = {435      V2: {436        parents: 1,437        interior: {438          X1:{439            AccountId32: {440              network: 'Any',441              id: bob.addressRaw,442            },443          },444        },445      },446    };447448    const currencies: any = [449      [450        {451          NativeAssetId: 'Parent',452        },453        TRANSFER_AMOUNT_RELAY,454      ],455    ];456457    const feeItem = 0;458459    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');460461    balanceBobFinal = await helper.balance.getSubstrate(bob.address);462    console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));463464    await usingRelayPlaygrounds(relayUrl, async (helper) => {465      await helper.wait.newBlocks(10);466      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);467468      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;469      console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));470      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;471    });472  });473});474475describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {476  let alice: IKeyringPair;477  let randomAccount: IKeyringPair;478479  let balanceUniqueTokenInit: bigint;480  let balanceUniqueTokenMiddle: bigint;481  let balanceUniqueTokenFinal: bigint;482  let balanceAcalaTokenInit: bigint;483  let balanceAcalaTokenMiddle: bigint;484  let balanceAcalaTokenFinal: bigint;485  let balanceUniqueForeignTokenInit: bigint;486  let balanceUniqueForeignTokenMiddle: bigint;487  let balanceUniqueForeignTokenFinal: bigint;488489  // computed by a test transfer from prod Unique to prod Acala.490  // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48491  // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)492  const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;493  const acalaEps = 8n * 10n ** 16n;494495  let acalaBackwardTransferAmount: bigint;496497  before(async () => {498    await usingPlaygrounds(async (helper, privateKey) => {499      alice = await privateKey('//Alice');500      [randomAccount] = await helper.arrange.createAccounts([0n], alice);501502      // Set the default version to wrap the first message to other chains.503      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);504    });505506    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {507      const destination = {508        V2: {509          parents: 1,510          interior: {511            X1: {512              Parachain: UNIQUE_CHAIN,513            },514          },515        },516      };517518      const metadata = {519        name: 'Unique Network',520        symbol: 'UNQ',521        decimals: 18,522        minimalBalance: 1250000000000000000n,523      };524      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>525        hexToString(v.toJSON()['symbol'])) as string[];526527      if(!assets.includes('UNQ')) {528        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);529      } else {530        console.log('UNQ token already registered on Acala assetRegistry pallet');531      }532      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);533      balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);534      balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});535    });536537    await usingPlaygrounds(async (helper) => {538      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);539      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);540    });541  });542543  itSub('Should connect and send UNQ to Acala', async ({helper}) => {544545    const destination = {546      V2: {547        parents: 1,548        interior: {549          X1: {550            Parachain: ACALA_CHAIN,551          },552        },553      },554    };555556    const beneficiary = {557      V2: {558        parents: 0,559        interior: {560          X1: {561            AccountId32: {562              network: 'Any',563              id: randomAccount.addressRaw,564            },565          },566        },567      },568    };569570    const assets = {571      V2: [572        {573          id: {574            Concrete: {575              parents: 0,576              interior: 'Here',577            },578          },579          fun: {580            Fungible: TRANSFER_AMOUNT,581          },582        },583      ],584    };585586    const feeAssetItem = 0;587588    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');589    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);590591    const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;592    console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));593    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;594595    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {596      await helper.wait.newBlocks(3);597598      balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});599      balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);600601      const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;602      const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;603      acalaBackwardTransferAmount = unqIncomeTransfer;604605      const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;606607      console.log(608        '[Unique -> Acala] transaction fees on Acala: %s ACA',609        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),610      );611      console.log(612        '[Unique -> Acala] transaction fees on Acala: %s UNQ',613        helper.util.bigIntToDecimals(acaUnqFees),614      );615      console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));616      expect(acaFees == 0n).to.be.true;617618      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;619620      expect(621        bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,622        'Acala took different income fee, check the Acala foreign asset config',623      ).to.be.true;624    });625  });626627  itSub('Should connect to Acala and send UNQ back', async ({helper}) => {628    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {629      const destination = {630        V2: {631          parents: 1,632          interior: {633            X2: [634              {Parachain: UNIQUE_CHAIN},635              {636                AccountId32: {637                  network: 'Any',638                  id: randomAccount.addressRaw,639                },640              },641            ],642          },643        },644      };645646      const id = {647        ForeignAsset: 0,648      };649650      await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');651      balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);652      balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);653654      const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;655      const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;656657      console.log(658        '[Acala -> Unique] transaction fees on Acala: %s ACA',659        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),660      );661      console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));662663      expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;664      expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;665    });666667    await helper.wait.newBlocks(3);668669    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);670    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;671    expect(actuallyDelivered > 0).to.be.true;672673    console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));674675    const unqFees = acalaBackwardTransferAmount - actuallyDelivered;676    console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));677    expect(unqFees == 0n).to.be.true;678  });679680  itSub('Acala can send only up to its balance', async ({helper}) => {681    // set Acala's sovereign account's balance682    const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);683    const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);684    await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);685686    const moreThanAcalaHas = acalaBalance * 2n;687688    let targetAccountBalance = 0n;689    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);690691    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(692      targetAccount.addressRaw,693      {694        Concrete: {695          parents: 0,696          interior: 'Here',697        },698      },699      moreThanAcalaHas,700    );701702    let maliciousXcmProgramSent: any;703    const maxWaitBlocks = 3;704705    // Try to trick Unique706    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {707      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);708709      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);710    });711712    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash713        && event.outcome.isFailedToTransactAsset);714715    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);716    expect(targetAccountBalance).to.be.equal(0n);717718    // But Acala still can send the correct amount719    const validTransferAmount = acalaBalance / 2n;720    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(721      targetAccount.addressRaw,722      {723        Concrete: {724          parents: 0,725          interior: 'Here',726        },727      },728      validTransferAmount,729    );730731    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {732      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);733    });734735    await helper.wait.newBlocks(maxWaitBlocks);736737    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);738    expect(targetAccountBalance).to.be.equal(validTransferAmount);739  });740741  itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {742    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);743    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);744745    const uniqueMultilocation = {746      V2: {747        parents: 1,748        interior: {749          X1: {750            Parachain: UNIQUE_CHAIN,751          },752        },753      },754    };755756    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(757      targetAccount.addressRaw,758      {759        Concrete: {760          parents: 1,761          interior: {762            X1: {763              Parachain: UNIQUE_CHAIN,764            },765          },766        },767      },768      testAmount,769    );770771    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(772      targetAccount.addressRaw,773      {774        Concrete: {775          parents: 0,776          interior: 'Here',777        },778      },779      testAmount,780    );781782    let maliciousXcmProgramFullIdSent: any;783    let maliciousXcmProgramHereIdSent: any;784    const maxWaitBlocks = 3;785786    // Try to trick Unique using full UNQ identification787    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {788      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);789790      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);791    });792793    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash794        && event.outcome.isUntrustedReserveLocation);795796    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);797    expect(accountBalance).to.be.equal(0n);798799    // Try to trick Unique using shortened UNQ identification800    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {801      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);802803      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);804    });805806    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash807        && event.outcome.isUntrustedReserveLocation);808809    accountBalance = await helper.balance.getSubstrate(targetAccount.address);810    expect(accountBalance).to.be.equal(0n);811  });812});813814describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {815  let alice: IKeyringPair;816  let randomAccount: IKeyringPair;817  let unqFees: bigint;818  let balanceUniqueTokenInit: bigint;819  let balanceUniqueTokenMiddle: bigint;820  let balanceUniqueTokenFinal: bigint;821  const maxWaitBlocks = 6;822823  const uniqueAssetId = {824    Concrete: {825      parents: 1,826      interior: {827        X1: {828          Parachain: UNIQUE_CHAIN,829        },830      },831    },832  };833834  before(async () => {835    await usingPlaygrounds(async (helper, privateKey) => {836      alice = await privateKey('//Alice');837      [randomAccount] = await helper.arrange.createAccounts([0n], alice);838839      // Set the default version to wrap the first message to other chains.840      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);841    });842843    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {844      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))845        .toJSON() as [])846        .map(nToBigInt).length != 0;847      /*848      Check whether the Unique token has been added849      to the whitelist, since an error will occur850      if it is added again. Needed for debugging851      when this test is run multiple times.852      */853      if(!isWhitelisted) {854        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);855      }856857      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);858    });859860    await usingPlaygrounds(async (helper) => {861      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);862      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);863    });864  });865866  itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {867868    const destination = {869      V2: {870        parents: 1,871        interior: {872          X1: {873            Parachain: POLKADEX_CHAIN,874          },875        },876      },877    };878879    const beneficiary = {880      V2: {881        parents: 0,882        interior: {883          X1: {884            AccountId32: {885              network: 'Any',886              id: randomAccount.addressRaw,887            },888          },889        },890      },891    };892893    const assets = {894      V2: [895        {896          id: {897            Concrete: {898              parents: 0,899              interior: 'Here',900            },901          },902          fun: {903            Fungible: TRANSFER_AMOUNT,904          },905        },906      ],907    };908909    const feeAssetItem = 0;910911    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');912    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);913    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);914915    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;916    console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));917    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;918919    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {920      /*921        Since only the parachain part of the Polkadex922        infrastructure is launched (without their923        solochain validators), processing incoming924        assets will lead to an error.925        This error indicates that the Polkadex chain926        received a message from the Unique network,927        since the hash is being checked to ensure928        it matches what was sent.929      */930      await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);931    });932  });933934935  itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {936937    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(938      randomAccount.addressRaw,939      {940        Concrete: {941          parents: 1,942          interior: {943            X1: {Parachain: UNIQUE_CHAIN},944          },945        },946      },947      TRANSFER_AMOUNT,948    );949950    let xcmProgramSent: any;951952953    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {954      await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);955956      xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);957    });958959    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);960961    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);962963    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);964  });965966  itSub('Polkadex can send only up to its balance', async ({helper}) => {967    const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);968    const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);969    await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);970    const moreThanPolkadexHas = 2n * polkadexBalance;971972    const targetAccount = helper.arrange.createEmptyAccount();973974    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(975      targetAccount.addressRaw,976      {977        Concrete: {978          parents: 0,979          interior: 'Here',980        },981      },982      moreThanPolkadexHas,983    );984985    let maliciousXcmProgramSent: any;986987988    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {989      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);990991      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);992    });993994    await expectFailedToTransact(helper, maliciousXcmProgramSent);995996    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);997    expect(targetAccountBalance).to.be.equal(0n);998  });9991000  itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {1001    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1002    const targetAccount = helper.arrange.createEmptyAccount();10031004    const uniqueMultilocation = {1005      V2: {1006        parents: 1,1007        interior: {1008          X1: {1009            Parachain: UNIQUE_CHAIN,1010          },1011        },1012      },1013    };10141015    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1016      targetAccount.addressRaw,1017      {1018        Concrete: {1019          parents: 1,1020          interior: {1021            X1: {1022              Parachain: UNIQUE_CHAIN,1023            },1024          },1025        },1026      },1027      testAmount,1028    );10291030    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1031      targetAccount.addressRaw,1032      {1033        Concrete: {1034          parents: 0,1035          interior: 'Here',1036        },1037      },1038      testAmount,1039    );10401041    let maliciousXcmProgramFullIdSent: any;1042    let maliciousXcmProgramHereIdSent: any;1043    const maxWaitBlocks = 3;10441045    // Try to trick Unique using full UNQ identification1046    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1047      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10481049      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1050    });10511052    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10531054    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1055    expect(accountBalance).to.be.equal(0n);10561057    // Try to trick Unique using shortened UNQ identification1058    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1059      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10601061      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1062    });10631064    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10651066    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1067    expect(accountBalance).to.be.equal(0n);1068  });1069});10701071// These tests are relevant only when1072// the the corresponding foreign assets are not registered1073describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1074  let alice: IKeyringPair;1075  let alith: IKeyringPair;10761077  const testAmount = 100_000_000_000n;1078  let uniqueParachainJunction;1079  let uniqueAccountJunction;10801081  let uniqueParachainMultilocation: any;1082  let uniqueAccountMultilocation: any;1083  let uniqueCombinedMultilocation: any;1084  let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210851086  let messageSent: any;10871088  const maxWaitBlocks = 3;10891090  before(async () => {1091    await usingPlaygrounds(async (helper, privateKey) => {1092      alice = await privateKey('//Alice');10931094      uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1095      uniqueAccountJunction = {1096        AccountId32: {1097          network: 'Any',1098          id: alice.addressRaw,1099        },1100      };11011102      uniqueParachainMultilocation = {1103        V2: {1104          parents: 1,1105          interior: {1106            X1: uniqueParachainJunction,1107          },1108        },1109      };11101111      uniqueAccountMultilocation = {1112        V2: {1113          parents: 0,1114          interior: {1115            X1: uniqueAccountJunction,1116          },1117        },1118      };11191120      uniqueCombinedMultilocation = {1121        V2: {1122          parents: 1,1123          interior: {1124            X2: [uniqueParachainJunction, uniqueAccountJunction],1125          },1126        },1127      };11281129      uniqueCombinedMultilocationAcala = {1130        V2: {1131          parents: 1,1132          interior: {1133            X2: [uniqueParachainJunction, uniqueAccountJunction],1134          },1135        },1136      };11371138      // Set the default version to wrap the first message to other chains.1139      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1140    });11411142    // eslint-disable-next-line require-await1143    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1144      alith = helper.account.alithAccount();1145    });1146  });1147114811491150  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1151    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1152      const id = {1153        Token: 'ACA',1154      };1155      const destination = uniqueCombinedMultilocationAcala;1156      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11571158      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1159    });11601161    await expectFailedToTransact(helper, messageSent);1162  });11631164  itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1165    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1166      const id = 'SelfReserve';1167      const destination = uniqueCombinedMultilocation;1168      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11691170      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1171    });11721173    await expectFailedToTransact(helper, messageSent);1174  });11751176  itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1177    await usingAstarPlaygrounds(astarUrl, async (helper) => {1178      const destinationParachain = uniqueParachainMultilocation;1179      const beneficiary = uniqueAccountMultilocation;1180      const assets = {1181        V2: [{1182          id: {1183            Concrete: {1184              parents: 0,1185              interior: 'Here',1186            },1187          },1188          fun: {1189            Fungible: testAmount,1190          },1191        }],1192      };1193      const feeAssetItem = 0;11941195      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1196        destinationParachain,1197        beneficiary,1198        assets,1199        feeAssetItem,1200      ]);12011202      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1203    });12041205    await expectFailedToTransact(helper, messageSent);1206  });12071208  itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12091210    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1211      helper.arrange.createEmptyAccount().addressRaw,1212      {1213        Concrete: {1214          parents: 1,1215          interior: {1216            X1: {1217              Parachain: POLKADEX_CHAIN,1218            },1219          },1220        },1221      },1222      testAmount,1223    );12241225    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1226      await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1227      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1228    });12291230    await expectFailedToTransact(helper, messageSent);1231  });1232});12331234describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1235  // Unique constants1236  let alice: IKeyringPair;1237  let uniqueAssetLocation;12381239  let randomAccountUnique: IKeyringPair;1240  let randomAccountMoonbeam: IKeyringPair;12411242  // Moonbeam constants1243  let assetId: string;12441245  const uniqueAssetMetadata = {1246    name: 'xcUnique',1247    symbol: 'xcUNQ',1248    decimals: 18,1249    isFrozen: false,1250    minimalBalance: 1n,1251  };12521253  let balanceUniqueTokenInit: bigint;1254  let balanceUniqueTokenMiddle: bigint;1255  let balanceUniqueTokenFinal: bigint;1256  let balanceForeignUnqTokenInit: bigint;1257  let balanceForeignUnqTokenMiddle: bigint;1258  let balanceForeignUnqTokenFinal: bigint;1259  let balanceGlmrTokenInit: bigint;1260  let balanceGlmrTokenMiddle: bigint;1261  let balanceGlmrTokenFinal: bigint;12621263  before(async () => {1264    await usingPlaygrounds(async (helper, privateKey) => {1265      alice = await privateKey('//Alice');1266      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12671268      balanceForeignUnqTokenInit = 0n;12691270      // Set the default version to wrap the first message to other chains.1271      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1272    });12731274    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1275      const alithAccount = helper.account.alithAccount();1276      const baltatharAccount = helper.account.baltatharAccount();1277      const dorothyAccount = helper.account.dorothyAccount();12781279      randomAccountMoonbeam = helper.account.create();12801281      // >>> Sponsoring Dorothy >>>1282      console.log('Sponsoring Dorothy.......');1283      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1284      console.log('Sponsoring Dorothy.......DONE');1285      // <<< Sponsoring Dorothy <<<12861287      uniqueAssetLocation = {1288        XCM: {1289          parents: 1,1290          interior: {X1: {Parachain: UNIQUE_CHAIN}},1291        },1292      };1293      const existentialDeposit = 1n;1294      const isSufficient = true;1295      const unitsPerSecond = 1n;1296      const numAssetsWeightHint = 0;12971298      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1299        console.log('Unique asset is already registered on MoonBeam');1300      } else {1301        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1302          location: uniqueAssetLocation,1303          metadata: uniqueAssetMetadata,1304          existentialDeposit,1305          isSufficient,1306          unitsPerSecond,1307          numAssetsWeightHint,1308        });13091310        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);13111312        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1313      }13141315      // >>> Acquire Unique AssetId Info on Moonbeam >>>1316      console.log('Acquire Unique AssetId Info on Moonbeam.......');13171318      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1319      console.log('UNQ asset ID is %s', assetId);1320      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1321      // >>> Acquire Unique AssetId Info on Moonbeam >>>13221323      // >>> Sponsoring random Account >>>1324      console.log('Sponsoring random Account.......');1325      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1326      console.log('Sponsoring random Account.......DONE');1327      // <<< Sponsoring random Account <<<13281329      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1330    });13311332    await usingPlaygrounds(async (helper) => {1333      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1334      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1335    });1336  });13371338  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1339    const currencyId = {1340      NativeAssetId: 'Here',1341    };1342    const dest = {1343      V2: {1344        parents: 1,1345        interior: {1346          X2: [1347            {Parachain: MOONBEAM_CHAIN},1348            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1349          ],1350        },1351      },1352    };1353    const amount = TRANSFER_AMOUNT;13541355    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13561357    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1358    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13591360    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1361    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1362    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13631364    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1365      await helper.wait.newBlocks(3);1366      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13671368      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1369      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1370      expect(glmrFees == 0n).to.be.true;13711372      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13731374      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1375      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1376      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1377    });1378  });13791380  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1381    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1382      const asset = {1383        V2: {1384          id: {1385            Concrete: {1386              parents: 1,1387              interior: {1388                X1: {Parachain: UNIQUE_CHAIN},1389              },1390            },1391          },1392          fun: {1393            Fungible: TRANSFER_AMOUNT,1394          },1395        },1396      };1397      const destination = {1398        V2: {1399          parents: 1,1400          interior: {1401            X2: [1402              {Parachain: UNIQUE_CHAIN},1403              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1404            ],1405          },1406        },1407      };14081409      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');14101411      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);14121413      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1414      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1415      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;14161417      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);14181419      expect(unqRandomAccountAsset).to.be.null;14201421      balanceForeignUnqTokenFinal = 0n;14221423      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1424      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1425      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1426    });14271428    await helper.wait.newBlocks(3);14291430    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1431    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1432    expect(actuallyDelivered > 0).to.be.true;14331434    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));14351436    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1437    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1438    expect(unqFees == 0n).to.be.true;1439  });14401441  itSub('Moonbeam can send only up to its balance', async ({helper}) => {1442    // set Moonbeam's sovereign account's balance1443    const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1444    const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1445    await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14461447    const moreThanMoonbeamHas = moonbeamBalance * 2n;14481449    let targetAccountBalance = 0n;1450    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14511452    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1453      targetAccount.addressRaw,1454      {1455        Concrete: {1456          parents: 0,1457          interior: 'Here',1458        },1459      },1460      moreThanMoonbeamHas,1461    );14621463    let maliciousXcmProgramSent: any;1464    const maxWaitBlocks = 3;14651466    // Try to trick Unique1467    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1468      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14691470      // Needed to bypass the call filter.1471      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1472      await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14731474      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1475    });14761477    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1478        && event.outcome.isFailedToTransactAsset);14791480    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1481    expect(targetAccountBalance).to.be.equal(0n);14821483    // But Moonbeam still can send the correct amount1484    const validTransferAmount = moonbeamBalance / 2n;1485    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1486      targetAccount.addressRaw,1487      {1488        Concrete: {1489          parents: 0,1490          interior: 'Here',1491        },1492      },1493      validTransferAmount,1494    );14951496    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1497      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);14981499      // Needed to bypass the call filter.1500      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1501      await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1502    });15031504    await helper.wait.newBlocks(maxWaitBlocks);15051506    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1507    expect(targetAccountBalance).to.be.equal(validTransferAmount);1508  });15091510  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1511    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1512    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15131514    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1515      targetAccount.addressRaw,1516      {1517        Concrete: {1518          parents: 0,1519          interior: {1520            X1: {1521              Parachain: UNIQUE_CHAIN,1522            },1523          },1524        },1525      },1526      testAmount,1527    );15281529    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1530      targetAccount.addressRaw,1531      {1532        Concrete: {1533          parents: 0,1534          interior: 'Here',1535        },1536      },1537      testAmount,1538    );15391540    let maliciousXcmProgramFullIdSent: any;1541    let maliciousXcmProgramHereIdSent: any;1542    const maxWaitBlocks = 3;15431544    // Try to trick Unique using full UNQ identification1545    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1546      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15471548      // Needed to bypass the call filter.1549      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1550      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15511552      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1553    });15541555    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1556        && event.outcome.isUntrustedReserveLocation);15571558    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1559    expect(accountBalance).to.be.equal(0n);15601561    // Try to trick Unique using shortened UNQ identification1562    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1563      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15641565      // Needed to bypass the call filter.1566      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1567      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15681569      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1570    });15711572    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1573        && event.outcome.isUntrustedReserveLocation);15741575    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1576    expect(accountBalance).to.be.equal(0n);1577  });1578});15791580describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1581  let alice: IKeyringPair;1582  let randomAccount: IKeyringPair;15831584  const UNQ_ASSET_ID_ON_ASTAR = 1;1585  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;15861587  // Unique -> Astar1588  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1589  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1590  const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1591  const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens15921593  // Astar -> Unique1594  const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1595  const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ15961597  let balanceAfterUniqueToAstarXCM: bigint;15981599  before(async () => {1600    await usingPlaygrounds(async (helper, privateKey) => {1601      alice = await privateKey('//Alice');1602      [randomAccount] = await helper.arrange.createAccounts([100n], alice);1603      console.log('randomAccount', randomAccount.address);16041605      // Set the default version to wrap the first message to other chains.1606      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1607    });16081609    await usingAstarPlaygrounds(astarUrl, async (helper) => {1610      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1611        console.log('1. Create foreign asset and metadata');1612        // TODO update metadata with values from production1613        await helper.assets.create(1614          alice,1615          UNQ_ASSET_ID_ON_ASTAR,1616          alice.address,1617          UNQ_MINIMAL_BALANCE_ON_ASTAR,1618        );16191620        await helper.assets.setMetadata(1621          alice,1622          UNQ_ASSET_ID_ON_ASTAR,1623          'Cross chain UNQ',1624          'xcUNQ',1625          Number(UNQ_DECIMALS),1626        );16271628        console.log('2. Register asset location on Astar');1629        const assetLocation = {1630          V2: {1631            parents: 1,1632            interior: {1633              X1: {1634                Parachain: UNIQUE_CHAIN,1635              },1636            },1637          },1638        };16391640        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16411642        console.log('3. Set UNQ payment for XCM execution on Astar');1643        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1644      }1645      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1646      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1647    });1648  });16491650  itSub('Should connect and send UNQ to Astar', async ({helper}) => {1651    const destination = {1652      V2: {1653        parents: 1,1654        interior: {1655          X1: {1656            Parachain: ASTAR_CHAIN,1657          },1658        },1659      },1660    };16611662    const beneficiary = {1663      V2: {1664        parents: 0,1665        interior: {1666          X1: {1667            AccountId32: {1668              network: 'Any',1669              id: randomAccount.addressRaw,1670            },1671          },1672        },1673      },1674    };16751676    const assets = {1677      V2: [1678        {1679          id: {1680            Concrete: {1681              parents: 0,1682              interior: 'Here',1683            },1684          },1685          fun: {1686            Fungible: unqToAstarTransferred,1687          },1688        },1689      ],1690    };16911692    // Initial balance is 100 UNQ1693    const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1694    console.log(`Initial balance is: ${balanceBefore}`);16951696    const feeAssetItem = 0;1697    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16981699    // Balance after reserve transfer is less than 901700    balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1701    console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1702    console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1703    expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17041705    await usingAstarPlaygrounds(astarUrl, async (helper) => {1706      await helper.wait.newBlocks(3);1707      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1708      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17091710      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1711      console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);17121713      expect(xcUNQbalance).to.eq(unqToAstarArrived);1714      // Astar balance does not changed1715      expect(astarBalance).to.eq(astarInitialBalance);1716    });1717  });17181719  itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1720    await usingAstarPlaygrounds(astarUrl, async (helper) => {1721      const destination = {1722        V2: {1723          parents: 1,1724          interior: {1725            X1: {1726              Parachain: UNIQUE_CHAIN,1727            },1728          },1729        },1730      };17311732      const beneficiary = {1733        V2: {1734          parents: 0,1735          interior: {1736            X1: {1737              AccountId32: {1738                network: 'Any',1739                id: randomAccount.addressRaw,1740              },1741            },1742          },1743        },1744      };17451746      const assets = {1747        V2: [1748          {1749            id: {1750              Concrete: {1751                parents: 1,1752                interior: {1753                  X1: {1754                    Parachain: UNIQUE_CHAIN,1755                  },1756                },1757              },1758            },1759            fun: {1760              Fungible: unqFromAstarTransfered,1761            },1762          },1763        ],1764      };17651766      // Initial balance is 1 ASTR1767      const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1768      console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1769      expect(balanceASTRbefore).to.eq(astarInitialBalance);17701771      const feeAssetItem = 0;1772      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1773      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17741775      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1776      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1777      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17781779      // Assert: xcUNQ balance correctly decreased1780      expect(xcUNQbalance).to.eq(unqOnAstarLeft);1781      // Assert: ASTR balance is 0.996...1782      expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1783    });17841785    await helper.wait.newBlocks(3);1786    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1787    console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1788    expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1789  });17901791  itSub('Astar can send only up to its balance', async ({helper}) => {1792    // set Astar's sovereign account's balance1793    const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1794    const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1795    await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17961797    const moreThanAstarHas = astarBalance * 2n;17981799    let targetAccountBalance = 0n;1800    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18011802    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1803      targetAccount.addressRaw,1804      {1805        Concrete: {1806          parents: 0,1807          interior: 'Here',1808        },1809      },1810      moreThanAstarHas,1811    );18121813    let maliciousXcmProgramSent: any;1814    const maxWaitBlocks = 3;18151816    // Try to trick Unique1817    await usingAstarPlaygrounds(astarUrl, async (helper) => {1818      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18191820      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1821    });18221823    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1824        && event.outcome.isFailedToTransactAsset);18251826    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1827    expect(targetAccountBalance).to.be.equal(0n);18281829    // But Astar still can send the correct amount1830    const validTransferAmount = astarBalance / 2n;1831    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1832      targetAccount.addressRaw,1833      {1834        Concrete: {1835          parents: 0,1836          interior: 'Here',1837        },1838      },1839      validTransferAmount,1840    );18411842    await usingAstarPlaygrounds(astarUrl, async (helper) => {1843      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1844    });18451846    await helper.wait.newBlocks(maxWaitBlocks);18471848    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1849    expect(targetAccountBalance).to.be.equal(validTransferAmount);1850  });18511852  itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1853    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1854    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18551856    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1857      targetAccount.addressRaw,1858      {1859        Concrete: {1860          parents: 1,1861          interior: {1862            X1: {1863              Parachain: UNIQUE_CHAIN,1864            },1865          },1866        },1867      },1868      testAmount,1869    );18701871    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1872      targetAccount.addressRaw,1873      {1874        Concrete: {1875          parents: 0,1876          interior: 'Here',1877        },1878      },1879      testAmount,1880    );18811882    let maliciousXcmProgramFullIdSent: any;1883    let maliciousXcmProgramHereIdSent: any;1884    const maxWaitBlocks = 3;18851886    // Try to trick Unique using full UNQ identification1887    await usingAstarPlaygrounds(astarUrl, async (helper) => {1888      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);18891890      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1891    });18921893    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1894        && event.outcome.isUntrustedReserveLocation);18951896    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1897    expect(accountBalance).to.be.equal(0n);18981899    // Try to trick Unique using shortened UNQ identification1900    await usingAstarPlaygrounds(astarUrl, async (helper) => {1901      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19021903      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1904    });19051906    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1907        && event.outcome.isUntrustedReserveLocation);19081909    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1910    expect(accountBalance).to.be.equal(0n);1911  });1912});
after · tests/src/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 {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {Event} from '../util/playgrounds/unique.dev';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';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      {732        Concrete: {733          parents: 1,734          interior: {735            X1: {736              Parachain: UNIQUE_CHAIN,737            },738          },739        },740      },741      testAmount,742    );743744    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(745      targetAccount.addressRaw,746      {747        Concrete: {748          parents: 0,749          interior: 'Here',750        },751      },752      testAmount,753    );754755    let maliciousXcmProgramFullIdSent: any;756    let maliciousXcmProgramHereIdSent: any;757    const maxWaitBlocks = 3;758759    // Try to trick Unique using full UNQ identification760    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {761      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);762763      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);764    });765766    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash767        && event.outcome.isUntrustedReserveLocation);768769    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);770    expect(accountBalance).to.be.equal(0n);771772    // Try to trick Unique using shortened UNQ identification773    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {774      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);775776      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);777    });778779    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash780        && event.outcome.isUntrustedReserveLocation);781782    accountBalance = await helper.balance.getSubstrate(targetAccount.address);783    expect(accountBalance).to.be.equal(0n);784  });785});786787describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {788  let alice: IKeyringPair;789  let randomAccount: IKeyringPair;790  let unqFees: bigint;791  let balanceUniqueTokenInit: bigint;792  let balanceUniqueTokenMiddle: bigint;793  let balanceUniqueTokenFinal: bigint;794  const maxWaitBlocks = 6;795796  before(async () => {797    await usingPlaygrounds(async (helper, privateKey) => {798      alice = await privateKey('//Alice');799      [randomAccount] = await helper.arrange.createAccounts([0n], alice);800801      // Set the default version to wrap the first message to other chains.802      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);803    });804805    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {806      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))807        .toJSON() as [])808        .map(nToBigInt).length != 0;809      /*810      Check whether the Unique token has been added811      to the whitelist, since an error will occur812      if it is added again. Needed for debugging813      when this test is run multiple times.814      */815      if(isWhitelisted) {816        console.log('UNQ token is already whitelisted on Polkadex');817      } else {818        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);819      }820821      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);822    });823824    await usingPlaygrounds(async (helper) => {825      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);826      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);827    });828  });829830  itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {831832    const destination = {833      V2: {834        parents: 1,835        interior: {836          X1: {837            Parachain: POLKADEX_CHAIN,838          },839        },840      },841    };842843    const beneficiary = {844      V2: {845        parents: 0,846        interior: {847          X1: {848            AccountId32: {849              network: 'Any',850              id: randomAccount.addressRaw,851            },852          },853        },854      },855    };856857    const assets = {858      V2: [859        {860          id: {861            Concrete: {862              parents: 0,863              interior: 'Here',864            },865          },866          fun: {867            Fungible: TRANSFER_AMOUNT,868          },869        },870      ],871    };872873    const feeAssetItem = 0;874875    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');876    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);877    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);878879    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;880    console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));881    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;882883    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {884      /*885        Since only the parachain part of the Polkadex886        infrastructure is launched (without their887        solochain validators), processing incoming888        assets will lead to an error.889        This error indicates that the Polkadex chain890        received a message from the Unique network,891        since the hash is being checked to ensure892        it matches what was sent.893      */894      await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);895    });896  });897898899  itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {900901    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(902      randomAccount.addressRaw,903      {904        Concrete: {905          parents: 1,906          interior: {907            X1: {Parachain: UNIQUE_CHAIN},908          },909        },910      },911      TRANSFER_AMOUNT,912    );913914    let xcmProgramSent: any;915916917    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {918      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);919920      xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);921    });922923    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);924925    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);926927    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);928  });929930  itSub('Polkadex can send only up to its balance', async ({helper}) => {931    const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);932    const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);933    await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);934    const moreThanPolkadexHas = 2n * polkadexBalance;935936    const targetAccount = helper.arrange.createEmptyAccount();937938    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(939      targetAccount.addressRaw,940      {941        Concrete: {942          parents: 0,943          interior: 'Here',944        },945      },946      moreThanPolkadexHas,947    );948949    let maliciousXcmProgramSent: any;950951952    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {953      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);954955      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);956    });957958    await expectFailedToTransact(helper, maliciousXcmProgramSent);959960    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);961    expect(targetAccountBalance).to.be.equal(0n);962  });963964  itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {965    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);966    const targetAccount = helper.arrange.createEmptyAccount();967968    const uniqueMultilocation = {969      V2: {970        parents: 1,971        interior: {972          X1: {973            Parachain: UNIQUE_CHAIN,974          },975        },976      },977    };978979    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(980      targetAccount.addressRaw,981      {982        Concrete: {983          parents: 1,984          interior: {985            X1: {986              Parachain: UNIQUE_CHAIN,987            },988          },989        },990      },991      testAmount,992    );993994    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(995      targetAccount.addressRaw,996      {997        Concrete: {998          parents: 0,999          interior: 'Here',1000        },1001      },1002      testAmount,1003    );10041005    let maliciousXcmProgramFullIdSent: any;1006    let maliciousXcmProgramHereIdSent: any;1007    const maxWaitBlocks = 3;10081009    // Try to trick Unique using full UNQ identification1010    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1011      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10121013      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1014    });10151016    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10171018    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1019    expect(accountBalance).to.be.equal(0n);10201021    // Try to trick Unique using shortened UNQ identification1022    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1023      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10241025      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1026    });10271028    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10291030    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1031    expect(accountBalance).to.be.equal(0n);1032  });1033});10341035// These tests are relevant only when1036// the the corresponding foreign assets are not registered1037describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1038  let alice: IKeyringPair;1039  let alith: IKeyringPair;10401041  const testAmount = 100_000_000_000n;1042  let uniqueParachainJunction;1043  let uniqueAccountJunction;10441045  let uniqueParachainMultilocation: any;1046  let uniqueAccountMultilocation: any;1047  let uniqueCombinedMultilocation: any;1048  let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210491050  let messageSent: any;10511052  const maxWaitBlocks = 3;10531054  before(async () => {1055    await usingPlaygrounds(async (helper, privateKey) => {1056      alice = await privateKey('//Alice');10571058      uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1059      uniqueAccountJunction = {1060        AccountId32: {1061          network: 'Any',1062          id: alice.addressRaw,1063        },1064      };10651066      uniqueParachainMultilocation = {1067        V2: {1068          parents: 1,1069          interior: {1070            X1: uniqueParachainJunction,1071          },1072        },1073      };10741075      uniqueAccountMultilocation = {1076        V2: {1077          parents: 0,1078          interior: {1079            X1: uniqueAccountJunction,1080          },1081        },1082      };10831084      uniqueCombinedMultilocation = {1085        V2: {1086          parents: 1,1087          interior: {1088            X2: [uniqueParachainJunction, uniqueAccountJunction],1089          },1090        },1091      };10921093      uniqueCombinedMultilocationAcala = {1094        V2: {1095          parents: 1,1096          interior: {1097            X2: [uniqueParachainJunction, uniqueAccountJunction],1098          },1099        },1100      };11011102      // Set the default version to wrap the first message to other chains.1103      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1104    });11051106    // eslint-disable-next-line require-await1107    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1108      alith = helper.account.alithAccount();1109    });1110  });1111111211131114  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1115    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1116      const id = {1117        Token: 'ACA',1118      };1119      const destination = uniqueCombinedMultilocationAcala;1120      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11211122      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1123    });11241125    await expectFailedToTransact(helper, messageSent);1126  });11271128  itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1129    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1130      const id = 'SelfReserve';1131      const destination = uniqueCombinedMultilocation;1132      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11331134      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1135    });11361137    await expectFailedToTransact(helper, messageSent);1138  });11391140  itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1141    await usingAstarPlaygrounds(astarUrl, async (helper) => {1142      const destinationParachain = uniqueParachainMultilocation;1143      const beneficiary = uniqueAccountMultilocation;1144      const assets = {1145        V2: [{1146          id: {1147            Concrete: {1148              parents: 0,1149              interior: 'Here',1150            },1151          },1152          fun: {1153            Fungible: testAmount,1154          },1155        }],1156      };1157      const feeAssetItem = 0;11581159      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1160        destinationParachain,1161        beneficiary,1162        assets,1163        feeAssetItem,1164      ]);11651166      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1167    });11681169    await expectFailedToTransact(helper, messageSent);1170  });11711172  itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11731174    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1175      helper.arrange.createEmptyAccount().addressRaw,1176      {1177        Concrete: {1178          parents: 1,1179          interior: {1180            X1: {1181              Parachain: POLKADEX_CHAIN,1182            },1183          },1184        },1185      },1186      testAmount,1187    );11881189    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1190      await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1191      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1192    });11931194    await expectFailedToTransact(helper, messageSent);1195  });1196});11971198describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1199  // Unique constants1200  let alice: IKeyringPair;1201  let uniqueAssetLocation;12021203  let randomAccountUnique: IKeyringPair;1204  let randomAccountMoonbeam: IKeyringPair;12051206  // Moonbeam constants1207  let assetId: string;12081209  const uniqueAssetMetadata = {1210    name: 'xcUnique',1211    symbol: 'xcUNQ',1212    decimals: 18,1213    isFrozen: false,1214    minimalBalance: 1n,1215  };12161217  let balanceUniqueTokenInit: bigint;1218  let balanceUniqueTokenMiddle: bigint;1219  let balanceUniqueTokenFinal: bigint;1220  let balanceForeignUnqTokenInit: bigint;1221  let balanceForeignUnqTokenMiddle: bigint;1222  let balanceForeignUnqTokenFinal: bigint;1223  let balanceGlmrTokenInit: bigint;1224  let balanceGlmrTokenMiddle: bigint;1225  let balanceGlmrTokenFinal: bigint;12261227  before(async () => {1228    await usingPlaygrounds(async (helper, privateKey) => {1229      alice = await privateKey('//Alice');1230      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12311232      balanceForeignUnqTokenInit = 0n;12331234      // Set the default version to wrap the first message to other chains.1235      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1236    });12371238    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1239      const alithAccount = helper.account.alithAccount();1240      const baltatharAccount = helper.account.baltatharAccount();1241      const dorothyAccount = helper.account.dorothyAccount();12421243      randomAccountMoonbeam = helper.account.create();12441245      // >>> Sponsoring Dorothy >>>1246      console.log('Sponsoring Dorothy.......');1247      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1248      console.log('Sponsoring Dorothy.......DONE');1249      // <<< Sponsoring Dorothy <<<12501251      uniqueAssetLocation = {1252        XCM: {1253          parents: 1,1254          interior: {X1: {Parachain: UNIQUE_CHAIN}},1255        },1256      };1257      const existentialDeposit = 1n;1258      const isSufficient = true;1259      const unitsPerSecond = 1n;1260      const numAssetsWeightHint = 0;12611262      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1263        console.log('Unique asset is already registered on MoonBeam');1264      } else {1265        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1266          location: uniqueAssetLocation,1267          metadata: uniqueAssetMetadata,1268          existentialDeposit,1269          isSufficient,1270          unitsPerSecond,1271          numAssetsWeightHint,1272        });12731274        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12751276        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1277      }12781279      // >>> Acquire Unique AssetId Info on Moonbeam >>>1280      console.log('Acquire Unique AssetId Info on Moonbeam.......');12811282      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1283      console.log('UNQ asset ID is %s', assetId);1284      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1285      // >>> Acquire Unique AssetId Info on Moonbeam >>>12861287      // >>> Sponsoring random Account >>>1288      console.log('Sponsoring random Account.......');1289      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1290      console.log('Sponsoring random Account.......DONE');1291      // <<< Sponsoring random Account <<<12921293      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1294    });12951296    await usingPlaygrounds(async (helper) => {1297      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1298      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1299    });1300  });13011302  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1303    const currencyId = {1304      NativeAssetId: 'Here',1305    };1306    const dest = {1307      V2: {1308        parents: 1,1309        interior: {1310          X2: [1311            {Parachain: MOONBEAM_CHAIN},1312            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1313          ],1314        },1315      },1316    };1317    const amount = TRANSFER_AMOUNT;13181319    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13201321    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1322    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13231324    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1325    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1326    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13271328    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1329      await helper.wait.newBlocks(3);1330      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13311332      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1333      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1334      expect(glmrFees == 0n).to.be.true;13351336      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13371338      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1339      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1340      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1341    });1342  });13431344  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1345    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1346      const asset = {1347        V2: {1348          id: {1349            Concrete: {1350              parents: 1,1351              interior: {1352                X1: {Parachain: UNIQUE_CHAIN},1353              },1354            },1355          },1356          fun: {1357            Fungible: TRANSFER_AMOUNT,1358          },1359        },1360      };1361      const destination = {1362        V2: {1363          parents: 1,1364          interior: {1365            X2: [1366              {Parachain: UNIQUE_CHAIN},1367              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1368            ],1369          },1370        },1371      };13721373      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13741375      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13761377      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1378      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1379      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13801381      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13821383      expect(unqRandomAccountAsset).to.be.null;13841385      balanceForeignUnqTokenFinal = 0n;13861387      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1388      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1389      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1390    });13911392    await helper.wait.newBlocks(3);13931394    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1395    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1396    expect(actuallyDelivered > 0).to.be.true;13971398    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13991400    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1401    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1402    expect(unqFees == 0n).to.be.true;1403  });14041405  itSub('Moonbeam can send only up to its balance', async ({helper}) => {1406    // set Moonbeam's sovereign account's balance1407    const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1408    const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1409    await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14101411    const moreThanMoonbeamHas = moonbeamBalance * 2n;14121413    let targetAccountBalance = 0n;1414    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14151416    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1417      targetAccount.addressRaw,1418      {1419        Concrete: {1420          parents: 0,1421          interior: 'Here',1422        },1423      },1424      moreThanMoonbeamHas,1425    );14261427    let maliciousXcmProgramSent: any;1428    const maxWaitBlocks = 3;14291430    // Try to trick Unique1431    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1432      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14331434      // Needed to bypass the call filter.1435      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1436      await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14371438      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1439    });14401441    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1442        && event.outcome.isFailedToTransactAsset);14431444    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1445    expect(targetAccountBalance).to.be.equal(0n);14461447    // But Moonbeam still can send the correct amount1448    const validTransferAmount = moonbeamBalance / 2n;1449    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1450      targetAccount.addressRaw,1451      {1452        Concrete: {1453          parents: 0,1454          interior: 'Here',1455        },1456      },1457      validTransferAmount,1458    );14591460    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1461      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14621463      // Needed to bypass the call filter.1464      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1465      await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1466    });14671468    await helper.wait.newBlocks(maxWaitBlocks);14691470    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1471    expect(targetAccountBalance).to.be.equal(validTransferAmount);1472  });14731474  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1475    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1476    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14771478    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1479      targetAccount.addressRaw,1480      {1481        Concrete: {1482          parents: 0,1483          interior: {1484            X1: {1485              Parachain: UNIQUE_CHAIN,1486            },1487          },1488        },1489      },1490      testAmount,1491    );14921493    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1494      targetAccount.addressRaw,1495      {1496        Concrete: {1497          parents: 0,1498          interior: 'Here',1499        },1500      },1501      testAmount,1502    );15031504    let maliciousXcmProgramFullIdSent: any;1505    let maliciousXcmProgramHereIdSent: any;1506    const maxWaitBlocks = 3;15071508    // Try to trick Unique using full UNQ identification1509    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1510      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);15111512      // Needed to bypass the call filter.1513      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1514      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15151516      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1517    });15181519    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1520        && event.outcome.isUntrustedReserveLocation);15211522    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1523    expect(accountBalance).to.be.equal(0n);15241525    // Try to trick Unique using shortened UNQ identification1526    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1527      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);15281529      // Needed to bypass the call filter.1530      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1531      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15321533      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1534    });15351536    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1537        && event.outcome.isUntrustedReserveLocation);15381539    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1540    expect(accountBalance).to.be.equal(0n);1541  });1542});15431544describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1545  let alice: IKeyringPair;1546  let randomAccount: IKeyringPair;15471548  const UNQ_ASSET_ID_ON_ASTAR = 1;1549  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;15501551  // Unique -> Astar1552  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1553  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1554  const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1555  const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens15561557  // Astar -> Unique1558  const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1559  const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ15601561  let balanceAfterUniqueToAstarXCM: bigint;15621563  before(async () => {1564    await usingPlaygrounds(async (helper, privateKey) => {1565      alice = await privateKey('//Alice');1566      [randomAccount] = await helper.arrange.createAccounts([100n], alice);1567      console.log('randomAccount', randomAccount.address);15681569      // Set the default version to wrap the first message to other chains.1570      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1571    });15721573    await usingAstarPlaygrounds(astarUrl, async (helper) => {1574      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1575        console.log('1. Create foreign asset and metadata');1576        // TODO update metadata with values from production1577        await helper.assets.create(1578          alice,1579          UNQ_ASSET_ID_ON_ASTAR,1580          alice.address,1581          UNQ_MINIMAL_BALANCE_ON_ASTAR,1582        );15831584        await helper.assets.setMetadata(1585          alice,1586          UNQ_ASSET_ID_ON_ASTAR,1587          'Cross chain UNQ',1588          'xcUNQ',1589          Number(UNQ_DECIMALS),1590        );15911592        console.log('2. Register asset location on Astar');1593        const assetLocation = {1594          V2: {1595            parents: 1,1596            interior: {1597              X1: {1598                Parachain: UNIQUE_CHAIN,1599              },1600            },1601          },1602        };16031604        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16051606        console.log('3. Set UNQ payment for XCM execution on Astar');1607        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1608      } else {1609        console.log('UNQ is already registered on Astar');1610      }1611      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1612      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1613    });1614  });16151616  itSub('Should connect and send UNQ to Astar', async ({helper}) => {1617    const destination = {1618      V2: {1619        parents: 1,1620        interior: {1621          X1: {1622            Parachain: ASTAR_CHAIN,1623          },1624        },1625      },1626    };16271628    const beneficiary = {1629      V2: {1630        parents: 0,1631        interior: {1632          X1: {1633            AccountId32: {1634              network: 'Any',1635              id: randomAccount.addressRaw,1636            },1637          },1638        },1639      },1640    };16411642    const assets = {1643      V2: [1644        {1645          id: {1646            Concrete: {1647              parents: 0,1648              interior: 'Here',1649            },1650          },1651          fun: {1652            Fungible: unqToAstarTransferred,1653          },1654        },1655      ],1656    };16571658    // Initial balance is 100 UNQ1659    const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1660    console.log(`Initial balance is: ${balanceBefore}`);16611662    const feeAssetItem = 0;1663    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16641665    // Balance after reserve transfer is less than 901666    balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1667    console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1668    console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1669    expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16701671    await usingAstarPlaygrounds(astarUrl, async (helper) => {1672      await helper.wait.newBlocks(3);1673      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1674      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16751676      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1677      console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);16781679      expect(xcUNQbalance).to.eq(unqToAstarArrived);1680      // Astar balance does not changed1681      expect(astarBalance).to.eq(astarInitialBalance);1682    });1683  });16841685  itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1686    await usingAstarPlaygrounds(astarUrl, async (helper) => {1687      const destination = {1688        V2: {1689          parents: 1,1690          interior: {1691            X1: {1692              Parachain: UNIQUE_CHAIN,1693            },1694          },1695        },1696      };16971698      const beneficiary = {1699        V2: {1700          parents: 0,1701          interior: {1702            X1: {1703              AccountId32: {1704                network: 'Any',1705                id: randomAccount.addressRaw,1706              },1707            },1708          },1709        },1710      };17111712      const assets = {1713        V2: [1714          {1715            id: {1716              Concrete: {1717                parents: 1,1718                interior: {1719                  X1: {1720                    Parachain: UNIQUE_CHAIN,1721                  },1722                },1723              },1724            },1725            fun: {1726              Fungible: unqFromAstarTransfered,1727            },1728          },1729        ],1730      };17311732      // Initial balance is 1 ASTR1733      const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1734      console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1735      expect(balanceASTRbefore).to.eq(astarInitialBalance);17361737      const feeAssetItem = 0;1738      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1739      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17401741      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1742      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1743      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17441745      // Assert: xcUNQ balance correctly decreased1746      expect(xcUNQbalance).to.eq(unqOnAstarLeft);1747      // Assert: ASTR balance is 0.996...1748      expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1749    });17501751    await helper.wait.newBlocks(3);1752    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1753    console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1754    expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1755  });17561757  itSub('Astar can send only up to its balance', async ({helper}) => {1758    // set Astar's sovereign account's balance1759    const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1760    const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1761    await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17621763    const moreThanAstarHas = astarBalance * 2n;17641765    let targetAccountBalance = 0n;1766    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17671768    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1769      targetAccount.addressRaw,1770      {1771        Concrete: {1772          parents: 0,1773          interior: 'Here',1774        },1775      },1776      moreThanAstarHas,1777    );17781779    let maliciousXcmProgramSent: any;1780    const maxWaitBlocks = 3;17811782    // Try to trick Unique1783    await usingAstarPlaygrounds(astarUrl, async (helper) => {1784      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17851786      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1787    });17881789    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1790        && event.outcome.isFailedToTransactAsset);17911792    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1793    expect(targetAccountBalance).to.be.equal(0n);17941795    // But Astar still can send the correct amount1796    const validTransferAmount = astarBalance / 2n;1797    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1798      targetAccount.addressRaw,1799      {1800        Concrete: {1801          parents: 0,1802          interior: 'Here',1803        },1804      },1805      validTransferAmount,1806    );18071808    await usingAstarPlaygrounds(astarUrl, async (helper) => {1809      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1810    });18111812    await helper.wait.newBlocks(maxWaitBlocks);18131814    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1815    expect(targetAccountBalance).to.be.equal(validTransferAmount);1816  });18171818  itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1819    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1820    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18211822    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1823      targetAccount.addressRaw,1824      {1825        Concrete: {1826          parents: 1,1827          interior: {1828            X1: {1829              Parachain: UNIQUE_CHAIN,1830            },1831          },1832        },1833      },1834      testAmount,1835    );18361837    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1838      targetAccount.addressRaw,1839      {1840        Concrete: {1841          parents: 0,1842          interior: 'Here',1843        },1844      },1845      testAmount,1846    );18471848    let maliciousXcmProgramFullIdSent: any;1849    let maliciousXcmProgramHereIdSent: any;1850    const maxWaitBlocks = 3;18511852    // Try to trick Unique using full UNQ identification1853    await usingAstarPlaygrounds(astarUrl, async (helper) => {1854      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18551856      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1857    });18581859    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1860        && event.outcome.isUntrustedReserveLocation);18611862    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1863    expect(accountBalance).to.be.equal(0n);18641865    // Try to trick Unique using shortened UNQ identification1866    await usingAstarPlaygrounds(astarUrl, async (helper) => {1867      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18681869      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1870    });18711872    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1873        && event.outcome.isUntrustedReserveLocation);18741875    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1876    expect(accountBalance).to.be.equal(0n);1877  });1878});