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

difftreelog

refactor xcm Unq tests

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

3 files changed

modifiedtests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth
before · tests/src/xcm/lowLevelXcmUnique.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, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';21import {nToBigInt} from '@polkadot/util';22import {hexToString} from '@polkadot/util';2324const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);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);29303132const acalaUrl = config.acalaUrl;33const moonbeamUrl = config.moonbeamUrl;34const astarUrl = config.astarUrl;35const polkadexUrl = config.polkadexUrl;3637const ASTAR_DECIMALS = 18n;38const UNQ_DECIMALS = 18n;3940const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;41const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;42const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;43const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;44const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;4546const SAFE_XCM_VERSION = 2;47const maxWaitBlocks = 6;4849const uniqueMultilocation = {50  V2: {51    parents: 1,52    interior: {53      X1: {54        Parachain: UNIQUE_CHAIN,55      },56    },57  },58};5960let balanceUniqueTokenInit: bigint;61let balanceUniqueTokenMiddle: bigint;62let balanceUniqueTokenFinal: bigint;63let unqFees: bigint;6465const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {66  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash67        && event.outcome.isFailedToTransactAsset);68};69const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {70  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash71         && event.outcome.isUntrustedReserveLocation);72};7374const NETWORKS = {75  acala: usingAcalaPlaygrounds,76  astar: usingAstarPlaygrounds,77  polkadex: usingPolkadexPlaygrounds,78  moonbeam: usingMoonbeamPlaygrounds,79} as const;8081function mapToChainId(networkName: keyof typeof NETWORKS) {82  switch (networkName) {83    case 'acala':84      return ACALA_CHAIN;85    case 'astar':86      return ASTAR_CHAIN;87    case 'moonbeam':88      return MOONBEAM_CHAIN;89    case 'polkadex':90      return POLKADEX_CHAIN;91  }92}9394function mapToChainUrl(networkName: keyof typeof NETWORKS): string {95  switch (networkName) {96    case 'acala':97      return acalaUrl;98    case 'astar':99      return astarUrl;100    case 'moonbeam':101      return moonbeamUrl;102    case 'polkadex':103      return polkadexUrl;104  }105}106107function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {108  return NETWORKS[name];109}110111112async function genericSendUnqTo(113  networkName: keyof typeof NETWORKS,114  randomAccount: IKeyringPair,115  randomAccountOnTargetChain = randomAccount,116) {117  const networkUrl = mapToChainUrl(networkName);118  const targetPlayground = getDevPlayground(networkName);119  await usingPlaygrounds(async (helper) => {120    balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);121    const destination = {122      V2: {123        parents: 1,124        interior: {125          X1: {126            Parachain: mapToChainId(networkName),127          },128        },129      },130    };131132    const beneficiary = {133      V2: {134        parents: 0,135        interior: {136          X1: (137            networkName == 'moonbeam' ?138              {139                AccountKey20: {140                  network: 'Any',141                  key: randomAccountOnTargetChain.address,142                },143              }144              :145              {146                AccountId32: {147                  network: 'Any',148                  id: randomAccountOnTargetChain.addressRaw,149                },150              }151          ),152        },153      },154    };155156    const assets = {157      V2: [158        {159          id: {160            Concrete: {161              parents: 0,162              interior: 'Here',163            },164          },165          fun: {166            Fungible: TRANSFER_AMOUNT,167          },168        },169      ],170    };171    const feeAssetItem = 0;172173    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');174    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);175    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);176177    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;178    console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));179    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;180181    await targetPlayground(networkUrl, async (helper) => {182      /*183        Since only the parachain part of the Polkadex184        infrastructure is launched (without their185        solochain validators), processing incoming186        assets will lead to an error.187        This error indicates that the Polkadex chain188        received a message from the Unique network,189        since the hash is being checked to ensure190        it matches what was sent.191      */192      if(networkName == 'polkadex') {193        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);194      } else {195        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);196      }197    });198199  });200}201202async function genericSendUnqBack(203  networkName: keyof typeof NETWORKS,204  sudoer: IKeyringPair,205  randomAccountOnUnq: IKeyringPair,206) {207  const networkUrl = mapToChainUrl(networkName);208209  const targetPlayground = getDevPlayground(networkName);210  await usingPlaygrounds(async (helper) => {211212    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(213      randomAccountOnUnq.addressRaw,214      {215        Concrete: {216          parents: 1,217          interior: {218            X1: {Parachain: UNIQUE_CHAIN},219          },220        },221      },222      SENDBACK_AMOUNT,223    );224225    let xcmProgramSent: any;226227228    await targetPlayground(networkUrl, async (helper) => {229      if('getSudo' in helper) {230        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, xcmProgram);231        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);232      } else if('fastDemocracy' in helper) {233        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, xcmProgram]);234        // Needed to bypass the call filter.235        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);236        await helper.fastDemocracy.executeProposal('sending MoonBeam -> Unique via XCM program', batchCall);237        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);238      }239    });240241    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);242243    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);244245    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);246247  });248}249250async function genericSendOnlyOwnedBalance(251  networkName: keyof typeof NETWORKS,252  sudoer: IKeyringPair,253) {254  const networkUrl = mapToChainUrl(networkName);255  const targetPlayground = getDevPlayground(networkName);256257  const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);258259  await usingPlaygrounds(async (helper) => {260    const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));261    await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);262    const moreThanTargetChainHas = 2n * targetChainBalance;263264    const targetAccount = helper.arrange.createEmptyAccount();265266    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(267      targetAccount.addressRaw,268      {269        Concrete: {270          parents: 0,271          interior: 'Here',272        },273      },274      moreThanTargetChainHas,275    );276277    let maliciousXcmProgramSent: any;278279280    await targetPlayground(networkUrl, async (helper) => {281      if('getSudo' in helper) {282        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgram);283        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);284      } else if('fastDemocracy' in helper) {285        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);286        // Needed to bypass the call filter.287        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);288        await helper.fastDemocracy.executeProposal('sending MoonBeam -> Unique via XCM program', batchCall);289        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);290      }291    });292293    await expectFailedToTransact(helper, maliciousXcmProgramSent);294295    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);296    expect(targetAccountBalance).to.be.equal(0n);297  });298}299300async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {301  const networkUrl = mapToChainUrl(netwokrName);302  const targetPlayground = getDevPlayground(netwokrName);303304  await usingPlaygrounds(async (helper) => {305    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);306    const targetAccount = helper.arrange.createEmptyAccount();307308    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(309      targetAccount.addressRaw,310      {311        Concrete: {312          parents: 1,313          interior: {314            X1: {315              Parachain: UNIQUE_CHAIN,316            },317          },318        },319      },320      testAmount,321    );322323    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(324      targetAccount.addressRaw,325      {326        Concrete: {327          parents: 0,328          interior: 'Here',329        },330      },331      testAmount,332    );333334    let maliciousXcmProgramFullIdSent: any;335    let maliciousXcmProgramHereIdSent: any;336    const maxWaitBlocks = 3;337338    // Try to trick Unique using full UNQ identification339    await targetPlayground(networkUrl, async (helper) => {340      if('getSudo' in helper) {341        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgramFullId);342        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);343      }344      // Moonbeam case345      else if('fastDemocracy' in helper) {346        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);347348        // Needed to bypass the call filter.349        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);350        await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);351352        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);353      }354    });355356357    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);358359    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);360    expect(accountBalance).to.be.equal(0n);361362    // Try to trick Unique using shortened UNQ identification363    await targetPlayground(networkUrl, async (helper) => {364      if('getSudo' in helper) {365        await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgramHereId);366        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);367      }368      else if('fastDemocracy' in helper) {369        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);370371        // Needed to bypass the call filter.372        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);373        await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);374375        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);376      }377    });378379    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);380381    accountBalance = await helper.balance.getSubstrate(targetAccount.address);382    expect(accountBalance).to.be.equal(0n);383  });384}385386async function genericRejectNativeToknsFrom(netwokrName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {387  const networkUrl = mapToChainUrl(netwokrName);388  const targetPlayground = getDevPlayground(netwokrName);389  let messageSent: any;390391  await usingPlaygrounds(async (helper) => {392    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(393      helper.arrange.createEmptyAccount().addressRaw,394      {395        Concrete: {396          parents: 1,397          interior: {398            X1: {399              Parachain: mapToChainId(netwokrName),400            },401          },402        },403      },404      TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,405    );406    await targetPlayground(networkUrl, async (helper) => {407      if('getSudo' in helper) {408        await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueMultilocation, maliciousXcmProgramFullId);409        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);410      } else if('fastDemocracy' in helper) {411        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);412413        // Needed to bypass the call filter.414        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);415        await helper.fastDemocracy.executeProposal('sending native tokens to the Unique via fast democracy', batchCall);416417        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);418      }419    });420421    await expectFailedToTransact(helper, messageSent);422  });423}424425426describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {427  let alice: IKeyringPair;428  let randomAccount: IKeyringPair;429430  before(async () => {431    await usingPlaygrounds(async (helper, privateKey) => {432      alice = await privateKey('//Alice');433      console.log(config.acalaUrl);434      randomAccount = helper.arrange.createEmptyAccount();435436      // Set the default version to wrap the first message to other chains.437      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);438    });439440    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {441      const destination = {442        V2: {443          parents: 1,444          interior: {445            X1: {446              Parachain: UNIQUE_CHAIN,447            },448          },449        },450      };451452      const metadata = {453        name: 'Unique Network',454        symbol: 'UNQ',455        decimals: 18,456        minimalBalance: 1250_000_000_000_000_000n,457      };458      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>459        hexToString(v.toJSON()['symbol'])) as string[];460461      if(!assets.includes('UNQ')) {462        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);463      } else {464        console.log('UNQ token already registered on Acala assetRegistry pallet');465      }466      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);467    });468469    await usingPlaygrounds(async (helper) => {470      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);471      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);472    });473  });474475  itSub('Should connect and send UNQ to Acala', async () => {476    await genericSendUnqTo('acala', randomAccount);477  });478479  itSub('Should connect to Acala and send UNQ back', async () => {480    await genericSendUnqBack('acala', alice, randomAccount);481  });482483  itSub('Acala can send only up to its balance', async () => {484    await genericSendOnlyOwnedBalance('acala', alice);485  });486487  itSub('Should not accept reserve transfer of UNQ from Acala', async () => {488    await genericReserveTransferUNQfrom('acala', alice);489  });490});491492describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {493  let alice: IKeyringPair;494  let randomAccount: IKeyringPair;495496  const uniqueAssetId = {497    Concrete: {498      parents: 1,499      interior: {500        X1: {501          Parachain: UNIQUE_CHAIN,502        },503      },504    },505  };506507  before(async () => {508    await usingPlaygrounds(async (helper, privateKey) => {509      alice = await privateKey('//Alice');510      randomAccount = helper.arrange.createEmptyAccount();511512      // Set the default version to wrap the first message to other chains.513      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);514    });515516    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {517      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))518        .toJSON() as [])519        .map(nToBigInt).length != 0;520      /*521      Check whether the Unique token has been added522      to the whitelist, since an error will occur523      if it is added again. Needed for debugging524      when this test is run multiple times.525      */526      if(!isWhitelisted) {527        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);528      }529530      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);531    });532533    await usingPlaygrounds(async (helper) => {534      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);535      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);536    });537  });538539  itSub('Should connect and send UNQ to Polkadex', async () => {540    await genericSendUnqTo('polkadex', randomAccount);541  });542543544  itSub('Should connect to Polkadex and send UNQ back', async () => {545    await genericSendUnqBack('polkadex', alice, randomAccount);546  });547548  itSub('Polkadex can send only up to its balance', async () => {549    await genericSendOnlyOwnedBalance('polkadex', alice);550  });551552  itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {553    await genericReserveTransferUNQfrom('polkadex', alice);554  });555});556557// These tests are relevant only when558// the the corresponding foreign assets are not registered559describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {560  let alice: IKeyringPair;561562  before(async () => {563    await usingPlaygrounds(async (helper, privateKey) => {564      alice = await privateKey('//Alice');565566      // Set the default version to wrap the first message to other chains.567      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);568    });569  });570571  itSub('Unique rejects ACA tokens from Acala', async () => {572    await genericRejectNativeToknsFrom('acala', alice);573  });574575  itSub('Unique rejects GLMR tokens from Moonbeam', async () => {576    await genericRejectNativeToknsFrom('moonbeam', alice);577  });578579  itSub('Unique rejects ASTR tokens from Astar', async () => {580    await genericRejectNativeToknsFrom('astar', alice);581  });582583  itSub('Unique rejects PDX tokens from Polkadex', async () => {584    await genericRejectNativeToknsFrom('polkadex', alice);585  });586});587588describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {589  // Unique constants590  let alice: IKeyringPair;591  let uniqueAssetLocation;592593  let randomAccountUnique: IKeyringPair;594  let randomAccountMoonbeam: IKeyringPair;595596  // Moonbeam constants597  let assetId: string;598599  const uniqueAssetMetadata = {600    name: 'xcUnique',601    symbol: 'xcUNQ',602    decimals: 18,603    isFrozen: false,604    minimalBalance: 1n,605  };606607608  before(async () => {609    await usingPlaygrounds(async (helper, privateKey) => {610      alice = await privateKey('//Alice');611      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);612613614      // Set the default version to wrap the first message to other chains.615      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);616    });617618    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {619      const alithAccount = helper.account.alithAccount();620      const baltatharAccount = helper.account.baltatharAccount();621      const dorothyAccount = helper.account.dorothyAccount();622623      randomAccountMoonbeam = helper.account.create();624625      // >>> Sponsoring Dorothy >>>626      console.log('Sponsoring Dorothy.......');627      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);628      console.log('Sponsoring Dorothy.......DONE');629      // <<< Sponsoring Dorothy <<<630      uniqueAssetLocation = {631        XCM: {632          parents: 1,633          interior: {X1: {Parachain: UNIQUE_CHAIN}},634        },635      };636      const existentialDeposit = 1n;637      const isSufficient = true;638      const unitsPerSecond = 1n;639      const numAssetsWeightHint = 0;640641      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {642        console.log('Unique asset already registered');643      } else {644        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({645          location: uniqueAssetLocation,646          metadata: uniqueAssetMetadata,647          existentialDeposit,648          isSufficient,649          unitsPerSecond,650          numAssetsWeightHint,651        });652653        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);654655        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);656      }657658      // >>> Acquire Unique AssetId Info on Moonbeam >>>659      console.log('Acquire Unique AssetId Info on Moonbeam.......');660661      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();662663      console.log('UNQ asset ID is %s', assetId);664      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');665666      // >>> Sponsoring random Account >>>667      console.log('Sponsoring random Account.......');668      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);669      console.log('Sponsoring random Account.......DONE');670      // <<< Sponsoring random Account <<<671    });672673    await usingPlaygrounds(async (helper) => {674      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);675      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);676    });677  });678679  itSub('Should connect and send UNQ to Moonbeam', async () => {680    await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);681  });682683  itSub('Should connect to Moonbeam and send UNQ back', async () => {684    await genericSendUnqBack('moonbeam', alice, randomAccountUnique);685  });686687  itSub('Moonbeam can send only up to its balance', async () => {688    await genericSendOnlyOwnedBalance('moonbeam', alice);689  });690691  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {692    await genericReserveTransferUNQfrom('moonbeam', alice);693  });694});695696describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {697  let alice: IKeyringPair;698  let randomAccount: IKeyringPair;699700  const UNQ_ASSET_ID_ON_ASTAR = 1;701  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;702703  // Unique -> Astar704  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.705  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?706707  before(async () => {708    await usingPlaygrounds(async (helper, privateKey) => {709      alice = await privateKey('//Alice');710      randomAccount = helper.arrange.createEmptyAccount();711      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);712      console.log('randomAccount', randomAccount.address);713714      // Set the default version to wrap the first message to other chains.715      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);716    });717718    await usingAstarPlaygrounds(astarUrl, async (helper) => {719      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {720        console.log('1. Create foreign asset and metadata');721        // TODO update metadata with values from production722        await helper.assets.create(723          alice,724          UNQ_ASSET_ID_ON_ASTAR,725          alice.address,726          UNQ_MINIMAL_BALANCE_ON_ASTAR,727        );728729        await helper.assets.setMetadata(730          alice,731          UNQ_ASSET_ID_ON_ASTAR,732          'Cross chain UNQ',733          'xcUNQ',734          Number(UNQ_DECIMALS),735        );736737        console.log('2. Register asset location on Astar');738        const assetLocation = {739          V2: {740            parents: 1,741            interior: {742              X1: {743                Parachain: UNIQUE_CHAIN,744              },745            },746          },747        };748749        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);750751        console.log('3. Set UNQ payment for XCM execution on Astar');752        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);753      }754      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');755      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);756    });757  });758759  itSub('Should connect and send UNQ to Astar', async () => {760    await genericSendUnqTo('astar', randomAccount);761  });762763  itSub('Should connect to Astar and send UNQ back', async () => {764    await genericSendUnqBack('astar', alice, randomAccount);765  });766767  itSub('Astar can send only up to its balance', async () => {768    await genericSendOnlyOwnedBalance('astar', alice);769  });770771  itSub('Should not accept reserve transfer of UNQ from Astar', async () => {772    await genericReserveTransferUNQfrom('astar', alice);773  });774});
after · tests/src/xcm/lowLevelXcmUnique.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, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {Event} from '../util/playgrounds/unique.dev';21import {nToBigInt} from '@polkadot/util';22import {hexToString} from '@polkadot/util';23import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';242526const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;27const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;28const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;29const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;30const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;3132let balanceUniqueTokenInit: bigint;33let balanceUniqueTokenMiddle: bigint;34let balanceUniqueTokenFinal: bigint;35let unqFees: bigint;363738async function genericSendUnqTo(39  networkName: keyof typeof NETWORKS,40  randomAccount: IKeyringPair,41  randomAccountOnTargetChain = randomAccount,42) {43  const networkUrl = mapToChainUrl(networkName);44  const targetPlayground = getDevPlayground(networkName);45  await usingPlaygrounds(async (helper) => {46    balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);47    const destination = {48      V2: {49        parents: 1,50        interior: {51          X1: {52            Parachain: mapToChainId(networkName),53          },54        },55      },56    };5758    const beneficiary = {59      V2: {60        parents: 0,61        interior: {62          X1: (63            networkName == 'moonbeam' ?64              {65                AccountKey20: {66                  network: 'Any',67                  key: randomAccountOnTargetChain.address,68                },69              }70              :71              {72                AccountId32: {73                  network: 'Any',74                  id: randomAccountOnTargetChain.addressRaw,75                },76              }77          ),78        },79      },80    };8182    const assets = {83      V2: [84        {85          id: {86            Concrete: {87              parents: 0,88              interior: 'Here',89            },90          },91          fun: {92            Fungible: TRANSFER_AMOUNT,93          },94        },95      ],96    };97    const feeAssetItem = 0;9899    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');100    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);101    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);102103    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;104    console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));105    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;106107    await targetPlayground(networkUrl, async (helper) => {108      /*109        Since only the parachain part of the Polkadex110        infrastructure is launched (without their111        solochain validators), processing incoming112        assets will lead to an error.113        This error indicates that the Polkadex chain114        received a message from the Unique network,115        since the hash is being checked to ensure116        it matches what was sent.117      */118      if(networkName == 'polkadex') {119        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);120      } else {121        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);122      }123    });124125  });126}127128async function genericSendUnqBack(129  networkName: keyof typeof NETWORKS,130  sudoer: IKeyringPair,131  randomAccountOnUnq: IKeyringPair,132) {133  const networkUrl = mapToChainUrl(networkName);134135  const targetPlayground = getDevPlayground(networkName);136  await usingPlaygrounds(async (helper) => {137138    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(139      randomAccountOnUnq.addressRaw,140      {141        Concrete: {142          parents: 1,143          interior: {144            X1: {Parachain: UNIQUE_CHAIN},145          },146        },147      },148      SENDBACK_AMOUNT,149    );150151    let xcmProgramSent: any;152153154    await targetPlayground(networkUrl, async (helper) => {155      if('getSudo' in helper) {156        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);157        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);158      } else if('fastDemocracy' in helper) {159        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);160        // Needed to bypass the call filter.161        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);162        await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);163        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);164      }165    });166167    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);168169    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);170171    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);172173  });174}175176async function genericSendOnlyOwnedBalance(177  networkName: keyof typeof NETWORKS,178  sudoer: IKeyringPair,179) {180  const networkUrl = mapToChainUrl(networkName);181  const targetPlayground = getDevPlayground(networkName);182183  const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);184185  await usingPlaygrounds(async (helper) => {186    const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));187    await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);188    const moreThanTargetChainHas = 2n * targetChainBalance;189190    const targetAccount = helper.arrange.createEmptyAccount();191192    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(193      targetAccount.addressRaw,194      {195        Concrete: {196          parents: 0,197          interior: 'Here',198        },199      },200      moreThanTargetChainHas,201    );202203    let maliciousXcmProgramSent: any;204205206    await targetPlayground(networkUrl, async (helper) => {207      if('getSudo' in helper) {208        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);209        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);210      } else if('fastDemocracy' in helper) {211        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);212        // Needed to bypass the call filter.213        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);214        await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);215        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);216      }217    });218219    await expectFailedToTransact(helper, maliciousXcmProgramSent);220221    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);222    expect(targetAccountBalance).to.be.equal(0n);223  });224}225226async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {227  const networkUrl = mapToChainUrl(netwokrName);228  const targetPlayground = getDevPlayground(netwokrName);229230  await usingPlaygrounds(async (helper) => {231    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);232    const targetAccount = helper.arrange.createEmptyAccount();233234    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(235      targetAccount.addressRaw,236      {237        Concrete: {238          parents: 1,239          interior: {240            X1: {241              Parachain: UNIQUE_CHAIN,242            },243          },244        },245      },246      testAmount,247    );248249    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(250      targetAccount.addressRaw,251      {252        Concrete: {253          parents: 0,254          interior: 'Here',255        },256      },257      testAmount,258    );259260    let maliciousXcmProgramFullIdSent: any;261    let maliciousXcmProgramHereIdSent: any;262    const maxWaitBlocks = 3;263264    // Try to trick Unique using full UNQ identification265    await targetPlayground(networkUrl, async (helper) => {266      if('getSudo' in helper) {267        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);268        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);269      }270      // Moonbeam case271      else if('fastDemocracy' in helper) {272        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);273        // Needed to bypass the call filter.274        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);275        await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);276277        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);278      }279    });280281282    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);283284    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);285    expect(accountBalance).to.be.equal(0n);286287    // Try to trick Unique using shortened UNQ identification288    await targetPlayground(networkUrl, async (helper) => {289      if('getSudo' in helper) {290        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);291        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);292      }293      else if('fastDemocracy' in helper) {294        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);295        // Needed to bypass the call filter.296        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);297        await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);298299        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);300      }301    });302303    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);304305    accountBalance = await helper.balance.getSubstrate(targetAccount.address);306    expect(accountBalance).to.be.equal(0n);307  });308}309310async function genericRejectNativeToknsFrom(netwokrName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {311  const networkUrl = mapToChainUrl(netwokrName);312  const targetPlayground = getDevPlayground(netwokrName);313  let messageSent: any;314315  await usingPlaygrounds(async (helper) => {316    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(317      helper.arrange.createEmptyAccount().addressRaw,318      {319        Concrete: {320          parents: 1,321          interior: {322            X1: {323              Parachain: mapToChainId(netwokrName),324            },325          },326        },327      },328      TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,329    );330    await targetPlayground(networkUrl, async (helper) => {331      if('getSudo' in helper) {332        await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);333        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);334      } else if('fastDemocracy' in helper) {335        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);336        // Needed to bypass the call filter.337        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);338        await helper.fastDemocracy.executeProposal(`${netwokrName} sending native tokens to the Unique via fast democracy`, batchCall);339340        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);341      }342    });343    await expectFailedToTransact(helper, messageSent);344  });345}346347348describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {349  let alice: IKeyringPair;350  let randomAccount: IKeyringPair;351352  before(async () => {353    await usingPlaygrounds(async (helper, privateKey) => {354      alice = await privateKey('//Alice');355      console.log(config.acalaUrl);356      randomAccount = helper.arrange.createEmptyAccount();357358      // Set the default version to wrap the first message to other chains.359      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);360    });361362    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {363      const destination = {364        V2: {365          parents: 1,366          interior: {367            X1: {368              Parachain: UNIQUE_CHAIN,369            },370          },371        },372      };373374      const metadata = {375        name: 'Unique Network',376        symbol: 'UNQ',377        decimals: 18,378        minimalBalance: 1250_000_000_000_000_000n,379      };380      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>381        hexToString(v.toJSON()['symbol'])) as string[];382383      if(!assets.includes('UNQ')) {384        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);385      } else {386        console.log('UNQ token already registered on Acala assetRegistry pallet');387      }388      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);389    });390391    await usingPlaygrounds(async (helper) => {392      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);393      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);394    });395  });396397  itSub('Should connect and send UNQ to Acala', async () => {398    await genericSendUnqTo('acala', randomAccount);399  });400401  itSub('Should connect to Acala and send UNQ back', async () => {402    await genericSendUnqBack('acala', alice, randomAccount);403  });404405  itSub('Acala can send only up to its balance', async () => {406    await genericSendOnlyOwnedBalance('acala', alice);407  });408409  itSub('Should not accept reserve transfer of UNQ from Acala', async () => {410    await genericReserveTransferUNQfrom('acala', alice);411  });412});413414describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {415  let alice: IKeyringPair;416  let randomAccount: IKeyringPair;417418  before(async () => {419    await usingPlaygrounds(async (helper, privateKey) => {420      alice = await privateKey('//Alice');421      randomAccount = helper.arrange.createEmptyAccount();422423      // Set the default version to wrap the first message to other chains.424      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);425    });426427    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {428      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))429        .toJSON() as [])430        .map(nToBigInt).length != 0;431      /*432      Check whether the Unique token has been added433      to the whitelist, since an error will occur434      if it is added again. Needed for debugging435      when this test is run multiple times.436      */437      if(isWhitelisted) {438        console.log('UNQ token is already whitelisted on Polkadex');439      } else {440        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);441      }442443      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);444    });445446    await usingPlaygrounds(async (helper) => {447      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);448      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);449    });450  });451452  itSub('Should connect and send UNQ to Polkadex', async () => {453    await genericSendUnqTo('polkadex', randomAccount);454  });455456457  itSub('Should connect to Polkadex and send UNQ back', async () => {458    await genericSendUnqBack('polkadex', alice, randomAccount);459  });460461  itSub('Polkadex can send only up to its balance', async () => {462    await genericSendOnlyOwnedBalance('polkadex', alice);463  });464465  itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {466    await genericReserveTransferUNQfrom('polkadex', alice);467  });468});469470// These tests are relevant only when471// the the corresponding foreign assets are not registered472describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {473  let alice: IKeyringPair;474475  before(async () => {476    await usingPlaygrounds(async (helper, privateKey) => {477      alice = await privateKey('//Alice');478479      // Set the default version to wrap the first message to other chains.480      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481    });482  });483484  itSub('Unique rejects ACA tokens from Acala', async () => {485    await genericRejectNativeToknsFrom('acala', alice);486  });487488  itSub('Unique rejects GLMR tokens from Moonbeam', async () => {489    await genericRejectNativeToknsFrom('moonbeam', alice);490  });491492  itSub('Unique rejects ASTR tokens from Astar', async () => {493    await genericRejectNativeToknsFrom('astar', alice);494  });495496  itSub('Unique rejects PDX tokens from Polkadex', async () => {497    await genericRejectNativeToknsFrom('polkadex', alice);498  });499});500501describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {502  // Unique constants503  let alice: IKeyringPair;504  let uniqueAssetLocation;505506  let randomAccountUnique: IKeyringPair;507  let randomAccountMoonbeam: IKeyringPair;508509  // Moonbeam constants510  let assetId: string;511512  const uniqueAssetMetadata = {513    name: 'xcUnique',514    symbol: 'xcUNQ',515    decimals: 18,516    isFrozen: false,517    minimalBalance: 1n,518  };519520521  before(async () => {522    await usingPlaygrounds(async (helper, privateKey) => {523      alice = await privateKey('//Alice');524      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);525526527      // Set the default version to wrap the first message to other chains.528      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);529    });530531    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {532      const alithAccount = helper.account.alithAccount();533      const baltatharAccount = helper.account.baltatharAccount();534      const dorothyAccount = helper.account.dorothyAccount();535536      randomAccountMoonbeam = helper.account.create();537538      // >>> Sponsoring Dorothy >>>539      console.log('Sponsoring Dorothy.......');540      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);541      console.log('Sponsoring Dorothy.......DONE');542      // <<< Sponsoring Dorothy <<<543      uniqueAssetLocation = {544        XCM: {545          parents: 1,546          interior: {X1: {Parachain: UNIQUE_CHAIN}},547        },548      };549      const existentialDeposit = 1n;550      const isSufficient = true;551      const unitsPerSecond = 1n;552      const numAssetsWeightHint = 0;553554      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {555        console.log('Unique asset already registered on Moonbeam');556      } else {557        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({558          location: uniqueAssetLocation,559          metadata: uniqueAssetMetadata,560          existentialDeposit,561          isSufficient,562          unitsPerSecond,563          numAssetsWeightHint,564        });565566        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);567568        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);569      }570571      // >>> Acquire Unique AssetId Info on Moonbeam >>>572      console.log('Acquire Unique AssetId Info on Moonbeam.......');573574      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();575576      console.log('UNQ asset ID is %s', assetId);577      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');578579      // >>> Sponsoring random Account >>>580      console.log('Sponsoring random Account.......');581      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);582      console.log('Sponsoring random Account.......DONE');583      // <<< Sponsoring random Account <<<584    });585586    await usingPlaygrounds(async (helper) => {587      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);588      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);589    });590  });591592  itSub('Should connect and send UNQ to Moonbeam', async () => {593    await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);594  });595596  itSub('Should connect to Moonbeam and send UNQ back', async () => {597    await genericSendUnqBack('moonbeam', alice, randomAccountUnique);598  });599600  itSub('Moonbeam can send only up to its balance', async () => {601    await genericSendOnlyOwnedBalance('moonbeam', alice);602  });603604  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {605    await genericReserveTransferUNQfrom('moonbeam', alice);606  });607});608609describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {610  let alice: IKeyringPair;611  let randomAccount: IKeyringPair;612613  const UNQ_ASSET_ID_ON_ASTAR = 1;614  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;615616  // Unique -> Astar617  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.618  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?619620  before(async () => {621    await usingPlaygrounds(async (helper, privateKey) => {622      alice = await privateKey('//Alice');623      randomAccount = helper.arrange.createEmptyAccount();624      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);625      console.log('randomAccount', randomAccount.address);626627      // Set the default version to wrap the first message to other chains.628      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);629    });630631    await usingAstarPlaygrounds(astarUrl, async (helper) => {632      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {633        console.log('1. Create foreign asset and metadata');634        // TODO update metadata with values from production635        await helper.assets.create(636          alice,637          UNQ_ASSET_ID_ON_ASTAR,638          alice.address,639          UNQ_MINIMAL_BALANCE_ON_ASTAR,640        );641642        await helper.assets.setMetadata(643          alice,644          UNQ_ASSET_ID_ON_ASTAR,645          'Cross chain UNQ',646          'xcUNQ',647          Number(UNQ_DECIMALS),648        );649650        console.log('2. Register asset location on Astar');651        const assetLocation = {652          V2: {653            parents: 1,654            interior: {655              X1: {656                Parachain: UNIQUE_CHAIN,657              },658            },659          },660        };661662        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);663664        console.log('3. Set UNQ payment for XCM execution on Astar');665        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);666      } else {667        console.log('UNQ is already registered on Astar');668      }669      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');670      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);671    });672  });673674  itSub('Should connect and send UNQ to Astar', async () => {675    await genericSendUnqTo('astar', randomAccount);676  });677678  itSub('Should connect to Astar and send UNQ back', async () => {679    await genericSendUnqBack('astar', alice, randomAccount);680  });681682  itSub('Astar can send only up to its balance', async () => {683    await genericSendOnlyOwnedBalance('astar', alice);684  });685686  itSub('Should not accept reserve transfer of UNQ from Astar', async () => {687    await genericReserveTransferUNQfrom('astar', alice);688  });689});
addedtests/src/xcm/xcm.types.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/xcm/xcm.types.ts
@@ -0,0 +1,85 @@
+import {usingAcalaPlaygrounds, usingAstarPlaygrounds, usingMoonbeamPlaygrounds, usingPolkadexPlaygrounds} from '../util';
+import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import config from '../config';
+
+export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
+export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
+export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);
+export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);
+export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
+export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
+
+export const acalaUrl = config.acalaUrl;
+export const moonbeamUrl = config.moonbeamUrl;
+export const astarUrl = config.astarUrl;
+export const polkadexUrl = config.polkadexUrl;
+
+export const SAFE_XCM_VERSION = 3;
+
+export const maxWaitBlocks = 6;
+
+
+export const ASTAR_DECIMALS = 18n;
+export const UNQ_DECIMALS = 18n;
+
+export const uniqueMultilocation = {
+  parents: 1,
+  interior: {
+    X1: {
+      Parachain: UNIQUE_CHAIN,
+    },
+  },
+};
+export const uniqueVersionedMultilocation = {
+  V3: uniqueMultilocation,
+};
+
+export const uniqueAssetId = {
+  Concrete: uniqueMultilocation,
+};
+
+export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
+  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
+        && event.outcome.isFailedToTransactAsset);
+};
+export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {
+  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
+         && event.outcome.isUntrustedReserveLocation);
+};
+
+export const NETWORKS = {
+  acala: usingAcalaPlaygrounds,
+  astar: usingAstarPlaygrounds,
+  polkadex: usingPolkadexPlaygrounds,
+  moonbeam: usingMoonbeamPlaygrounds,
+} as const;
+
+export function mapToChainId(networkName: keyof typeof NETWORKS) {
+  switch (networkName) {
+    case 'acala':
+      return ACALA_CHAIN;
+    case 'astar':
+      return ASTAR_CHAIN;
+    case 'moonbeam':
+      return MOONBEAM_CHAIN;
+    case 'polkadex':
+      return POLKADEX_CHAIN;
+  }
+}
+
+export function mapToChainUrl(networkName: keyof typeof NETWORKS): string {
+  switch (networkName) {
+    case 'acala':
+      return acalaUrl;
+    case 'astar':
+      return astarUrl;
+    case 'moonbeam':
+      return moonbeamUrl;
+    case 'polkadex':
+      return polkadexUrl;
+  }
+}
+
+export function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {
+  return NETWORKS[name];
+}
\ No newline at end of file
modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -17,15 +17,10 @@
 import {IKeyringPair} from '@polkadot/types/types';
 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 {Event} from '../util/playgrounds/unique.dev';
 import {hexToString, nToBigInt} from '@polkadot/util';
