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
before · tests/package.json
1{2  "name": "unique-tests",3  "version": "1.0.0",4  "description": "Unique Chain Tests",5  "main": "",6  "devDependencies": {7    "@polkadot/typegen": "10.9.1",8    "@types/chai": "^4.3.3",9    "@types/chai-as-promised": "^7.1.5",10    "@types/chai-like": "^1.1.1",11    "@types/chai-subset": "^1.3.3",12    "@types/mocha": "^10.0.0",13    "@types/node": "^20.4.2",14    "@typescript-eslint/eslint-plugin": "^6.0.0",15    "@typescript-eslint/parser": "^6.0.0",16    "chai": "^4.3.6",17    "chai-subset": "^1.6.0",18    "eslint": "^8.45.0",19    "eslint-plugin-mocha": "^10.1.0",20    "mocha": "^10.1.0",21    "mochawesome": "^7.1.3",22    "ts-node": "^10.9.1",23    "typescript": "^5.1.6"24  },25  "mocha": {26    "timeout": 9999999,27    "require": [28      "ts-node/register"29    ]30  },31  "scripts": {32    "lint": "eslint --ext .ts,.js src/",33    "fix": "yarn lint --fix",34    "setup": "ts-node --esm ./src/util/globalSetup.ts",35    "setIdentities": "ts-node --esm ./src/util/identitySetter.ts",36    "checkRelayIdentities": "ts-node --esm ./src/util/relayIdentitiesChecker.ts",37    "frankenstein": "ts-node --esm ./src/util/frankenstein.ts",38    "_test": "yarn setup && mocha --timeout 9999999 --loader=ts-node/esm.mjs",39    "_testParallel": "yarn setup && mocha --timeout 9999999 --parallel --loader=ts-node/esm.mjs",40    "test": "yarn _test './src/**/*.*test.ts'",41    "testParallelFull": "yarn testParallel && yarn testSequential",42    "testParallel": "yarn _testParallel './src/**/*.test.ts'",43    "testSequential": "yarn _test './src/**/*.seqtest.ts'",44    "testStructure": "yarn _test ./**/nesting/*.*test.ts",45    "testGovernance": "RUN_GOV_TESTS=1 yarn _test ./**/governance/*.*test.ts",46    "testEth": "yarn _test './**/eth/**/*.*test.ts'",47    "testEthNesting": "yarn _test './**/eth/nesting/**/*.*test.ts'",48    "testEthFractionalizer": "yarn _test './**/eth/fractionalizer/**/*.*test.ts'",49    "testEthMarketplace": "yarn _test './**/eth/marketplace/**/*.*test.ts'",50    "testEthMarket": "yarn _test './**/eth/marketplace-v2/**/*.*test.ts'",51    "testSub": "yarn _test './**/sub/**/*.*test.ts'",52    "testSubNesting": "yarn _test './**/sub/nesting/**/*.*test.ts'",53    "testEvent": "yarn _test ./src/check-event/*.*test.ts",54    "testEthPayable": "yarn _test './**/eth/payable.test.ts'",55    "testEvmCoder": "yarn _test './**/eth/evmCoder.test.ts'",56    "testNesting": "yarn _test ./**/nest.test.ts",57    "testUnnesting": "yarn _test ./**/unnest.test.ts",58    "testProperties": "yarn _test ./**/collectionProperties.*test.ts ./**/tokenProperties.*test.ts ./**/getPropertiesRpc.test.ts",59    "testCollectionProperties": "yarn _test ./**/collectionProperties.*test.ts",60    "testTokenProperties": "yarn _test ./**/tokenProperties.*test.ts",61    "testMigration": "yarn _test ./**/nesting/migration-check.test.ts",62    "testAddCollectionAdmin": "yarn _test ./**/addCollectionAdmin.test.ts",63    "testSetCollectionLimits": "yarn _test ./**/setCollectionLimits.test.ts",64    "testChangeCollectionOwner": "yarn _test ./**/change-collection-owner.test.ts",65    "testSetCollectionSponsor": "yarn _test ./**/setCollectionSponsor.test.ts",66    "testConfirmSponsorship": "yarn _test ./**/confirmSponsorship.test.ts",67    "testRemoveCollectionAdmin": "yarn _test ./**/removeCollectionAdmin.test.ts",68    "testRemoveCollectionSponsor": "yarn _test ./**/removeCollectionSponsor.test.ts",69    "testAllowLists": "yarn _test ./**/allowLists.test.ts",70    "testConnection": "yarn _test ./**/connection.test.ts",71    "testContracts": "yarn _test ./**/contracts.test.ts",72    "testCreateItem": "yarn _test ./**/createItem.test.ts",73    "testCreateMultipleItems": "yarn _test ./**/createMultipleItems.test.ts",74    "testCreateMultipleItemsEx": "yarn _test ./**/createMultipleItemsEx.test.ts",75    "testApprove": "yarn _test ./**/approve.test.ts",76    "testTransferFrom": "yarn _test ./**/transferFrom.test.ts",77    "testCreateCollection": "yarn _test ./**/createCollection.test.ts",78    "testDestroyCollection": "yarn _test ./**/destroyCollection.test.ts",79    "testToggleContractAllowList": "yarn _test ./**/toggleContractAllowList.test.ts",80    "testAddToContractAllowList": "yarn _test ./**/addToContractAllowList.test.ts",81    "testTransfer": "yarn _test ./**/transfer.test.ts",82    "testBurnItem": "yarn _test ./**/burnItem.test.ts",83    "testAdminTransferAndBurn": "yarn _test ./**/adminTransferAndBurn.test.ts",84    "testSetPermissions": "yarn _test ./**/setPermissions.test.ts",85    "testCreditFeesToTreasury": "yarn _test ./**/creditFeesToTreasury.seqtest.ts",86    "testContractSponsoring": "yarn _test ./**/eth/contractSponsoring.test.ts",87    "testEnableContractSponsoring": "yarn _test ./**/enableContractSponsoring.test.ts",88    "testRemoveFromContractAllowList": "yarn _test ./**/removeFromContractAllowList.test.ts",89    "testSetContractSponsoringRateLimit": "yarn _test ./**/setContractSponsoringRateLimit.test.ts",90    "testSetOffchainSchema": "yarn _test ./**/setOffchainSchema.test.ts",91    "testNextSponsoring": "yarn _test ./**/nextSponsoring.test.ts",92    "testOverflow": "yarn _test ./**/overflow.test.ts",93    "testMaintenance": "yarn _test ./**/maintenance.seqtest.ts",94    "testInflation": "yarn _test ./**/inflation.seqtest.ts",95    "testScheduler": "yarn _test ./**/scheduler.seqtest.ts",96    "testSchedulingEVM": "yarn _test ./**/eth/scheduling.test.ts",97    "testPalletPresence": "yarn _test ./**/pallet-presence.test.ts",98    "testEnableDisableTransfers": "yarn _test ./**/enableDisableTransfer.test.ts",99    "testLimits": "yarn _test ./**/limits.test.ts",100    "testEthCreateNFTCollection": "yarn _test ./**/eth/createNFTCollection.test.ts",101    "testEthCreateRFTCollection": "yarn _test ./**/eth/createRFTCollection.test.ts",102    "testEthNFT": "yarn _test ./**/eth/nonFungible.test.ts",103    "testRFT": "yarn _test ./**/refungible.test.ts",104    "testEthRFT": "yarn _test ./**/eth/reFungible.test.ts ./**/eth/reFungibleToken.test.ts",105    "testFT": "yarn _test ./**/fungible.test.ts",106    "testEthFT": "yarn _test ./**/eth/fungible.test.ts",107    "testRPC": "yarn _test ./**/rpc.test.ts",108    "testPromotion": "yarn _test ./**/appPromotion/*test.ts",109    "testApiConsts": "yarn _test ./**/apiConsts.test.ts",110    "testCouncil": "yarn _test ./**/council.*test.ts",111    "testDemocracy": "yarn _test ./**/democracy.*test.ts",112    "testCollators": "RUN_COLLATOR_TESTS=1 yarn _test ./**/collator-selection/**.*test.ts --timeout 49999999",113    "testCollatorSelection": "RUN_COLLATOR_TESTS=1 yarn _test ./**/collatorSelection.*test.ts --timeout 49999999",114    "testIdentity": "RUN_COLLATOR_TESTS=1 yarn _test ./**/identity.*test.ts --timeout 49999999",115    "testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmUnique.test.ts",116    "testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmQuartz.test.ts",117    "testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts",118    "testXcmTransferAcala": "yarn _test ./**/xcm/xcmTransferAcala.test.ts acalaId=2000 uniqueId=5000",119    "testXcmTransferStatemine": "yarn _test ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000",120    "testXcmTransferMoonbeam": "yarn _test ./**/xcm/xcmTransferMoonbeam.test.ts",121    "benchMintingFee": "ts-node src/benchmarks/mintFee/benchmark.ts",122    "load": "yarn _test './**/*.load.ts'",123    "loadTransfer": "ts-node src/transfer.nload.ts",124    "polkadot-types-fetch-metadata": "yarn ts-node --esm src/fetchMetadata.ts",125    "polkadot-types-from-defs": "ts-node --esm ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .",126    "polkadot-types-from-chain": "ts-node --esm ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",127    "polkadot-types": "echo \"export default {}\" > src/interfaces/lookup.ts && yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain",128    "generateEnv": "ts-node --esm ./src/generateEnv.ts"129  },130  "author": "",131  "license": "SEE LICENSE IN ../LICENSE",132  "homepage": "",133  "dependencies": {134    "@openzeppelin/contracts": "^4.9.2",135    "@polkadot/api": "10.9.1",136    "@polkadot/rpc-core": "^10.9.1",137    "@polkadot/util": "12.3.2",138    "@polkadot/util-crypto": "12.3.2",139    "@polkadot/wasm-crypto-asmjs": "^7.2.1",140    "@polkadot/wasm-crypto-wasm": "^7.2.1",141    "@rmrk-team/evm-contracts": "^1.2.1",142    "@typechain/web3-v1": "^6.0.3",143    "chai-as-promised": "^7.1.1",144    "chai-like": "^1.1.1",145    "csv-writer": "^1.6.0",146    "find-process": "^1.4.7",147    "lossless-json": "^2.0.9",148    "solc": "0.8.20",149    "typechain": "^8.2.0",150    "web3": "1.10.0"151  },152  "resolutions": {153    "decode-uri-component": "^0.2.1"154  },155  "type": "module",156  "packageManager": "yarn@3.6.1"157}
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
--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -18,7 +18,7 @@
 import config from '../config';
 import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';
 import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
