git.delta.rocks / unique-network / refs/commits / 355fa3f6ae5c

difftreelog

feat(test xcm) added generic `low level` xcm tests

PraetorP2023-09-25parent: #f2a1159.patch.diff
in: master

4 files changed

modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -112,6 +112,7 @@
     "testCollators": "RUN_COLLATOR_TESTS=1 yarn _test ./**/collator-selection/**.*test.ts --timeout 49999999",
     "testCollatorSelection": "RUN_COLLATOR_TESTS=1 yarn _test ./**/collatorSelection.*test.ts --timeout 49999999",
     "testIdentity": "RUN_COLLATOR_TESTS=1 yarn _test ./**/identity.*test.ts --timeout 49999999",
+    "testLowLevelXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/lowLevelXcmUnique.test.ts",
     "testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmUnique.test.ts",
     "testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmQuartz.test.ts",
     "testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts",
@@ -154,4 +155,4 @@
   },
   "type": "module",
   "packageManager": "yarn@3.6.1"
-}
+}
\ No newline at end of file
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -458,9 +458,9 @@
   session: SessionGroup;
   testUtils: TestUtilGroup;
   foreignAssets: ForeignAssetsGroup;
-  xcm: XcmGroup<UniqueHelper>;
-  xTokens: XTokensGroup<UniqueHelper>;
-  tokens: TokensGroup<UniqueHelper>;
+  xcm: XcmGroup<DevUniqueHelper>;
+  xTokens: XTokensGroup<DevUniqueHelper>;
+  tokens: TokensGroup<DevUniqueHelper>;
   scheduler: SchedulerGroup;
   collatorSelection: CollatorSelectionGroup;
   council: ICollectiveGroup;
@@ -627,10 +627,10 @@
     super(logger, options);
     this.wait = new WaitGroup(this);
   }
-  getSudo<T extends AcalaHelper>() {
+  getSudo() {
     // eslint-disable-next-line @typescript-eslint/naming-convention
     const SudoHelperType = SudoHelper(this.helperBase);
-    return this.clone(SudoHelperType) as T;
+    return this.clone(SudoHelperType) as DevAcalaHelper;
   }
 }
 
@@ -643,10 +643,10 @@
     this.wait = new WaitGroup(this);
   }
 
-  getSudo<T extends PolkadexHelper>() {
+  getSudo() {
     // eslint-disable-next-line @typescript-eslint/naming-convention
     const SudoHelperType = SudoHelper(this.helperBase);
-    return this.clone(SudoHelperType) as T;
+    return this.clone(SudoHelperType) as DevPolkadexHelper;
   }
 }
 