+import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
 
-const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
-const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
-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 STATEMINT_PALLET_INSTANCE = 50;
 
@@ -54,29 +49,7 @@
 const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';
 const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;
 const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;
-
-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);
-};
-const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {
-  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
-         && event.outcome.isUntrustedReserveLocation);
-};
 describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {
   let alice: IKeyringPair;
   let bob: IKeyringPair;
@@ -704,7 +677,7 @@
 
     // Try to trick Unique
     await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);
 
       maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
     });
@@ -729,7 +702,7 @@
     );
 
     await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);
+      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);
     });
 
     await helper.wait.newBlocks(maxWaitBlocks);
@@ -819,17 +792,6 @@
   let balanceUniqueTokenMiddle: bigint;
   let balanceUniqueTokenFinal: bigint;
   const maxWaitBlocks = 6;
-
-  const uniqueAssetId = {
-    Concrete: {
-      parents: 1,
-      interior: {
-        X1: {
-          Parachain: UNIQUE_CHAIN,
-        },
-      },
-    },
-  };
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
@@ -850,7 +812,9 @@
       if it is added again. Needed for debugging
       when this test is run multiple times.
       */
-      if(!isWhitelisted) {
+      if(isWhitelisted) {
+        console.log('UNQ token is already whitelisted on Polkadex');
+      } else {
         await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);
       }
 