-import {nToBigInt} from '@polkadot/util';
+import {hexToString, nToBigInt} from '@polkadot/util';
 
 const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
 const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
@@ -57,6 +57,18 @@
 
 const SAFE_XCM_VERSION = 2;
 const maxWaitBlocks = 6;
+
+const uniqueMultilocation = {
+  V2: {
+    parents: 1,
+    interior: {
+      X1: {
+        Parachain: UNIQUE_CHAIN,
+      },
+    },
+  },
+};
+
 const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
   await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
         && event.outcome.isFailedToTransactAsset);
@@ -509,11 +521,15 @@
         decimals: 18,
         minimalBalance: 1250000000000000000n,
       };
+      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>
+        hexToString(v.toJSON()['symbol'])) as string[];
 
-      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+      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);
-      balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);
-      balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
     });
 
     await usingPlaygrounds(async (helper) => {
@@ -669,15 +685,6 @@
 
     let targetAccountBalance = 0n;
     const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
-
-    const uniqueMultilocation = {
-      V2: {
-        parents: 1,
-        interior: {
-          X1: {Parachain: UNIQUE_CHAIN},
-        },
-      },
-    };
 
     const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
       targetAccount.addressRaw,
@@ -925,15 +932,6 @@
 
   itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {
 
-    const uniqueMultilocation = {
-      V2: {
-        parents: 1,
-        interior: {
-          X1: {Parachain: UNIQUE_CHAIN},
-        },
-      },
-    };
-
     const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
       randomAccount.addressRaw,
       {
@@ -971,15 +969,6 @@
 
     const targetAccount = helper.arrange.createEmptyAccount();
 
-    const uniqueMultilocation = {
-      V2: {
-        parents: 1,
-        interior: {
-          X1: {Parachain: UNIQUE_CHAIN},
-        },
-      },
-    };
-
     const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
       targetAccount.addressRaw,
       {
@@ -1304,26 +1293,30 @@
       const unitsPerSecond = 1n;
       const numAssetsWeightHint = 0;
 
-      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
-        location: uniqueAssetLocation,
-        metadata: uniqueAssetMetadata,
-        existentialDeposit,
-        isSufficient,
-        unitsPerSecond,
-        numAssetsWeightHint,
-      });
+      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON) {
+        console.log('Unique asset is already registered on MoonBeam');
+      } else {
+        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
+          location: uniqueAssetLocation,
+          metadata: uniqueAssetMetadata,
+          existentialDeposit,
+          isSufficient,
+          unitsPerSecond,
+          numAssetsWeightHint,
+        });
 
-      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
+        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
 
-      await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);
+        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);
 