@@ -1290,7 +1290,8 @@
         } else if(maxBlocksToWait > 0) {
           maxBlocksToWait--;
         } else {
-          this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);
+          this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found.
+          The wait lasted until block ${blockNumber} inclusive`);
           unsubscribe();
           resolve(null);
         }
addedtests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -0,0 +1,775 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+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 {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);
+
+
+
+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,
+  randomAccountOnTargetChain = randomAccount,
+) {
+  const networkUrl = mapToChainUrl(networkName);
+  const targetPlayground = getDevPlayground(networkName);
+  await usingPlaygrounds(async (helper) => {
+    balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+    const destination = {
+      V2: {
+        parents: 1,
+        interior: {
+          X1: {
+            Parachain: mapToChainId(networkName),
+          },
+        },
+      },
+    };
+
+    const beneficiary = {
+      V2: {
+        parents: 0,
+        interior: {
+          X1: (
+            networkName == 'moonbeam' ?
+              {
+                AccountKey20: {
+                  network: 'Any',
+                  key: randomAccountOnTargetChain.address,
+                },
+              }
+              :
+              {
+                AccountId32: {
+                  network: 'Any',
+                  id: randomAccountOnTargetChain.addressRaw,
+                },
+              }
+          ),
+        },
+      },
+    };
+
+    const assets = {
+      V2: [
+        {
+          id: {
+            Concrete: {
+              parents: 0,
+              interior: 'Here',
+            },
+          },
+          fun: {
+            Fungible: TRANSFER_AMOUNT,
+          },
+        },
+      ],
+    };
+    const feeAssetItem = 0;
+
+    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
+    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
+
+    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
+    console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));
+    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
+
+    await targetPlayground(networkUrl, async (helper) => {
+      /*
+        Since only the parachain part of the Polkadex
+        infrastructure is launched (without their
+        solochain validators), processing incoming
+        assets will lead to an error.
+        This error indicates that the Polkadex chain
+        received a message from the Unique network,
+        since the hash is being checked to ensure
+        it matches what was sent.
+      */
+      if(networkName == 'polkadex') {
+        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);
+      } else {
+        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);
+      }
+    });
+
+  });
+}
+
+async function genericSendUnqBack(
+  networkName: keyof typeof NETWORKS,
+  sudoer: IKeyringPair,
+  randomAccountOnUnq: IKeyringPair,
+) {
+  const networkUrl = mapToChainUrl(networkName);
+
+  const targetPlayground = getDevPlayground(networkName);
+  await usingPlaygrounds(async (helper) => {
+
+    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
+      randomAccountOnUnq.addressRaw,
+      {
+        Concrete: {
+          parents: 1,
+          interior: {
+            X1: {Parachain: UNIQUE_CHAIN},
+          },
+        },
+      },
+      SENDBACK_AMOUNT,
+    );
+
+    let xcmProgramSent: any;
+
+
+    await targetPlayground(networkUrl, async (helper) => {
+      if('getSudo' in helper) {
+        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, 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]);
+        // 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);
+        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+      }
+    });
+
+    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
+
+    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
+
+    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);
+
+  });
+}
+
+async function genericSendOnlyOwnedBalance(
+  networkName: keyof typeof NETWORKS,
+  sudoer: IKeyringPair,
+) {
+  const networkUrl = mapToChainUrl(networkName);
+  const targetPlayground = getDevPlayground(networkName);
+
+  const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);
+
+  await usingPlaygrounds(async (helper) => {
+    const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));
+    await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);
+    const moreThanTargetChainHas = 2n * targetChainBalance;
+
+    const targetAccount = helper.arrange.createEmptyAccount();
+
+    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      moreThanTargetChainHas,
+    );
+
+    let maliciousXcmProgramSent: any;
+
+
+    await targetPlayground(networkUrl, async (helper) => {
+      if('getSudo' in helper) {
+        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, 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]);
+        // 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);
+        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+      }
+    });
+
+    await expectFailedToTransact(helper, maliciousXcmProgramSent);
+
+    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(targetAccountBalance).to.be.equal(0n);
+  });
+}
+
+async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {
+  const networkUrl = mapToChainUrl(netwokrName);
+  const targetPlayground = getDevPlayground(netwokrName);
+
+  await usingPlaygrounds(async (helper) => {
+    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
+    const targetAccount = helper.arrange.createEmptyAccount();
+
+    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 1,
+          interior: {
+            X1: {
+              Parachain: UNIQUE_CHAIN,
+            },
+          },
+        },
+      },
+      testAmount,
+    );
+
+    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      testAmount,
+    );
+
+    let maliciousXcmProgramFullIdSent: any;
+    let maliciousXcmProgramHereIdSent: any;
+    const maxWaitBlocks = 3;
+
+    // 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);
+        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]);
+
+        // 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);
+
+        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+      }
+    });
+
+
+    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);
+
+    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
+
+    // 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);
+        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+      }
+      else if('fastDemocracy' in helper) {
+        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, 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);
+
+        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+      }
+    });
+
+    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);
+
+    accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
+  });
+}
+
+async function genericRejectNativeToknsFrom(netwokrName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {
+  const networkUrl = mapToChainUrl(netwokrName);
+  const targetPlayground = getDevPlayground(netwokrName);
+  let messageSent: any;
+
+  await usingPlaygrounds(async (helper) => {
+    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      helper.arrange.createEmptyAccount().addressRaw,
+      {
+        Concrete: {
+          parents: 1,
+          interior: {
+            X1: {
+              Parachain: mapToChainId(netwokrName),
+            },
+          },
+        },
+      },
+      TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,
+    );
+    await targetPlayground(networkUrl, async (helper) => {
+      if('getSudo' in helper) {
+        await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueMultilocation, 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]);
+
+        // 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);
+
+        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+      }
+    });
+
+    await expectFailedToTransact(helper, messageSent);
+  });
+}
+
+
+describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {
+  let alice: IKeyringPair;
+  let randomAccount: IKeyringPair;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = await privateKey('//Alice');
+      console.log(config.acalaUrl);
+      randomAccount = helper.arrange.createEmptyAccount();
+
+      // Set the default version to wrap the first message to other chains.
+      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+    });
+
+    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
+      const destination = {
+        V2: {
+          parents: 1,
+          interior: {
+            X1: {
+              Parachain: UNIQUE_CHAIN,
+            },
+          },
+        },
+      };
+
+      const metadata = {
+        name: 'Unique Network',
+        symbol: 'UNQ',
+        decimals: 18,
+        minimalBalance: 1250_000_000_000_000_000n,
+      };
+      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>
+        hexToString(v.toJSON()['symbol'])) as string[];
+
+      if(!assets.includes('UNQ')) {
+        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+      } else {
+        console.log('UNQ token already registered on Acala assetRegistry pallet');
+      }
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
+    });
+
+    await usingPlaygrounds(async (helper) => {
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
+      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+    });
+  });
+
+  itSub('Should connect and send UNQ to Acala', async () => {
+    await genericSendUnqTo('acala', randomAccount);
+  });
+
+  itSub('Should connect to Acala and send UNQ back', async () => {
+    await genericSendUnqBack('acala', alice, randomAccount);
+  });
+
+  itSub('Acala can send only up to its balance', async () => {
+    await genericSendOnlyOwnedBalance('acala', alice);
+  });
+
+  itSub('Should not accept reserve transfer of UNQ from Acala', async () => {
+    await genericReserveTransferUNQfrom('acala', alice);
+  });
+});
+
+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) => {
+      alice = await privateKey('//Alice');
+      randomAccount = helper.arrange.createEmptyAccount();
+
+      // Set the default version to wrap the first message to other chains.
+      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+    });
+
+    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
+      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))
+        .toJSON() as [])
+        .map(nToBigInt).length != 0;
+      /*
+      Check whether the Unique token has been added
+      to the whitelist, since an error will occur
+      if it is added again. Needed for debugging
+      when this test is run multiple times.
+      */
+      if(!isWhitelisted) {
+        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);
+      }
+
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
+    });
+
+    await usingPlaygrounds(async (helper) => {
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
+      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+    });
+  });
+
+  itSub('Should connect and send UNQ to Polkadex', async () => {
+    await genericSendUnqTo('polkadex', randomAccount);
+  });
+
+
+  itSub('Should connect to Polkadex and send UNQ back', async () => {
+    await genericSendUnqBack('polkadex', alice, randomAccount);
+  });
+
+  itSub('Polkadex can send only up to its balance', async () => {
+    await genericSendOnlyOwnedBalance('polkadex', alice);
+  });
+
+  itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {
+    await genericReserveTransferUNQfrom('polkadex', alice);
+  });
+});
+
+// These tests are relevant only when
+// the the corresponding foreign assets are not registered
+describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {
+  let alice: IKeyringPair;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = await privateKey('//Alice');
+
+      // Set the default version to wrap the first message to other chains.
+      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+    });
+  });
+
+  itSub('Unique rejects ACA tokens from Acala', async () => {
+    await genericRejectNativeToknsFrom('acala', alice);
+  });
+
+  itSub('Unique rejects GLMR tokens from Moonbeam', async () => {
+    await genericRejectNativeToknsFrom('moonbeam', alice);
+  });
+
+  itSub('Unique rejects ASTR tokens from Astar', async () => {
+    await genericRejectNativeToknsFrom('astar', alice);
+  });
+
+  itSub('Unique rejects PDX tokens from Polkadex', async () => {
+    await genericRejectNativeToknsFrom('polkadex', alice);
+  });
+});
+
+describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {
+  // Unique constants
+  let alice: IKeyringPair;
+  let uniqueAssetLocation;
+
+  let randomAccountUnique: IKeyringPair;
+  let randomAccountMoonbeam: IKeyringPair;
+
+  // Moonbeam constants
+  let assetId: string;
+
+  const uniqueAssetMetadata = {
+    name: 'xcUnique',
+    symbol: 'xcUNQ',
+    decimals: 18,
+    isFrozen: false,
+    minimalBalance: 1n,
+  };
+
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = await privateKey('//Alice');
+      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);
+
+
+      // Set the default version to wrap the first message to other chains.
+      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+    });
+
+    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
+      const alithAccount = helper.account.alithAccount();
+      const baltatharAccount = helper.account.baltatharAccount();
+      const dorothyAccount = helper.account.dorothyAccount();
+
+      randomAccountMoonbeam = helper.account.create();
+
+      // >>> Sponsoring Dorothy >>>
+      console.log('Sponsoring Dorothy.......');
+      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);
+      console.log('Sponsoring Dorothy.......DONE');
+      // <<< Sponsoring Dorothy <<<
+      uniqueAssetLocation = {
+        XCM: {
+          parents: 1,
+          interior: {X1: {Parachain: UNIQUE_CHAIN}},
+        },
+      };
+      const existentialDeposit = 1n;
+      const isSufficient = true;
+      const unitsPerSecond = 1n;
+      const numAssetsWeightHint = 0;
+
+      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {
+        console.log('Unique asset already registered');
+      } else {
+        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
+          location: uniqueAssetLocation,
+          metadata: uniqueAssetMetadata,
+          existentialDeposit,
+          isSufficient,
+          unitsPerSecond,
+          numAssetsWeightHint,
+        });
+
+        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
+
+        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);
+
+        // >>> Acquire Unique AssetId Info on Moonbeam >>>
+        console.log('Acquire Unique AssetId Info on Moonbeam.......');
+
+        assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();
+
+        console.log('UNQ asset ID is %s', assetId);
+        console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');
+      }
+      // >>> Acquire Unique AssetId Info on Moonbeam >>>
+
+      // >>> Sponsoring random Account >>>
+      console.log('Sponsoring random Account.......');
+      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);
+      console.log('Sponsoring random Account.......DONE');
+      // <<< Sponsoring random Account <<<
+    });
+
+    await usingPlaygrounds(async (helper) => {
+      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);
+      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
+    });
+  });
+
+  itSub('Should connect and send UNQ to Moonbeam', async () => {
+    await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);
+  });
+
+  itSub('Should connect to Moonbeam and send UNQ back', async () => {
+    await genericSendUnqBack('moonbeam', alice, randomAccountUnique);
+  });
+
+  itSub('Moonbeam can send only up to its balance', async () => {
+    await genericSendOnlyOwnedBalance('moonbeam', alice);
+  });
+
+  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {
+    await genericReserveTransferUNQfrom('moonbeam', alice);
+  });
+});
+
+describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {
+  let alice: IKeyringPair;
+  let randomAccount: IKeyringPair;
+
+  const UNQ_ASSET_ID_ON_ASTAR = 1;
+  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;
+
+  // Unique -> Astar
+  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.
+  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = await privateKey('//Alice');
+      randomAccount = helper.arrange.createEmptyAccount();
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
+      console.log('randomAccount', randomAccount.address);
+
+      // Set the default version to wrap the first message to other chains.
+      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+    });
+
+    await usingAstarPlaygrounds(astarUrl, async (helper) => {
+      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {
+        console.log('1. Create foreign asset and metadata');
+        // TODO update metadata with values from production
+        await helper.assets.create(
+          alice,
+          UNQ_ASSET_ID_ON_ASTAR,
+          alice.address,
+          UNQ_MINIMAL_BALANCE_ON_ASTAR,
+        );
+
+        await helper.assets.setMetadata(
+          alice,
+          UNQ_ASSET_ID_ON_ASTAR,
+          'Cross chain UNQ',
+          'xcUNQ',
+          Number(UNQ_DECIMALS),
+        );
+
+        console.log('2. Register asset location on Astar');
+        const assetLocation = {
+          V2: {
+            parents: 1,
+            interior: {
+              X1: {
+                Parachain: UNIQUE_CHAIN,
+              },
+            },
+          },
+        };
+
+        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);
+
+        console.log('3. Set UNQ payment for XCM execution on Astar');
+        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+      }
+      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);
+    });
+  });
+
+  itSub('Should connect and send UNQ to Astar', async () => {
+    await genericSendUnqTo('astar', randomAccount);
+  });
+
+  itSub('Should connect to Astar and send UNQ back', async () => {
+    await genericSendUnqBack('astar', alice, randomAccount);
+  });
+
+  itSub('Astar can send only up to its balance', async () => {
+    await genericSendOnlyOwnedBalance('astar', alice);
+  });
+
+  itSub('Should not accept reserve transfer of UNQ from Astar', async () => {
+    await genericReserveTransferUNQfrom('astar', alice);
+  });
+});
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 {nToBigInt} from '@polkadot/util';2223const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);24const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);25const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);26const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);27const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);28const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);2930const STATEMINT_PALLET_INSTANCE = 50;3132const relayUrl = config.relayUrl;33const statemintUrl = config.statemintUrl;34const acalaUrl = config.acalaUrl;35const moonbeamUrl = config.moonbeamUrl;36const astarUrl = config.astarUrl;37const polkadexUrl = config.polkadexUrl;3839const RELAY_DECIMALS = 12;40const STATEMINT_DECIMALS = 12;41const ACALA_DECIMALS = 12;42const ASTAR_DECIMALS = 18n;43const UNQ_DECIMALS = 18n;4445const TRANSFER_AMOUNT = 2000000000000000000000000n;4647const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4849const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;5051const USDT_ASSET_ID = 100;52const USDT_ASSET_METADATA_DECIMALS = 18;53const USDT_ASSET_METADATA_NAME = 'USDT';54const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';55const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;56const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5758const SAFE_XCM_VERSION = 2;59const maxWaitBlocks = 6;60const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {61  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash62        && event.outcome.isFailedToTransactAsset);63};64const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {65  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash66         && event.outcome.isUntrustedReserveLocation);67};68describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {69  let alice: IKeyringPair;70  let bob: IKeyringPair;7172  let balanceStmnBefore: bigint;73  let balanceStmnAfter: bigint;7475  let balanceUniqueBefore: bigint;76  let balanceUniqueAfter: bigint;77  let balanceUniqueFinal: bigint;7879  let balanceBobBefore: bigint;80  let balanceBobAfter: bigint;81  let balanceBobFinal: bigint;8283  let balanceBobRelayTokenBefore: bigint;84  let balanceBobRelayTokenAfter: bigint;858687  before(async () => {88    await usingPlaygrounds(async (helper, privateKey) => {89      alice = await privateKey('//Alice');90      bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor9192      // Set the default version to wrap the first message to other chains.93      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);94    });9596    await usingRelayPlaygrounds(relayUrl, async (helper) => {97      // Fund accounts on Statemint98      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);99      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);100    });101102    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {103      const sovereignFundingAmount = 3_500_000_000n;104105      await helper.assets.create(106        alice,107        USDT_ASSET_ID,108        alice.address,109        USDT_ASSET_METADATA_MINIMAL_BALANCE,110      );111      await helper.assets.setMetadata(112        alice,113        USDT_ASSET_ID,114        USDT_ASSET_METADATA_NAME,115        USDT_ASSET_METADATA_DESCRIPTION,116        USDT_ASSET_METADATA_DECIMALS,117      );118      await helper.assets.mint(119        alice,120        USDT_ASSET_ID,121        alice.address,122        USDT_ASSET_AMOUNT,123      );124125      // funding parachain sovereing account on Statemint.126      // The sovereign account should be created before any action127      // (the assets pallet on Statemint check if the sovereign account exists)128      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);129      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);130    });131132133    await usingPlaygrounds(async (helper) => {134      const location = {135        V2: {136          parents: 1,137          interior: {X3: [138            {139              Parachain: STATEMINT_CHAIN,140            },141            {142              PalletInstance: STATEMINT_PALLET_INSTANCE,143            },144            {145              GeneralIndex: USDT_ASSET_ID,146            },147          ]},148        },149      };150151      const metadata =152      {153        name: USDT_ASSET_ID,154        symbol: USDT_ASSET_METADATA_NAME,155        decimals: USDT_ASSET_METADATA_DECIMALS,156        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,157      };158      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);159      balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);160    });161162163    // Providing the relay currency to the unique sender account164    // (fee for USDT XCM are paid in relay tokens)165    await usingRelayPlaygrounds(relayUrl, async (helper) => {166      const destination = {167        V2: {168          parents: 0,169          interior: {X1: {170            Parachain: UNIQUE_CHAIN,171          },172          },173        }};174175      const beneficiary = {176        V2: {177          parents: 0,178          interior: {X1: {179            AccountId32: {180              network: 'Any',181              id: alice.addressRaw,182            },183          }},184        },185      };186187      const assets = {188        V2: [189          {190            id: {191              Concrete: {192                parents: 0,193                interior: 'Here',194              },195            },196            fun: {197              Fungible: TRANSFER_AMOUNT_RELAY,198            },199          },200        ],201      };202203      const feeAssetItem = 0;204205      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');206    });207208  });209210  itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {211    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {212      const dest = {213        V2: {214          parents: 1,215          interior: {X1: {216            Parachain: UNIQUE_CHAIN,217          },218          },219        }};220221      const beneficiary = {222        V2: {223          parents: 0,224          interior: {X1: {225            AccountId32: {226              network: 'Any',227              id: alice.addressRaw,228            },229          }},230        },231      };232233      const assets = {234        V2: [235          {236            id: {237              Concrete: {238                parents: 0,239                interior: {240                  X2: [241                    {242                      PalletInstance: STATEMINT_PALLET_INSTANCE,243                    },244                    {245                      GeneralIndex: USDT_ASSET_ID,246                    },247                  ]},248              },249            },250            fun: {251              Fungible: TRANSFER_AMOUNT,252            },253          },254        ],255      };256257      const feeAssetItem = 0;258259      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);260      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');261262      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);263264      // common good parachain take commission in it native token265      console.log(266        '[Statemint -> Unique] transaction fees on Statemint: %s WND',267        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),268      );269      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;270271    });272273274    // ensure that asset has been delivered275    await helper.wait.newBlocks(3);276277    // expext collection id will be with id 1278    const free = await helper.ft.getBalance(1, {Substrate: alice.address});279280    balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);281282    console.log(283      '[Statemint -> Unique] transaction fees on Unique: %s USDT',284      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),285    );286    console.log(287      '[Statemint -> Unique] transaction fees on Unique: %s UNQ',288      helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),289    );290    // commission has not paid in USDT token291    expect(free).to.be.equal(TRANSFER_AMOUNT);292    // ... and parachain native token293    expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;294  });295296  itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {297    const destination = {298      V2: {299        parents: 1,300        interior: {X2: [301          {302            Parachain: STATEMINT_CHAIN,303          },304          {305            AccountId32: {306              network: 'Any',307              id: alice.addressRaw,308            },309          },310        ]},311      },312    };313314    const relayFee = 400_000_000_000_000n;315    const currencies: [any, bigint][] = [316      [317        {318          ForeignAssetId: 0,319        },320        TRANSFER_AMOUNT,321      ],322      [323        {324          NativeAssetId: 'Parent',325        },326        relayFee,327      ],328    ];329330    const feeItem = 1;331332    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');333334    // the commission has been paid in parachain native token335    balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);336    console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));337    expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;338339    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {340      await helper.wait.newBlocks(3);341342      // The USDT token never paid fees. Its amount not changed from begin value.343      // Also check that xcm transfer has been succeeded344      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;345    });346  });347348  itSub('Should connect and send Relay token to Unique', async ({helper}) => {349    balanceBobBefore = await helper.balance.getSubstrate(bob.address);350    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});351352    await usingRelayPlaygrounds(relayUrl, async (helper) => {353      const destination = {354        V2: {355          parents: 0,356          interior: {X1: {357            Parachain: UNIQUE_CHAIN,358          },359          },360        }};361362      const beneficiary = {363        V2: {364          parents: 0,365          interior: {X1: {366            AccountId32: {367              network: 'Any',368              id: bob.addressRaw,369            },370          }},371        },372      };373374      const assets = {375        V2: [376          {377            id: {378              Concrete: {379                parents: 0,380                interior: 'Here',381              },382            },383            fun: {384              Fungible: TRANSFER_AMOUNT_RELAY,385            },386          },387        ],388      };389390      const feeAssetItem = 0;391392      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');393    });394395    await helper.wait.newBlocks(3);396397    balanceBobAfter = await helper.balance.getSubstrate(bob.address);398    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});399400    const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;401    const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;402    console.log(403      '[Relay (Westend) -> Unique] transaction fees: %s UNQ',404      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),405    );406    console.log(407      '[Relay (Westend) -> Unique] transaction fees: %s WND',408      helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),409    );410    console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);411    expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;412    expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;413  });414415  itSub('Should connect and send Relay token back', async ({helper}) => {416    let relayTokenBalanceBefore: bigint;417    let relayTokenBalanceAfter: bigint;418    await usingRelayPlaygrounds(relayUrl, async (helper) => {419      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);420    });421422    const destination = {423      V2: {424        parents: 1,425        interior: {426          X1:{427            AccountId32: {428              network: 'Any',429              id: bob.addressRaw,430            },431          },432        },433      },434    };435436    const currencies: any = [437      [438        {439          NativeAssetId: 'Parent',440        },441        TRANSFER_AMOUNT_RELAY,442      ],443    ];444445    const feeItem = 0;446447    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');448449    balanceBobFinal = await helper.balance.getSubstrate(bob.address);450    console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));451452    await usingRelayPlaygrounds(relayUrl, async (helper) => {453      await helper.wait.newBlocks(10);454      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);455456      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;457      console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));458      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;459    });460  });461});462463describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {464  let alice: IKeyringPair;465  let randomAccount: IKeyringPair;466467  let balanceUniqueTokenInit: bigint;468  let balanceUniqueTokenMiddle: bigint;469  let balanceUniqueTokenFinal: bigint;470  let balanceAcalaTokenInit: bigint;471  let balanceAcalaTokenMiddle: bigint;472  let balanceAcalaTokenFinal: bigint;473  let balanceUniqueForeignTokenInit: bigint;474  let balanceUniqueForeignTokenMiddle: bigint;475  let balanceUniqueForeignTokenFinal: bigint;476477  // computed by a test transfer from prod Unique to prod Acala.478  // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48479  // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)480  const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;481  const acalaEps = 8n * 10n ** 16n;482483  let acalaBackwardTransferAmount: bigint;484485  before(async () => {486    await usingPlaygrounds(async (helper, privateKey) => {487      alice = await privateKey('//Alice');488      [randomAccount] = await helper.arrange.createAccounts([0n], alice);489490      // Set the default version to wrap the first message to other chains.491      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);492    });493494    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {495      const destination = {496        V2: {497          parents: 1,498          interior: {499            X1: {500              Parachain: UNIQUE_CHAIN,501            },502          },503        },504      };505506      const metadata = {507        name: 'Unique Network',508        symbol: 'UNQ',509        decimals: 18,510        minimalBalance: 1250000000000000000n,511      };512513      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);514      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);515      balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);516      balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});517    });518519    await usingPlaygrounds(async (helper) => {520      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);521      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);522    });523  });524525  itSub('Should connect and send UNQ to Acala', async ({helper}) => {526527    const destination = {528      V2: {529        parents: 1,530        interior: {531          X1: {532            Parachain: ACALA_CHAIN,533          },534        },535      },536    };537538    const beneficiary = {539      V2: {540        parents: 0,541        interior: {542          X1: {543            AccountId32: {544              network: 'Any',545              id: randomAccount.addressRaw,546            },547          },548        },549      },550    };551552    const assets = {553      V2: [554        {555          id: {556            Concrete: {557              parents: 0,558              interior: 'Here',559            },560          },561          fun: {562            Fungible: TRANSFER_AMOUNT,563          },564        },565      ],566    };567568    const feeAssetItem = 0;569570    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');571    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);572573    const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;574    console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));575    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;576577    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {578      await helper.wait.newBlocks(3);579580      balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});581      balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);582583      const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;584      const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;585      acalaBackwardTransferAmount = unqIncomeTransfer;586587      const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;588589      console.log(590        '[Unique -> Acala] transaction fees on Acala: %s ACA',591        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),592      );593      console.log(594        '[Unique -> Acala] transaction fees on Acala: %s UNQ',595        helper.util.bigIntToDecimals(acaUnqFees),596      );597      console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));598      expect(acaFees == 0n).to.be.true;599600      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;601602      expect(603        bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,604        'Acala took different income fee, check the Acala foreign asset config',605      ).to.be.true;606    });607  });608609  itSub('Should connect to Acala and send UNQ back', async ({helper}) => {610    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {611      const destination = {612        V2: {613          parents: 1,614          interior: {615            X2: [616              {Parachain: UNIQUE_CHAIN},617              {618                AccountId32: {619                  network: 'Any',620                  id: randomAccount.addressRaw,621                },622              },623            ],624          },625        },626      };627628      const id = {629        ForeignAsset: 0,630      };631632      await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');633      balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);634      balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);635636      const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;637      const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;638639      console.log(640        '[Acala -> Unique] transaction fees on Acala: %s ACA',641        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),642      );643      console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));644645      expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;646      expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;647    });648649    await helper.wait.newBlocks(3);650651    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);652    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;653    expect(actuallyDelivered > 0).to.be.true;654655    console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));656657    const unqFees = acalaBackwardTransferAmount - actuallyDelivered;658    console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));659    expect(unqFees == 0n).to.be.true;660  });661662  itSub('Acala can send only up to its balance', async ({helper}) => {663    // set Acala's sovereign account's balance664    const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);665    const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);666    await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);667668    const moreThanAcalaHas = acalaBalance * 2n;669670    let targetAccountBalance = 0n;671    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);672673    const uniqueMultilocation = {674      V2: {675        parents: 1,676        interior: {677          X1: {Parachain: UNIQUE_CHAIN},678        },679      },680    };681682    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(683      targetAccount.addressRaw,684      {685        Concrete: {686          parents: 0,687          interior: 'Here',688        },689      },690      moreThanAcalaHas,691    );692693    let maliciousXcmProgramSent: any;694    const maxWaitBlocks = 3;695696    // Try to trick Unique697    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {698      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);699700      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);701    });702703    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash704        && event.outcome.isFailedToTransactAsset);705706    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);707    expect(targetAccountBalance).to.be.equal(0n);708709    // But Acala still can send the correct amount710    const validTransferAmount = acalaBalance / 2n;711    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(712      targetAccount.addressRaw,713      {714        Concrete: {715          parents: 0,716          interior: 'Here',717        },718      },719      validTransferAmount,720    );721722    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {723      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);724    });725726    await helper.wait.newBlocks(maxWaitBlocks);727728    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);729    expect(targetAccountBalance).to.be.equal(validTransferAmount);730  });731732  itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {733    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);734    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);735736    const uniqueMultilocation = {737      V2: {738        parents: 1,739        interior: {740          X1: {741            Parachain: UNIQUE_CHAIN,742          },743        },744      },745    };746747    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(748      targetAccount.addressRaw,749      {750        Concrete: {751          parents: 1,752          interior: {753            X1: {754              Parachain: UNIQUE_CHAIN,755            },756          },757        },758      },759      testAmount,760    );761762    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(763      targetAccount.addressRaw,764      {765        Concrete: {766          parents: 0,767          interior: 'Here',768        },769      },770      testAmount,771    );772773    let maliciousXcmProgramFullIdSent: any;774    let maliciousXcmProgramHereIdSent: any;775    const maxWaitBlocks = 3;776777    // Try to trick Unique using full UNQ identification778    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {779      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);780781      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);782    });783784    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash785        && event.outcome.isUntrustedReserveLocation);786787    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);788    expect(accountBalance).to.be.equal(0n);789790    // Try to trick Unique using shortened UNQ identification791    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {792      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);793794      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);795    });796797    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash798        && event.outcome.isUntrustedReserveLocation);799800    accountBalance = await helper.balance.getSubstrate(targetAccount.address);801    expect(accountBalance).to.be.equal(0n);802  });803});804805describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {806  let alice: IKeyringPair;807  let randomAccount: IKeyringPair;808  let unqFees: bigint;809  let balanceUniqueTokenInit: bigint;810  let balanceUniqueTokenMiddle: bigint;811  let balanceUniqueTokenFinal: bigint;812  const maxWaitBlocks = 6;813814  const uniqueAssetId = {815    Concrete: {816      parents: 1,817      interior: {818        X1: {819          Parachain: UNIQUE_CHAIN,820        },821      },822    },823  };824825  before(async () => {826    await usingPlaygrounds(async (helper, privateKey) => {827      alice = await privateKey('//Alice');828      [randomAccount] = await helper.arrange.createAccounts([0n], alice);829830      // Set the default version to wrap the first message to other chains.831      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);832    });833834    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {835      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))836        .toJSON() as [])837        .map(nToBigInt).length != 0;838      /*839      Check whether the Unique token has been added840      to the whitelist, since an error will occur841      if it is added again. Needed for debugging842      when this test is run multiple times.843      */844      if(!isWhitelisted) {845        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);846      }847848      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);849    });850851    await usingPlaygrounds(async (helper) => {852      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);853      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);854    });855  });856857  itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {858859    const destination = {860      V2: {861        parents: 1,862        interior: {863          X1: {864            Parachain: POLKADEX_CHAIN,865          },866        },867      },868    };869870    const beneficiary = {871      V2: {872        parents: 0,873        interior: {874          X1: {875            AccountId32: {876              network: 'Any',877              id: randomAccount.addressRaw,878            },879          },880        },881      },882    };883884    const assets = {885      V2: [886        {887          id: {888            Concrete: {889              parents: 0,890              interior: 'Here',891            },892          },893          fun: {894            Fungible: TRANSFER_AMOUNT,895          },896        },897      ],898    };899900    const feeAssetItem = 0;901902    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');903    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);904    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);905906    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;907    console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));908    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;909910    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {911      /*912        Since only the parachain part of the Polkadex913        infrastructure is launched (without their914        solochain validators), processing incoming915        assets will lead to an error.916        This error indicates that the Polkadex chain917        received a message from the Unique network,918        since the hash is being checked to ensure919        it matches what was sent.920      */921      await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);922    });923  });924925926  itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {927928    const uniqueMultilocation = {929      V2: {930        parents: 1,931        interior: {932          X1: {Parachain: UNIQUE_CHAIN},933        },934      },935    };936937    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(938      randomAccount.addressRaw,939      {940        Concrete: {941          parents: 1,942          interior: {943            X1: {Parachain: UNIQUE_CHAIN},944          },945        },946      },947      TRANSFER_AMOUNT,948    );949950    let xcmProgramSent: any;951952953    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {954      await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);955956      xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);957    });958959    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);960961    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);962963    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);964  });965966  itSub('Polkadex can send only up to its balance', async ({helper}) => {967    const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);968    const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);969    await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);970    const moreThanPolkadexHas = 2n * polkadexBalance;971972    const targetAccount = helper.arrange.createEmptyAccount();973974    const uniqueMultilocation = {975      V2: {976        parents: 1,977        interior: {978          X1: {Parachain: UNIQUE_CHAIN},979        },980      },981    };982983    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(984      targetAccount.addressRaw,985      {986        Concrete: {987          parents: 0,988          interior: 'Here',989        },990      },991      moreThanPolkadexHas,992    );993994    let maliciousXcmProgramSent: any;995996997    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {998      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);9991000      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1001    });10021003    await expectFailedToTransact(helper, maliciousXcmProgramSent);10041005    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1006    expect(targetAccountBalance).to.be.equal(0n);1007  });10081009  itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {1010    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1011    const targetAccount = helper.arrange.createEmptyAccount();10121013    const uniqueMultilocation = {1014      V2: {1015        parents: 1,1016        interior: {1017          X1: {1018            Parachain: UNIQUE_CHAIN,1019          },1020        },1021      },1022    };10231024    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1025      targetAccount.addressRaw,1026      {1027        Concrete: {1028          parents: 1,1029          interior: {1030            X1: {1031              Parachain: UNIQUE_CHAIN,1032            },1033          },1034        },1035      },1036      testAmount,1037    );10381039    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1040      targetAccount.addressRaw,1041      {1042        Concrete: {1043          parents: 0,1044          interior: 'Here',1045        },1046      },1047      testAmount,1048    );10491050    let maliciousXcmProgramFullIdSent: any;1051    let maliciousXcmProgramHereIdSent: any;1052    const maxWaitBlocks = 3;10531054    // Try to trick Unique using full UNQ identification1055    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1056      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10571058      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1059    });10601061    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10621063    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1064    expect(accountBalance).to.be.equal(0n);10651066    // Try to trick Unique using shortened UNQ identification1067    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1068      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10691070      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1071    });10721073    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10741075    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1076    expect(accountBalance).to.be.equal(0n);1077  });1078});10791080// These tests are relevant only when1081// the the corresponding foreign assets are not registered1082describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1083  let alice: IKeyringPair;1084  let alith: IKeyringPair;10851086  const testAmount = 100_000_000_000n;1087  let uniqueParachainJunction;1088  let uniqueAccountJunction;10891090  let uniqueParachainMultilocation: any;1091  let uniqueAccountMultilocation: any;1092  let uniqueCombinedMultilocation: any;1093  let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210941095  let messageSent: any;10961097  const maxWaitBlocks = 3;10981099  before(async () => {1100    await usingPlaygrounds(async (helper, privateKey) => {1101      alice = await privateKey('//Alice');11021103      uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1104      uniqueAccountJunction = {1105        AccountId32: {1106          network: 'Any',1107          id: alice.addressRaw,1108        },1109      };11101111      uniqueParachainMultilocation = {1112        V2: {1113          parents: 1,1114          interior: {1115            X1: uniqueParachainJunction,1116          },1117        },1118      };11191120      uniqueAccountMultilocation = {1121        V2: {1122          parents: 0,1123          interior: {1124            X1: uniqueAccountJunction,1125          },1126        },1127      };11281129      uniqueCombinedMultilocation = {1130        V2: {1131          parents: 1,1132          interior: {1133            X2: [uniqueParachainJunction, uniqueAccountJunction],1134          },1135        },1136      };11371138      uniqueCombinedMultilocationAcala = {1139        V2: {1140          parents: 1,1141          interior: {1142            X2: [uniqueParachainJunction, uniqueAccountJunction],1143          },1144        },1145      };11461147      // Set the default version to wrap the first message to other chains.1148      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1149    });11501151    // eslint-disable-next-line require-await1152    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1153      alith = helper.account.alithAccount();1154    });1155  });1156115711581159  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1160    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1161      const id = {1162        Token: 'ACA',1163      };1164      const destination = uniqueCombinedMultilocationAcala;1165      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11661167      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1168    });11691170    await expectFailedToTransact(helper, messageSent);1171  });11721173  itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1174    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1175      const id = 'SelfReserve';1176      const destination = uniqueCombinedMultilocation;1177      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11781179      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1180    });11811182    await expectFailedToTransact(helper, messageSent);1183  });11841185  itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1186    await usingAstarPlaygrounds(astarUrl, async (helper) => {1187      const destinationParachain = uniqueParachainMultilocation;1188      const beneficiary = uniqueAccountMultilocation;1189      const assets = {1190        V2: [{1191          id: {1192            Concrete: {1193              parents: 0,1194              interior: 'Here',1195            },1196          },1197          fun: {1198            Fungible: testAmount,1199          },1200        }],1201      };1202      const feeAssetItem = 0;12031204      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1205        destinationParachain,1206        beneficiary,1207        assets,1208        feeAssetItem,1209      ]);12101211      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1212    });12131214    await expectFailedToTransact(helper, messageSent);1215  });12161217  itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12181219    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1220      helper.arrange.createEmptyAccount().addressRaw,1221      {1222        Concrete: {1223          parents: 1,1224          interior: {1225            X1: {1226              Parachain: POLKADEX_CHAIN,1227            },1228          },1229        },1230      },1231      testAmount,1232    );12331234    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1235      await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1236      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1237    });12381239    await expectFailedToTransact(helper, messageSent);1240  });1241});12421243describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1244  // Unique constants1245  let alice: IKeyringPair;1246  let uniqueAssetLocation;12471248  let randomAccountUnique: IKeyringPair;1249  let randomAccountMoonbeam: IKeyringPair;12501251  // Moonbeam constants1252  let assetId: string;12531254  const uniqueAssetMetadata = {1255    name: 'xcUnique',1256    symbol: 'xcUNQ',1257    decimals: 18,1258    isFrozen: false,1259    minimalBalance: 1n,1260  };12611262  let balanceUniqueTokenInit: bigint;1263  let balanceUniqueTokenMiddle: bigint;1264  let balanceUniqueTokenFinal: bigint;1265  let balanceForeignUnqTokenInit: bigint;1266  let balanceForeignUnqTokenMiddle: bigint;1267  let balanceForeignUnqTokenFinal: bigint;1268  let balanceGlmrTokenInit: bigint;1269  let balanceGlmrTokenMiddle: bigint;1270  let balanceGlmrTokenFinal: bigint;12711272  before(async () => {1273    await usingPlaygrounds(async (helper, privateKey) => {1274      alice = await privateKey('//Alice');1275      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12761277      balanceForeignUnqTokenInit = 0n;12781279      // Set the default version to wrap the first message to other chains.1280      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1281    });12821283    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1284      const alithAccount = helper.account.alithAccount();1285      const baltatharAccount = helper.account.baltatharAccount();1286      const dorothyAccount = helper.account.dorothyAccount();12871288      randomAccountMoonbeam = helper.account.create();12891290      // >>> Sponsoring Dorothy >>>1291      console.log('Sponsoring Dorothy.......');1292      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1293      console.log('Sponsoring Dorothy.......DONE');1294      // <<< Sponsoring Dorothy <<<12951296      uniqueAssetLocation = {1297        XCM: {1298          parents: 1,1299          interior: {X1: {Parachain: UNIQUE_CHAIN}},1300        },1301      };1302      const existentialDeposit = 1n;1303      const isSufficient = true;1304      const unitsPerSecond = 1n;1305      const numAssetsWeightHint = 0;13061307      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1308        location: uniqueAssetLocation,1309        metadata: uniqueAssetMetadata,1310        existentialDeposit,1311        isSufficient,1312        unitsPerSecond,1313        numAssetsWeightHint,1314      });13151316      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);13171318      await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);13191320      // >>> Acquire Unique AssetId Info on Moonbeam >>>1321      console.log('Acquire Unique AssetId Info on Moonbeam.......');13221323      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();13241325      console.log('UNQ asset ID is %s', assetId);1326      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1327      // >>> Acquire Unique AssetId Info on Moonbeam >>>13281329      // >>> Sponsoring random Account >>>1330      console.log('Sponsoring random Account.......');1331      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1332      console.log('Sponsoring random Account.......DONE');1333      // <<< Sponsoring random Account <<<13341335      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1336    });13371338    await usingPlaygrounds(async (helper) => {1339      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1340      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1341    });1342  });13431344  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1345    const currencyId = {1346      NativeAssetId: 'Here',1347    };1348    const dest = {1349      V2: {1350        parents: 1,1351        interior: {1352          X2: [1353            {Parachain: MOONBEAM_CHAIN},1354            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1355          ],1356        },1357      },1358    };1359    const amount = TRANSFER_AMOUNT;13601361    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13621363    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1364    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13651366    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1367    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1368    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13691370    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1371      await helper.wait.newBlocks(3);13721373      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13741375      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1376      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1377      expect(glmrFees == 0n).to.be.true;13781379      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13801381      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1382      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1383      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1384    });1385  });13861387  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1388    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1389      const asset = {1390        V2: {1391          id: {1392            Concrete: {1393              parents: 1,1394              interior: {1395                X1: {Parachain: UNIQUE_CHAIN},1396              },1397            },1398          },1399          fun: {1400            Fungible: TRANSFER_AMOUNT,1401          },1402        },1403      };1404      const destination = {1405        V2: {1406          parents: 1,1407          interior: {1408            X2: [1409              {Parachain: UNIQUE_CHAIN},1410              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1411            ],1412          },1413        },1414      };14151416      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');14171418      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);14191420      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1421      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1422      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;14231424      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);14251426      expect(unqRandomAccountAsset).to.be.null;14271428      balanceForeignUnqTokenFinal = 0n;14291430      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1431      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1432      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1433    });14341435    await helper.wait.newBlocks(3);14361437    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1438    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1439    expect(actuallyDelivered > 0).to.be.true;14401441    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));14421443    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1444    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1445    expect(unqFees == 0n).to.be.true;1446  });14471448  itSub('Moonbeam can send only up to its balance', async ({helper}) => {1449    // set Moonbeam's sovereign account's balance1450    const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1451    const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1452    await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14531454    const moreThanMoonbeamHas = moonbeamBalance * 2n;14551456    let targetAccountBalance = 0n;1457    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14581459    const uniqueMultilocation = {1460      V2: {1461        parents: 1,1462        interior: {1463          X1: {Parachain: UNIQUE_CHAIN},1464        },1465      },1466    };14671468    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1469      targetAccount.addressRaw,1470      {1471        Concrete: {1472          parents: 0,1473          interior: 'Here',1474        },1475      },1476      moreThanMoonbeamHas,1477    );14781479    let maliciousXcmProgramSent: any;1480    const maxWaitBlocks = 3;14811482    // Try to trick Unique1483    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1484      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14851486      // Needed to bypass the call filter.1487      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1488      await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14891490      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1491    });14921493    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1494        && event.outcome.isFailedToTransactAsset);14951496    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1497    expect(targetAccountBalance).to.be.equal(0n);14981499    // But Moonbeam still can send the correct amount1500    const validTransferAmount = moonbeamBalance / 2n;1501    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1502      targetAccount.addressRaw,1503      {1504        Concrete: {1505          parents: 0,1506          interior: 'Here',1507        },1508      },1509      validTransferAmount,1510    );15111512    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1513      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);15141515      // Needed to bypass the call filter.1516      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1517      await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1518    });15191520    await helper.wait.newBlocks(maxWaitBlocks);15211522    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1523    expect(targetAccountBalance).to.be.equal(validTransferAmount);1524  });15251526  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1527    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1528    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15291530    const uniqueMultilocation = {1531      V2: {1532        parents: 1,1533        interior: {1534          X1: {1535            Parachain: UNIQUE_CHAIN,1536          },1537        },1538      },1539    };15401541    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1542      targetAccount.addressRaw,1543      {1544        Concrete: {1545          parents: 0,1546          interior: {1547            X1: {1548              Parachain: UNIQUE_CHAIN,1549            },1550          },1551        },1552      },1553      testAmount,1554    );15551556    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1557      targetAccount.addressRaw,1558      {1559        Concrete: {1560          parents: 0,1561          interior: 'Here',1562        },1563      },1564      testAmount,1565    );15661567    let maliciousXcmProgramFullIdSent: any;1568    let maliciousXcmProgramHereIdSent: any;1569    const maxWaitBlocks = 3;15701571    // Try to trick Unique using full UNQ identification1572    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1573      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15741575      // Needed to bypass the call filter.1576      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1577      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15781579      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1580    });15811582    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1583        && event.outcome.isUntrustedReserveLocation);15841585    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1586    expect(accountBalance).to.be.equal(0n);15871588    // Try to trick Unique using shortened UNQ identification1589    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1590      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15911592      // Needed to bypass the call filter.1593      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1594      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15951596      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1597    });15981599    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1600        && event.outcome.isUntrustedReserveLocation);16011602    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1603    expect(accountBalance).to.be.equal(0n);1604  });1605});16061607describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1608  let alice: IKeyringPair;1609  let randomAccount: IKeyringPair;16101611  const UNQ_ASSET_ID_ON_ASTAR = 1;1612  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;16131614  // Unique -> Astar1615  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1616  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1617  const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1618  const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens16191620  // Astar -> Unique1621  const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1622  const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ16231624  let balanceAfterUniqueToAstarXCM: bigint;16251626  before(async () => {1627    await usingPlaygrounds(async (helper, privateKey) => {1628      alice = await privateKey('//Alice');1629      [randomAccount] = await helper.arrange.createAccounts([100n], alice);1630      console.log('randomAccount', randomAccount.address);16311632      // Set the default version to wrap the first message to other chains.1633      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1634    });16351636    await usingAstarPlaygrounds(astarUrl, async (helper) => {1637      console.log('1. Create foreign asset and metadata');1638      // TODO update metadata with values from production1639      await helper.assets.create(1640        alice,1641        UNQ_ASSET_ID_ON_ASTAR,1642        alice.address,1643        UNQ_MINIMAL_BALANCE_ON_ASTAR,1644      );16451646      await helper.assets.setMetadata(1647        alice,1648        UNQ_ASSET_ID_ON_ASTAR,1649        'Cross chain UNQ',1650        'xcUNQ',1651        Number(UNQ_DECIMALS),1652      );16531654      console.log('2. Register asset location on Astar');1655      const assetLocation = {1656        V2: {1657          parents: 1,1658          interior: {1659            X1: {1660              Parachain: UNIQUE_CHAIN,1661            },1662          },1663        },1664      };16651666      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16671668      console.log('3. Set UNQ payment for XCM execution on Astar');1669      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);16701671      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1672      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1673    });1674  });16751676  itSub('Should connect and send UNQ to Astar', async ({helper}) => {1677    const destination = {1678      V2: {1679        parents: 1,1680        interior: {1681          X1: {1682            Parachain: ASTAR_CHAIN,1683          },1684        },1685      },1686    };16871688    const beneficiary = {1689      V2: {1690        parents: 0,1691        interior: {1692          X1: {1693            AccountId32: {1694              network: 'Any',1695              id: randomAccount.addressRaw,1696            },1697          },1698        },1699      },1700    };17011702    const assets = {1703      V2: [1704        {1705          id: {1706            Concrete: {1707              parents: 0,1708              interior: 'Here',1709            },1710          },1711          fun: {1712            Fungible: unqToAstarTransferred,1713          },1714        },1715      ],1716    };17171718    // Initial balance is 100 UNQ1719    const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1720    console.log(`Initial balance is: ${balanceBefore}`);17211722    const feeAssetItem = 0;1723    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');17241725    // Balance after reserve transfer is less than 901726    balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1727    console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1728    console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1729    expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17301731    await usingAstarPlaygrounds(astarUrl, async (helper) => {1732      await helper.wait.newBlocks(3);1733      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1734      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17351736      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1737      console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);17381739      expect(xcUNQbalance).to.eq(unqToAstarArrived);1740      // Astar balance does not changed1741      expect(astarBalance).to.eq(astarInitialBalance);1742    });1743  });17441745  itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1746    await usingAstarPlaygrounds(astarUrl, async (helper) => {1747      const destination = {1748        V2: {1749          parents: 1,1750          interior: {1751            X1: {1752              Parachain: UNIQUE_CHAIN,1753            },1754          },1755        },1756      };17571758      const beneficiary = {1759        V2: {1760          parents: 0,1761          interior: {1762            X1: {1763              AccountId32: {1764                network: 'Any',1765                id: randomAccount.addressRaw,1766              },1767            },1768          },1769        },1770      };17711772      const assets = {1773        V2: [1774          {1775            id: {1776              Concrete: {1777                parents: 1,1778                interior: {1779                  X1: {1780                    Parachain: UNIQUE_CHAIN,1781                  },1782                },1783              },1784            },1785            fun: {1786              Fungible: unqFromAstarTransfered,1787            },1788          },1789        ],1790      };17911792      // Initial balance is 1 ASTR1793      const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1794      console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1795      expect(balanceASTRbefore).to.eq(astarInitialBalance);17961797      const feeAssetItem = 0;1798      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1799      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);18001801      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1802      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1803      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);18041805      // Assert: xcUNQ balance correctly decreased1806      expect(xcUNQbalance).to.eq(unqOnAstarLeft);1807      // Assert: ASTR balance is 0.996...1808      expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1809    });18101811    await helper.wait.newBlocks(3);1812    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1813    console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1814    expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1815  });18161817  itSub('Astar can send only up to its balance', async ({helper}) => {1818    // set Astar's sovereign account's balance1819    const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1820    const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1821    await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);18221823    const moreThanAstarHas = astarBalance * 2n;18241825    let targetAccountBalance = 0n;1826    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18271828    const uniqueMultilocation = {1829      V2: {1830        parents: 1,1831        interior: {1832          X1: {Parachain: UNIQUE_CHAIN},1833        },1834      },1835    };18361837    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1838      targetAccount.addressRaw,1839      {1840        Concrete: {1841          parents: 0,1842          interior: 'Here',1843        },1844      },1845      moreThanAstarHas,1846    );18471848    let maliciousXcmProgramSent: any;1849    const maxWaitBlocks = 3;18501851    // Try to trick Unique1852    await usingAstarPlaygrounds(astarUrl, async (helper) => {1853      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18541855      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1856    });18571858    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1859        && event.outcome.isFailedToTransactAsset);18601861    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1862    expect(targetAccountBalance).to.be.equal(0n);18631864    // But Astar still can send the correct amount1865    const validTransferAmount = astarBalance / 2n;1866    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1867      targetAccount.addressRaw,1868      {1869        Concrete: {1870          parents: 0,1871          interior: 'Here',1872        },1873      },1874      validTransferAmount,1875    );18761877    await usingAstarPlaygrounds(astarUrl, async (helper) => {1878      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1879    });18801881    await helper.wait.newBlocks(maxWaitBlocks);18821883    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1884    expect(targetAccountBalance).to.be.equal(validTransferAmount);1885  });18861887  itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1888    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1889    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18901891    const uniqueMultilocation = {1892      V2: {1893        parents: 1,1894        interior: {1895          X1: {1896            Parachain: UNIQUE_CHAIN,1897          },1898        },1899      },1900    };19011902    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1903      targetAccount.addressRaw,1904      {1905        Concrete: {1906          parents: 1,1907          interior: {1908            X1: {1909              Parachain: UNIQUE_CHAIN,1910            },1911          },1912        },1913      },1914      testAmount,1915    );19161917    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1918      targetAccount.addressRaw,1919      {1920        Concrete: {1921          parents: 0,1922          interior: 'Here',1923        },1924      },1925      testAmount,1926    );19271928    let maliciousXcmProgramFullIdSent: any;1929    let maliciousXcmProgramHereIdSent: any;1930    const maxWaitBlocks = 3;19311932    // Try to trick Unique using full UNQ identification1933    await usingAstarPlaygrounds(astarUrl, async (helper) => {1934      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);19351936      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1937    });19381939    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1940        && event.outcome.isUntrustedReserveLocation);19411942    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1943    expect(accountBalance).to.be.equal(0n);19441945    // Try to trick Unique using shortened UNQ identification1946    await usingAstarPlaygrounds(astarUrl, async (helper) => {1947      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19481949      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1950    });19511952    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1953        && event.outcome.isUntrustedReserveLocation);19541955    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1956    expect(accountBalance).to.be.equal(0n);1957  });1958});
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 {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    });534535    await usingPlaygrounds(async (helper) => {536      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);537      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);538    });539  });540541  itSub('Should connect and send UNQ to Acala', async ({helper}) => {542543    const destination = {544      V2: {545        parents: 1,546        interior: {547          X1: {548            Parachain: ACALA_CHAIN,549          },550        },551      },552    };553554    const beneficiary = {555      V2: {556        parents: 0,557        interior: {558          X1: {559            AccountId32: {560              network: 'Any',561              id: randomAccount.addressRaw,562            },563          },564        },565      },566    };567568    const assets = {569      V2: [570        {571          id: {572            Concrete: {573              parents: 0,574              interior: 'Here',575            },576          },577          fun: {578            Fungible: TRANSFER_AMOUNT,579          },580        },581      ],582    };583584    const feeAssetItem = 0;585586    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');587    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);588589    const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;590    console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));591    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;592593    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {594      await helper.wait.newBlocks(3);595596      balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});597      balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);598599      const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;600      const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;601      acalaBackwardTransferAmount = unqIncomeTransfer;602603      const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;604605      console.log(606        '[Unique -> Acala] transaction fees on Acala: %s ACA',607        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),608      );609      console.log(610        '[Unique -> Acala] transaction fees on Acala: %s UNQ',611        helper.util.bigIntToDecimals(acaUnqFees),612      );613      console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));614      expect(acaFees == 0n).to.be.true;615616      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;617618      expect(619        bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,620        'Acala took different income fee, check the Acala foreign asset config',621      ).to.be.true;622    });623  });624625  itSub('Should connect to Acala and send UNQ back', async ({helper}) => {626    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {627      const destination = {628        V2: {629          parents: 1,630          interior: {631            X2: [632              {Parachain: UNIQUE_CHAIN},633              {634                AccountId32: {635                  network: 'Any',636                  id: randomAccount.addressRaw,637                },638              },639            ],640          },641        },642      };643644      const id = {645        ForeignAsset: 0,646      };647648      await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');649      balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);650      balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);651652      const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;653      const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;654655      console.log(656        '[Acala -> Unique] transaction fees on Acala: %s ACA',657        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),658      );659      console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));660661      expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;662      expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;663    });664665    await helper.wait.newBlocks(3);666667    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);668    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;669    expect(actuallyDelivered > 0).to.be.true;670671    console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));672673    const unqFees = acalaBackwardTransferAmount - actuallyDelivered;674    console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));675    expect(unqFees == 0n).to.be.true;676  });677678  itSub('Acala can send only up to its balance', async ({helper}) => {679    // set Acala's sovereign account's balance680    const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);681    const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);682    await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);683684    const moreThanAcalaHas = acalaBalance * 2n;685686    let targetAccountBalance = 0n;687    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);688689    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(690      targetAccount.addressRaw,691      {692        Concrete: {693          parents: 0,694          interior: 'Here',695        },696      },697      moreThanAcalaHas,698    );699700    let maliciousXcmProgramSent: any;701    const maxWaitBlocks = 3;702703    // Try to trick Unique704    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {705      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);706707      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);708    });709710    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash711        && event.outcome.isFailedToTransactAsset);712713    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);714    expect(targetAccountBalance).to.be.equal(0n);715716    // But Acala still can send the correct amount717    const validTransferAmount = acalaBalance / 2n;718    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(719      targetAccount.addressRaw,720      {721        Concrete: {722          parents: 0,723          interior: 'Here',724        },725      },726      validTransferAmount,727    );728729    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {730      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);731    });732733    await helper.wait.newBlocks(maxWaitBlocks);734735    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);736    expect(targetAccountBalance).to.be.equal(validTransferAmount);737  });738739  itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {740    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);741    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);742743    const uniqueMultilocation = {744      V2: {745        parents: 1,746        interior: {747          X1: {748            Parachain: UNIQUE_CHAIN,749          },750        },751      },752    };753754    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(755      targetAccount.addressRaw,756      {757        Concrete: {758          parents: 1,759          interior: {760            X1: {761              Parachain: UNIQUE_CHAIN,762            },763          },764        },765      },766      testAmount,767    );768769    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(770      targetAccount.addressRaw,771      {772        Concrete: {773          parents: 0,774          interior: 'Here',775        },776      },777      testAmount,778    );779780    let maliciousXcmProgramFullIdSent: any;781    let maliciousXcmProgramHereIdSent: any;782    const maxWaitBlocks = 3;783784    // Try to trick Unique using full UNQ identification785    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {786      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);787788      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);789    });790791    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash792        && event.outcome.isUntrustedReserveLocation);793794    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);795    expect(accountBalance).to.be.equal(0n);796797    // Try to trick Unique using shortened UNQ identification798    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {799      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);800801      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);802    });803804    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash805        && event.outcome.isUntrustedReserveLocation);806807    accountBalance = await helper.balance.getSubstrate(targetAccount.address);808    expect(accountBalance).to.be.equal(0n);809  });810});811812describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {813  let alice: IKeyringPair;814  let randomAccount: IKeyringPair;815  let unqFees: bigint;816  let balanceUniqueTokenInit: bigint;817  let balanceUniqueTokenMiddle: bigint;818  let balanceUniqueTokenFinal: bigint;819  const maxWaitBlocks = 6;820821  const uniqueAssetId = {822    Concrete: {823      parents: 1,824      interior: {825        X1: {826          Parachain: UNIQUE_CHAIN,827        },828      },829    },830  };831832  before(async () => {833    await usingPlaygrounds(async (helper, privateKey) => {834      alice = await privateKey('//Alice');835      [randomAccount] = await helper.arrange.createAccounts([0n], alice);836837      // Set the default version to wrap the first message to other chains.838      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);839    });840841    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {842      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))843        .toJSON() as [])844        .map(nToBigInt).length != 0;845      /*846      Check whether the Unique token has been added847      to the whitelist, since an error will occur848      if it is added again. Needed for debugging849      when this test is run multiple times.850      */851      if(!isWhitelisted) {852        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);853      }854855      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);856    });857858    await usingPlaygrounds(async (helper) => {859      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);860      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);861    });862  });863864  itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {865866    const destination = {867      V2: {868        parents: 1,869        interior: {870          X1: {871            Parachain: POLKADEX_CHAIN,872          },873        },874      },875    };876877    const beneficiary = {878      V2: {879        parents: 0,880        interior: {881          X1: {882            AccountId32: {883              network: 'Any',884              id: randomAccount.addressRaw,885            },886          },887        },888      },889    };890891    const assets = {892      V2: [893        {894          id: {895            Concrete: {896              parents: 0,897              interior: 'Here',898            },899          },900          fun: {901            Fungible: TRANSFER_AMOUNT,902          },903        },904      ],905    };906907    const feeAssetItem = 0;908909    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');910    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);911    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);912913    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;914    console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));915    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;916917    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {918      /*919        Since only the parachain part of the Polkadex920        infrastructure is launched (without their921        solochain validators), processing incoming922        assets will lead to an error.923        This error indicates that the Polkadex chain924        received a message from the Unique network,925        since the hash is being checked to ensure926        it matches what was sent.927      */928      await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);929    });930  });931932933  itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {934935    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(936      randomAccount.addressRaw,937      {938        Concrete: {939          parents: 1,940          interior: {941            X1: {Parachain: UNIQUE_CHAIN},942          },943        },944      },945      TRANSFER_AMOUNT,946    );947948    let xcmProgramSent: any;949950951    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {952      await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);953954      xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);955    });956957    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);958959    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);960961    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);962  });963964  itSub('Polkadex can send only up to its balance', async ({helper}) => {965    const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);966    const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);967    await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);968    const moreThanPolkadexHas = 2n * polkadexBalance;969970    const targetAccount = helper.arrange.createEmptyAccount();971972    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(973      targetAccount.addressRaw,974      {975        Concrete: {976          parents: 0,977          interior: 'Here',978        },979      },980      moreThanPolkadexHas,981    );982983    let maliciousXcmProgramSent: any;984985986    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {987      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);988989      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);990    });991992    await expectFailedToTransact(helper, maliciousXcmProgramSent);993994    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);995    expect(targetAccountBalance).to.be.equal(0n);996  });997998  itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {999    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1000    const targetAccount = helper.arrange.createEmptyAccount();10011002    const uniqueMultilocation = {1003      V2: {1004        parents: 1,1005        interior: {1006          X1: {1007            Parachain: UNIQUE_CHAIN,1008          },1009        },1010      },1011    };10121013    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1014      targetAccount.addressRaw,1015      {1016        Concrete: {1017          parents: 1,1018          interior: {1019            X1: {1020              Parachain: UNIQUE_CHAIN,1021            },1022          },1023        },1024      },1025      testAmount,1026    );10271028    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1029      targetAccount.addressRaw,1030      {1031        Concrete: {1032          parents: 0,1033          interior: 'Here',1034        },1035      },1036      testAmount,1037    );10381039    let maliciousXcmProgramFullIdSent: any;1040    let maliciousXcmProgramHereIdSent: any;1041    const maxWaitBlocks = 3;10421043    // Try to trick Unique using full UNQ identification1044    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1045      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10461047      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1048    });10491050    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10511052    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1053    expect(accountBalance).to.be.equal(0n);10541055    // Try to trick Unique using shortened UNQ identification1056    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1057      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10581059      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1060    });10611062    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10631064    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1065    expect(accountBalance).to.be.equal(0n);1066  });1067});10681069// These tests are relevant only when1070// the the corresponding foreign assets are not registered1071describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1072  let alice: IKeyringPair;1073  let alith: IKeyringPair;10741075  const testAmount = 100_000_000_000n;1076  let uniqueParachainJunction;1077  let uniqueAccountJunction;10781079  let uniqueParachainMultilocation: any;1080  let uniqueAccountMultilocation: any;1081  let uniqueCombinedMultilocation: any;1082  let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210831084  let messageSent: any;10851086  const maxWaitBlocks = 3;10871088  before(async () => {1089    await usingPlaygrounds(async (helper, privateKey) => {1090      alice = await privateKey('//Alice');10911092      uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1093      uniqueAccountJunction = {1094        AccountId32: {1095          network: 'Any',1096          id: alice.addressRaw,1097        },1098      };10991100      uniqueParachainMultilocation = {1101        V2: {1102          parents: 1,1103          interior: {1104            X1: uniqueParachainJunction,1105          },1106        },1107      };11081109      uniqueAccountMultilocation = {1110        V2: {1111          parents: 0,1112          interior: {1113            X1: uniqueAccountJunction,1114          },1115        },1116      };11171118      uniqueCombinedMultilocation = {1119        V2: {1120          parents: 1,1121          interior: {1122            X2: [uniqueParachainJunction, uniqueAccountJunction],1123          },1124        },1125      };11261127      uniqueCombinedMultilocationAcala = {1128        V2: {1129          parents: 1,1130          interior: {1131            X2: [uniqueParachainJunction, uniqueAccountJunction],1132          },1133        },1134      };11351136      // Set the default version to wrap the first message to other chains.1137      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1138    });11391140    // eslint-disable-next-line require-await1141    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1142      alith = helper.account.alithAccount();1143    });1144  });1145114611471148  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1149    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1150      const id = {1151        Token: 'ACA',1152      };1153      const destination = uniqueCombinedMultilocationAcala;1154      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11551156      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1157    });11581159    await expectFailedToTransact(helper, messageSent);1160  });11611162  itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1163    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1164      const id = 'SelfReserve';1165      const destination = uniqueCombinedMultilocation;1166      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11671168      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1169    });11701171    await expectFailedToTransact(helper, messageSent);1172  });11731174  itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1175    await usingAstarPlaygrounds(astarUrl, async (helper) => {1176      const destinationParachain = uniqueParachainMultilocation;1177      const beneficiary = uniqueAccountMultilocation;1178      const assets = {1179        V2: [{1180          id: {1181            Concrete: {1182              parents: 0,1183              interior: 'Here',1184            },1185          },1186          fun: {1187            Fungible: testAmount,1188          },1189        }],1190      };1191      const feeAssetItem = 0;11921193      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1194        destinationParachain,1195        beneficiary,1196        assets,1197        feeAssetItem,1198      ]);11991200      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1201    });12021203    await expectFailedToTransact(helper, messageSent);1204  });12051206  itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12071208    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1209      helper.arrange.createEmptyAccount().addressRaw,1210      {1211        Concrete: {1212          parents: 1,1213          interior: {1214            X1: {1215              Parachain: POLKADEX_CHAIN,1216            },1217          },1218        },1219      },1220      testAmount,1221    );12221223    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1224      await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1225      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1226    });12271228    await expectFailedToTransact(helper, messageSent);1229  });1230});12311232describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1233  // Unique constants1234  let alice: IKeyringPair;1235  let uniqueAssetLocation;12361237  let randomAccountUnique: IKeyringPair;1238  let randomAccountMoonbeam: IKeyringPair;12391240  // Moonbeam constants1241  let assetId: string;12421243  const uniqueAssetMetadata = {1244    name: 'xcUnique',1245    symbol: 'xcUNQ',1246    decimals: 18,1247    isFrozen: false,1248    minimalBalance: 1n,1249  };12501251  let balanceUniqueTokenInit: bigint;1252  let balanceUniqueTokenMiddle: bigint;1253  let balanceUniqueTokenFinal: bigint;1254  let balanceForeignUnqTokenInit: bigint;1255  let balanceForeignUnqTokenMiddle: bigint;1256  let balanceForeignUnqTokenFinal: bigint;1257  let balanceGlmrTokenInit: bigint;1258  let balanceGlmrTokenMiddle: bigint;1259  let balanceGlmrTokenFinal: bigint;12601261  before(async () => {1262    await usingPlaygrounds(async (helper, privateKey) => {1263      alice = await privateKey('//Alice');1264      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12651266      balanceForeignUnqTokenInit = 0n;12671268      // Set the default version to wrap the first message to other chains.1269      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1270    });12711272    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1273      const alithAccount = helper.account.alithAccount();1274      const baltatharAccount = helper.account.baltatharAccount();1275      const dorothyAccount = helper.account.dorothyAccount();12761277      randomAccountMoonbeam = helper.account.create();12781279      // >>> Sponsoring Dorothy >>>1280      console.log('Sponsoring Dorothy.......');1281      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1282      console.log('Sponsoring Dorothy.......DONE');1283      // <<< Sponsoring Dorothy <<<12841285      uniqueAssetLocation = {1286        XCM: {1287          parents: 1,1288          interior: {X1: {Parachain: UNIQUE_CHAIN}},1289        },1290      };1291      const existentialDeposit = 1n;1292      const isSufficient = true;1293      const unitsPerSecond = 1n;1294      const numAssetsWeightHint = 0;12951296      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON) {1297        console.log('Unique asset is already registered on MoonBeam');1298      } else {1299        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1300          location: uniqueAssetLocation,1301          metadata: uniqueAssetMetadata,1302          existentialDeposit,1303          isSufficient,1304          unitsPerSecond,1305          numAssetsWeightHint,1306        });13071308        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);13091310        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);13111312        // >>> Acquire Unique AssetId Info on Moonbeam >>>1313        console.log('Acquire Unique AssetId Info on Moonbeam.......');13141315        assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();13161317        console.log('UNQ asset ID is %s', assetId);1318        console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1319      }1320      // >>> Acquire Unique AssetId Info on Moonbeam >>>13211322      // >>> Sponsoring random Account >>>1323      console.log('Sponsoring random Account.......');1324      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1325      console.log('Sponsoring random Account.......DONE');1326      // <<< Sponsoring random Account <<<13271328      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1329    });13301331    await usingPlaygrounds(async (helper) => {1332      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1333      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1334    });1335  });13361337  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1338    const currencyId = {1339      NativeAssetId: 'Here',1340    };1341    const dest = {1342      V2: {1343        parents: 1,1344        interior: {1345          X2: [1346            {Parachain: MOONBEAM_CHAIN},1347            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1348          ],1349        },1350      },1351    };1352    const amount = TRANSFER_AMOUNT;13531354    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13551356    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1357    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13581359    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1360    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1361    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13621363    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1364      await helper.wait.newBlocks(3);13651366      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});