@@ -951,7 +915,7 @@
 
 
     await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);
+      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);
 
       xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
     });
@@ -986,7 +950,7 @@
 
 
     await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);
 
       maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
     });
@@ -1465,7 +1429,7 @@
 
     // Try to trick Unique
     await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
-      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
 
       // Needed to bypass the call filter.
       const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
@@ -1494,7 +1458,7 @@
     );
 
     await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
-      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);
 
       // Needed to bypass the call filter.
       const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
@@ -1543,7 +1507,7 @@
 
     // Try to trick Unique using full UNQ identification
     await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
-      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
 
       // Needed to bypass the call filter.
       const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
@@ -1560,7 +1524,7 @@
 
     // Try to trick Unique using shortened UNQ identification
     await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
-      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
 
       // Needed to bypass the call filter.
       const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
@@ -1641,6 +1605,8 @@
 
         console.log('3. Set UNQ payment for XCM execution on Astar');
         await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+      } else {
+        console.log('UNQ is already registered on Astar');
       }
       console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');
       await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);
@@ -1815,7 +1781,7 @@
 
     // Try to trick Unique
     await usingAstarPlaygrounds(astarUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);
 
       maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
     });
@@ -1840,7 +1806,7 @@
     );
 
     await usingAstarPlaygrounds(astarUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);
+      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);
     });
 
     await helper.wait.newBlocks(maxWaitBlocks);
@@ -1885,7 +1851,7 @@
 
     // Try to trick Unique using full UNQ identification
     await usingAstarPlaygrounds(astarUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);
+      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
 
       maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
     });
@@ -1898,7 +1864,7 @@
 
     // Try to trick Unique using shortened UNQ identification
     await usingAstarPlaygrounds(astarUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);
+      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
 
       maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
     });