-      // >>> Acquire Unique AssetId Info on Moonbeam >>>
-      console.log('Acquire Unique AssetId Info on Moonbeam.......');
+        // >>> Acquire Unique AssetId Info on Moonbeam >>>
+        console.log('Acquire Unique AssetId Info on Moonbeam.......');
 
-      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();
+        assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();
 
-      console.log('UNQ asset ID is %s', assetId);
-      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');
+        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 >>>
@@ -1455,15 +1448,6 @@
 
     let targetAccountBalance = 0n;
     const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
-
-    const uniqueMultilocation = {
-      V2: {
-        parents: 1,
-        interior: {
-          X1: {Parachain: UNIQUE_CHAIN},
-        },
-      },
-    };
 
     const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
       targetAccount.addressRaw,
@@ -1527,17 +1511,6 @@
     const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
     const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
 
-    const uniqueMultilocation = {
-      V2: {
-        parents: 1,
-        interior: {
-          X1: {
-            Parachain: UNIQUE_CHAIN,
-          },
-        },
-      },
-    };
-
     const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
       targetAccount.addressRaw,
       {
@@ -1634,40 +1607,41 @@
     });
 
     await usingAstarPlaygrounds(astarUrl, async (helper) => {
-      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,
-      );
+      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),
-      );
+        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,
+        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]);
+        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);
     });
@@ -1824,15 +1798,6 @@
 
     let targetAccountBalance = 0n;
     const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
-
-    const uniqueMultilocation = {
-      V2: {
-        parents: 1,
-        interior: {
-          X1: {Parachain: UNIQUE_CHAIN},
-        },
-      },
-    };
 
     const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
       targetAccount.addressRaw,
@@ -1887,17 +1852,6 @@
   itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {
     const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
     const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
-
-    const uniqueMultilocation = {
-      V2: {
-        parents: 1,
-        interior: {
-          X1: {
-            Parachain: UNIQUE_CHAIN,
-          },
-        },
-      },
-    };
 
     const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
       targetAccount.addressRaw,