git.delta.rocks / unique-network / refs/commits / 503028ebb65c

difftreelog

test(xcm) qtz/unq rejects relay tokens

Daniel Shiposha2022-09-06parent: #6c9f876.patch.diff
in: master

3 files changed

modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -104,14 +104,9 @@
 }
 
 export function bigIntToDecimals(number: bigint, decimals = 18): string {
-  let numberStr = number.toString();
-  console.log('[0] str = ', numberStr);
-
-  // Get rid of `n` at the end
-  numberStr = numberStr.substring(0, numberStr.length - 1);
-  console.log('[1] str = ', numberStr);
-
+  const numberStr = number.toString();
   const dotPos = numberStr.length - decimals;
+
   if (dotPos <= 0) {
     return '0.' + numberStr;
   } else {
@@ -1794,19 +1789,31 @@
 ): Promise<EventRecord | null> {
 
   const promise = new Promise<EventRecord | null>(async (resolve) => {
-    const unsubscribe = await api.query.system.events(eventRecords => {
+    const unsubscribe = await api.rpc.chain.subscribeNewHeads(async header => {
+      const blockNumber = header.number.toHuman();
+      const blockHash = header.hash;
+      const eventIdStr = `${eventSection}.${eventMethod}`;
+      const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;
+
+      console.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);
+
+      const apiAt = await api.at(blockHash);
+      const eventRecords = await apiAt.query.system.events();
+
       const neededEvent = eventRecords.find(r => {
         return r.event.section == eventSection && r.event.method == eventMethod;
       });
 
       if (neededEvent) {
+        console.log(`Event \`${eventIdStr}\` is found`);
+
         unsubscribe();
         resolve(neededEvent);
-      }
-
-      if (maxBlocksToWait > 0) {
+      } else if (maxBlocksToWait > 0) {
         maxBlocksToWait--;
       } else {
+        console.log(`Event \`${eventIdStr}\` is NOT found`);
+
         unsubscribe();
         resolve(null);
       }
modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.test.ts
@@ -26,6 +26,7 @@
 import {blake2AsHex} from '@polkadot/util-crypto';
 import waitNewBlocks from '../substrate/wait-new-blocks';
 import getBalance from '../substrate/get-balance';
+import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
@@ -34,6 +35,7 @@
 const KARURA_CHAIN = 2000;
 const MOONRIVER_CHAIN = 2023;
 
+const RELAY_PORT = 9844;
 const KARURA_PORT = 9946;
 const MOONRIVER_PORT = 9947;
 
@@ -55,6 +57,10 @@
   return parachainApiOptions(MOONRIVER_PORT);
 }
 
+function relayOptions(): ApiOptions {
+  return parachainApiOptions(RELAY_PORT);
+}
+
 describe_xcm('Integration test: Exchanging tokens with Karura', () => {
   let alice: IKeyringPair;
   let randomAccount: IKeyringPair;
@@ -200,8 +206,8 @@
         const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;
         const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;
 
-        console.log('
-          [Quartz -> Karura] transaction fees on Karura: %s KAR',
+        console.log(
+          '[Quartz -> Karura] transaction fees on Karura: %s KAR',
           bigIntToDecimals(karFees, KARURA_DECIMALS),
         );
         console.log('[Quartz -> Karura] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));
@@ -281,10 +287,100 @@
       expect(qtzFees == 0n).to.be.true;
     });
   });
+});
+
+// These tests are relevant only when the foreign asset pallet is disabled
+describe('Integration test: Quartz rejects non-native tokens', () => {
+  let alice: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
+    });
+  });
+  
+  it('Quartz rejects tokens from the Relay', async () => {
+    await usingApi(async (api) => {
+      const destination = {
+        V1: {
+          parents: 0,
+          interior: {X1: {
+            Parachain: QUARTZ_CHAIN,
+          },
+          },
+        }};
+
+      const beneficiary = {
+        V1: {
+          parents: 0,
+          interior: {X1: {
+            AccountId32: {
+              network: 'Any',
+              id: alice.addressRaw,
+            },
+          }},
+        },
+      };
+
+      const assets = {
+        V1: [
+          {
+            id: {
+              Concrete: {
+                parents: 0,
+                interior: 'Here',
+              },
+            },
+            fun: {
+              Fungible: 50_000_000_000_000_000n,
+            },
+          },
+        ],
+      };
+
+      const feeAssetItem = 0;
+
+      const weightLimit = {
+        Limited: 5_000_000_000,
+      };
+
+      const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getGenericResult(events);
+      expect(result.success).to.be.true;
+    }, relayOptions());
+
+    await usingApi(async api => {
+      const maxWaitBlocks = 3;
+      const dmpQueueExecutedDownward = await waitEvent(
+        api,
+        maxWaitBlocks,
+        'dmpQueue',
+        'ExecutedDownward',
+      );
+
+      expect(
+        dmpQueueExecutedDownward != null,
+        '[Relay] dmpQueue.ExecutedDownward event is expected',
+      ).to.be.true;
+
+      const event = dmpQueueExecutedDownward!.event;
+      const outcome = event.data[1] as XcmV2TraitsOutcome;
+
+      expect(
+        outcome.isIncomplete,
+        '[Relay] The outcome of the XCM should be `Incomplete`',
+      ).to.be.true;
+
+      const incomplete = outcome.asIncomplete;
+      expect(
+        incomplete[1].toString() == 'AssetNotFound',
+        '[Relay] The XCM error should be `AssetNotFound`',
+      ).to.be.true;
+    });
+  });
 
   it('Quartz rejects KAR tokens from Karura', async () => {
-    // This test is relevant only when the foreign asset pallet is disabled
-
     await usingApi(async (api) => {
       const destination = {
         V1: {
@@ -295,7 +391,7 @@
               {
                 AccountId32: {
                   network: 'Any',
-                  id: randomAccount.addressRaw,
+                  id: alice.addressRaw,
                 },
               },
             ],
@@ -321,7 +417,15 @@
 
       expect(
         xcmpQueueFailEvent != null,
-        'Only native token is supported when the Foreign-Assets pallet is not connected',
+        '[Karura] xcmpQueue.FailEvent event is expected',
+      ).to.be.true;
+
+      const event = xcmpQueueFailEvent!.event;
+      const outcome = event.data[1] as XcmV2TraitsError;
+
+      expect(
+        outcome.isUntrustedReserveLocation,
+        '[Karura] The XCM error should be `UntrustedReserveLocation`',
       ).to.be.true;
     });
   });
modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
before · tests/src/xcm/xcmUnique.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import chai from 'chai';18import chaiAsPromised from 'chai-as-promised';1920import {WsProvider, Keyring} from '@polkadot/api';21import {ApiOptions} from '@polkadot/api/types';22import {IKeyringPair} from '@polkadot/types/types';23import usingApi, {submitTransactionAsync} from '../substrate/substrate-api';24import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../util/helpers';25import {MultiLocation} from '@polkadot/types/interfaces';26import {blake2AsHex} from '@polkadot/util-crypto';27import waitNewBlocks from '../substrate/wait-new-blocks';28import getBalance from '../substrate/get-balance';2930chai.use(chaiAsPromised);31const expect = chai.expect;3233const UNIQUE_CHAIN = 2037;34const ACALA_CHAIN = 2000;35const MOONBEAM_CHAIN = 2004;3637const ACALA_PORT = 9946;38const MOONBEAM_PORT = 9947;3940const ACALA_DECIMALS = 12;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344function parachainApiOptions(port: number): ApiOptions {45  return {46    provider: new WsProvider('ws://127.0.0.1:' + port.toString()),47  };48}4950function acalaOptions(): ApiOptions {51  return parachainApiOptions(ACALA_PORT);52}5354function moonbeamOptions(): ApiOptions {55  return parachainApiOptions(MOONBEAM_PORT);56}5758describe_xcm('Integration test: Exchanging tokens with Acala', () => {59  let alice: IKeyringPair;60  let randomAccount: IKeyringPair;61  62  let balanceUniqueTokenInit: bigint;63  let balanceUniqueTokenMiddle: bigint;64  let balanceUniqueTokenFinal: bigint;65  let balanceAcalaTokenInit: bigint;66  let balanceAcalaTokenMiddle: bigint;67  let balanceAcalaTokenFinal: bigint;68  let balanceUniqueForeignTokenInit: bigint;69  let balanceUniqueForeignTokenMiddle: bigint;70  let balanceUniqueForeignTokenFinal: bigint;71  72  before(async () => {73    await usingApi(async (api, privateKeyWrapper) => {74      alice = privateKeyWrapper('//Alice');75      randomAccount = generateKeyringPair();76    });7778    // Acala side79    await usingApi(80      async (api) => {81        const destination = {82          V0: {83            X2: [84              'Parent',85              {86                Parachain: UNIQUE_CHAIN,87              },88            ],89          },90        };91  92        const metadata = {93          name: 'UNQ',94          symbol: 'UNQ',95          decimals: 18,96          minimalBalance: 1,97        };98  99        const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);100        const sudoTx = api.tx.sudo.sudo(tx as any);101        const events = await submitTransactionAsync(alice, sudoTx);102        const result = getGenericResult(events);103        expect(result.success).to.be.true;104  105        const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n);106        const events1 = await submitTransactionAsync(alice, tx1);107        const result1 = getGenericResult(events1);108        expect(result1.success).to.be.true;109  110        [balanceAcalaTokenInit] = await getBalance(api, [randomAccount.address]);111        {112          const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;113          balanceUniqueForeignTokenInit = BigInt(free);114        }115      },116      acalaOptions(),117    );118  119    // Unique side120    await usingApi(async (api) => {121      const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT);122      const events0 = await submitTransactionAsync(alice, tx0);123      const result0 = getGenericResult(events0);124      expect(result0.success).to.be.true;125  126      [balanceUniqueTokenInit] = await getBalance(api, [randomAccount.address]);127    });128  });129  130  it('Should connect and send UNQ to Acala', async () => {131  132    // Unique side133    await usingApi(async (api) => {134  135      const destination = {136        V0: {137          X2: [138            'Parent',139            {140              Parachain: ACALA_CHAIN,141            },142          ],143        },144      };145  146      const beneficiary = {147        V0: {148          X1: {149            AccountId32: {150              network: 'Any',151              id: randomAccount.addressRaw,152            },153          },154        },155      };156  157      const assets = {158        V1: [159          {160            id: {161              Concrete: {162                parents: 0,163                interior: 'Here',164              },165            },166            fun: {167              Fungible: TRANSFER_AMOUNT,168            },169          },170        ],171      };172  173      const feeAssetItem = 0;174  175      const weightLimit = {176        Limited: 5000000000,177      };178  179      const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);180      const events = await submitTransactionAsync(randomAccount, tx);181      const result = getGenericResult(events);182      expect(result.success).to.be.true;183  184      [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccount.address]);185  186      const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;187      console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));188      expect(unqFees > 0n).to.be.true;189    });190  191    // Acala side192    await usingApi(193      async (api) => {194        await waitNewBlocks(api, 3);195        const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;196        balanceUniqueForeignTokenMiddle = BigInt(free);197  198        [balanceAcalaTokenMiddle] = await getBalance(api, [randomAccount.address]);199200        const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;201        const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;202203        console.log(204          '[Unique -> Acala] transaction fees on Acala: %s ACA',205          bigIntToDecimals(acaFees, ACALA_DECIMALS),206        );207        console.log('[Unique -> Acala] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));208        expect(acaFees == 0n).to.be.true;209        expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;210      },211      acalaOptions(),212    );213  });214  215  it('Should connect to Acala and send UNQ back', async () => {216  217    // Acala side218    await usingApi(219      async (api) => {220        const destination = {221          V1: {222            parents: 1,223            interior: {224              X2: [225                {Parachain: UNIQUE_CHAIN},226                {227                  AccountId32: {228                    network: 'Any',229                    id: randomAccount.addressRaw,230                  },231                },232              ],233            },234          },235        };236  237        const id = {238          ForeignAsset: 0,239        };240241        const destWeight = 50000000;242  243        const tx = api.tx.xTokens.transfer(id, TRANSFER_AMOUNT, destination, destWeight);244        const events = await submitTransactionAsync(randomAccount, tx);245        const result = getGenericResult(events);246        expect(result.success).to.be.true;247  248        [balanceAcalaTokenFinal] = await getBalance(api, [randomAccount.address]);249        {250          const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;251          balanceUniqueForeignTokenFinal = BigInt(free);252        }253  254        const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;255        const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;256257        console.log(258          '[Acala -> Unique] transaction fees on Acala: %s ACA',259          bigIntToDecimals(acaFees, ACALA_DECIMALS),260        );261        console.log('[Acala -> Unique] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));262263        expect(acaFees > 0).to.be.true;264        expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;265      },266      acalaOptions(),267    );268269    // Unique side270    await usingApi(async (api) => {271      await waitNewBlocks(api, 3);272  273      [balanceUniqueTokenFinal] = await getBalance(api, [randomAccount.address]);274      const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;275      expect(actuallyDelivered > 0).to.be.true;276277      console.log('[Acala -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));278  279      const unqFees = TRANSFER_AMOUNT - actuallyDelivered;280      console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));281      expect(unqFees == 0n).to.be.true;282    });283  });284285  it('Unique rejects ACA tokens from Acala', async () => {286    // This test is relevant only when the foreign asset pallet is disabled287288    await usingApi(async (api) => {289      const destination = {290        V1: {291          parents: 1,292          interior: {293            X2: [294              {Parachain: UNIQUE_CHAIN},295              {296                AccountId32: {297                  network: 'Any',298                  id: randomAccount.addressRaw,299                },300              },301            ],302          },303        },304      };305306      const id = {307        Token: 'ACA',308      };309310      const destWeight = 50000000;311312      const tx = api.tx.xTokens.transfer(id, 100_000_000_000, destination, destWeight);313      const events = await submitTransactionAsync(alice, tx);314      const result = getGenericResult(events);315      expect(result.success).to.be.true;316    }, acalaOptions());317318    await usingApi(async api => {319      const maxWaitBlocks = 3;320      const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail');321322      expect(323        xcmpQueueFailEvent != null,324        'Only native token is supported when the Foreign-Assets pallet is not connected',325      ).to.be.true;326    });327  });328});329330describe_xcm('Integration test: Exchanging UNQ with Moonbeam', () => {331332  // Unique constants333  let uniqueAlice: IKeyringPair;334  let uniqueAssetLocation;335336  let randomAccountUnique: IKeyringPair;337  let randomAccountMoonbeam: IKeyringPair;338339  // Moonbeam constants340  let assetId: string;341342  const moonbeamKeyring = new Keyring({type: 'ethereum'});343  const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';344  const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';345  const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';346347  const alithAccount = moonbeamKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum');348  const baltatharAccount = moonbeamKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');349  const dorothyAccount = moonbeamKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');350351  const councilVotingThreshold = 2;352  const technicalCommitteeThreshold = 2;353  const votingPeriod = 3;354  const delayPeriod = 0;355356  const uniqueAssetMetadata = {357    name: 'xcUnique',358    symbol: 'xcUNQ',359    decimals: 18,360    isFrozen: false,361    minimalBalance: 1,362  };363364  let balanceUniqueTokenInit: bigint;365  let balanceUniqueTokenMiddle: bigint;366  let balanceUniqueTokenFinal: bigint;367  let balanceForeignUnqTokenInit: bigint;368  let balanceForeignUnqTokenMiddle: bigint;369  let balanceForeignUnqTokenFinal: bigint;370  let balanceGlmrTokenInit: bigint;371  let balanceGlmrTokenMiddle: bigint;372  let balanceGlmrTokenFinal: bigint;373374  before(async () => {375    await usingApi(async (api, privateKeyWrapper) => {376      uniqueAlice = privateKeyWrapper('//Alice');377      randomAccountUnique = generateKeyringPair();378      randomAccountMoonbeam = generateKeyringPair('ethereum');379380      balanceForeignUnqTokenInit = 0n;381    });382383    await usingApi(384      async (api) => {385386        // >>> Sponsoring Dorothy >>>387        console.log('Sponsoring Dorothy.......');388        const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n);389        const events0 = await submitTransactionAsync(alithAccount, tx0);390        const result0 = getGenericResult(events0);391        expect(result0.success).to.be.true;392        console.log('Sponsoring Dorothy.......DONE');393        // <<< Sponsoring Dorothy <<<394395        const sourceLocation: MultiLocation = api.createType(396          'MultiLocation',397          {398            parents: 1,399            interior: {X1: {Parachain: UNIQUE_CHAIN}},400          },401        );402403        uniqueAssetLocation = {XCM: sourceLocation};404        const existentialDeposit = 1;405        const isSufficient = true;406        const unitsPerSecond = '1';407        const numAssetsWeightHint = 0;408409        const registerTx = api.tx.assetManager.registerForeignAsset(410          uniqueAssetLocation,411          uniqueAssetMetadata,412          existentialDeposit,413          isSufficient,414        );415        console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');416417        const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(418          uniqueAssetLocation,419          unitsPerSecond,420          numAssetsWeightHint,421        );422        console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');423424        const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);425        console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');426427        // >>> Note motion preimage >>>428        console.log('Note motion preimage.......');429        const encodedProposal = batchCall?.method.toHex() || '';430        const proposalHash = blake2AsHex(encodedProposal);431        console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);432        console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);433        console.log('Encoded length %d', encodedProposal.length);434435        const tx1 = api.tx.democracy.notePreimage(encodedProposal);436        const events1 = await submitTransactionAsync(baltatharAccount, tx1);437        const result1 = getGenericResult(events1);438        expect(result1.success).to.be.true;439        console.log('Note motion preimage.......DONE');440        // <<< Note motion preimage <<<441442        // >>> Propose external motion through council >>>443        console.log('Propose external motion through council.......');444        const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash);445        const tx2 = api.tx.councilCollective.propose(446          councilVotingThreshold,447          externalMotion,448          externalMotion.encodedLength,449        );450        const events2 = await submitTransactionAsync(baltatharAccount, tx2);451        const result2 = getGenericResult(events2);452        expect(result2.success).to.be.true;453454        const encodedMotion = externalMotion?.method.toHex() || '';455        const motionHash = blake2AsHex(encodedMotion);456        console.log('Motion hash is %s', motionHash);457458        const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);459        {460          const events3 = await submitTransactionAsync(dorothyAccount, tx3);461          const result3 = getGenericResult(events3);462          expect(result3.success).to.be.true;463        }464        {465          const events3 = await submitTransactionAsync(baltatharAccount, tx3);466          const result3 = getGenericResult(events3);467          expect(result3.success).to.be.true;468        }469470        const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);471        const events4 = await submitTransactionAsync(dorothyAccount, tx4);472        const result4 = getGenericResult(events4);473        expect(result4.success).to.be.true;474        console.log('Propose external motion through council.......DONE');475        // <<< Propose external motion through council <<<476477        // >>> Fast track proposal through technical committee >>>478        console.log('Fast track proposal through technical committee.......');479        const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);480        const tx5 = api.tx.techCommitteeCollective.propose(481          technicalCommitteeThreshold,482          fastTrack,483          fastTrack.encodedLength,484        );485        const events5 = await submitTransactionAsync(alithAccount, tx5);486        const result5 = getGenericResult(events5);487        expect(result5.success).to.be.true;488489        const encodedFastTrack = fastTrack?.method.toHex() || '';490        const fastTrackHash = blake2AsHex(encodedFastTrack);491        console.log('FastTrack hash is %s', fastTrackHash);492493        const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;494        const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);495        {496          const events6 = await submitTransactionAsync(baltatharAccount, tx6);497          const result6 = getGenericResult(events6);498          expect(result6.success).to.be.true;499        }500        {501          const events6 = await submitTransactionAsync(alithAccount, tx6);502          const result6 = getGenericResult(events6);503          expect(result6.success).to.be.true;504        }505506        const tx7 = api.tx.techCommitteeCollective507          .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);508        const events7 = await submitTransactionAsync(baltatharAccount, tx7);509        const result7 = getGenericResult(events7);510        expect(result7.success).to.be.true;511        console.log('Fast track proposal through technical committee.......DONE');512        // <<< Fast track proposal through technical committee <<<513514        // >>> Referendum voting >>>515        console.log('Referendum voting.......');516        const tx8 = api.tx.democracy.vote(517          0,518          {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}},519        );520        const events8 = await submitTransactionAsync(dorothyAccount, tx8);521        const result8 = getGenericResult(events8);522        expect(result8.success).to.be.true;523        console.log('Referendum voting.......DONE');524        // <<< Referendum voting <<<525526        // >>> Acquire Unique AssetId Info on Moonbeam >>>527        console.log('Acquire Unique AssetId Info on Moonbeam.......');528529        // Wait for the democracy execute530        await waitNewBlocks(api, 5);531532        assetId = (await api.query.assetManager.assetTypeId({533          XCM: sourceLocation,534        })).toString();535536        console.log('UNQ asset ID is %s', assetId);537        console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');538        // >>> Acquire Unique AssetId Info on Moonbeam >>>539540        // >>> Sponsoring random Account >>>541        console.log('Sponsoring random Account.......');542        const tx10 = api.tx.balances.transfer(randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);543        const events10 = await submitTransactionAsync(baltatharAccount, tx10);544        const result10 = getGenericResult(events10);545        expect(result10.success).to.be.true;546        console.log('Sponsoring random Account.......DONE');547        // <<< Sponsoring random Account <<<548549        [balanceGlmrTokenInit] = await getBalance(api, [randomAccountMoonbeam.address]);550      },551      moonbeamOptions(),552    );553554    await usingApi(async (api) => {555      const tx0 = api.tx.balances.transfer(randomAccountUnique.address, 10n * TRANSFER_AMOUNT);556      const events0 = await submitTransactionAsync(uniqueAlice, tx0);557      const result0 = getGenericResult(events0);558      expect(result0.success).to.be.true;559560      [balanceUniqueTokenInit] = await getBalance(api, [randomAccountUnique.address]);561    });562  });563564  it('Should connect and send UNQ to Moonbeam', async () => {565    await usingApi(async (api) => {566      const currencyId = {567        NativeAssetId: 'Here',568      };569      const dest = {570        V1: {571          parents: 1,572          interior: {573            X2: [574              {Parachain: MOONBEAM_CHAIN},575              {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},576            ],577          },578        },579      };580      const amount = TRANSFER_AMOUNT;581      const destWeight = 850000000;582583      const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight);584      const events = await submitTransactionAsync(randomAccountUnique, tx);585      const result = getGenericResult(events);586      expect(result.success).to.be.true;587588      [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccountUnique.address]);589      expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;590591      const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;592      console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', bigIntToDecimals(transactionFees));593      expect(transactionFees > 0).to.be.true;594    });595596    await usingApi(597      async (api) => {598        await waitNewBlocks(api, 3);599600        [balanceGlmrTokenMiddle] = await getBalance(api, [randomAccountMoonbeam.address]);601602        const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;603        console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));604        expect(glmrFees == 0n).to.be.true;605606        const unqRandomAccountAsset = (607          await api.query.assets.account(assetId, randomAccountMoonbeam.address)608        ).toJSON()! as any;609610        balanceForeignUnqTokenMiddle = BigInt(unqRandomAccountAsset['balance']);611        const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;612        console.log('[Unique -> Moonbeam] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));613        expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;614      },615      moonbeamOptions(),616    );617  });618619  it('Should connect to Moonbeam and send UNQ back', async () => {620    await usingApi(621      async (api) => {622        const asset = {623          V1: {624            id: {625              Concrete: {626                parents: 1,627                interior: {628                  X1: {Parachain: UNIQUE_CHAIN},629                },630              },631            },632            fun: {633              Fungible: TRANSFER_AMOUNT,634            },635          },636        };637        const destination = {638          V1: {639            parents: 1,640            interior: {641              X2: [642                {Parachain: UNIQUE_CHAIN},643                {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},644              ],645            },646          },647        };648        const destWeight = 50000000;649650        const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight);651        const events = await submitTransactionAsync(randomAccountMoonbeam, tx);652        const result = getGenericResult(events);653        expect(result.success).to.be.true;654655        [balanceGlmrTokenFinal] = await getBalance(api, [randomAccountMoonbeam.address]);656657        const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;658        console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));659        expect(glmrFees > 0).to.be.true;660661        const unqRandomAccountAsset = (662          await api.query.assets.account(assetId, randomAccountMoonbeam.address)663        ).toJSON()! as any;664665        expect(unqRandomAccountAsset).to.be.null;666667        balanceForeignUnqTokenFinal = 0n;668669        const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;670        console.log('[Unique -> Moonbeam] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));671        expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;672      },673      moonbeamOptions(),674    );675676    await usingApi(async (api) => {677      await waitNewBlocks(api, 3);678679      [balanceUniqueTokenFinal] = await getBalance(api, [randomAccountUnique.address]);680      const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;681      expect(actuallyDelivered > 0).to.be.true;682683      console.log('[Moonbeam -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));684685      const unqFees = TRANSFER_AMOUNT - actuallyDelivered;686      console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));687      expect(unqFees == 0n).to.be.true;688    });689  });690});
after · tests/src/xcm/xcmUnique.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import chai from 'chai';18import chaiAsPromised from 'chai-as-promised';1920import {WsProvider, Keyring} from '@polkadot/api';21import {ApiOptions} from '@polkadot/api/types';22import {IKeyringPair} from '@polkadot/types/types';23import usingApi, {submitTransactionAsync} from '../substrate/substrate-api';24import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../util/helpers';25import {MultiLocation} from '@polkadot/types/interfaces';26import {blake2AsHex} from '@polkadot/util-crypto';27import waitNewBlocks from '../substrate/wait-new-blocks';28import getBalance from '../substrate/get-balance';29import {XcmV2TraitsError, XcmV2TraitsOutcome} from '../interfaces';3031chai.use(chaiAsPromised);32const expect = chai.expect;3334const UNIQUE_CHAIN = 2037;35const ACALA_CHAIN = 2000;36const MOONBEAM_CHAIN = 2004;3738const RELAY_PORT = 9844;39const ACALA_PORT = 9946;40const MOONBEAM_PORT = 9947;4142const ACALA_DECIMALS = 12;4344const TRANSFER_AMOUNT = 2000000000000000000000000n;4546function parachainApiOptions(port: number): ApiOptions {47  return {48    provider: new WsProvider('ws://127.0.0.1:' + port.toString()),49  };50}5152function acalaOptions(): ApiOptions {53  return parachainApiOptions(ACALA_PORT);54}5556function moonbeamOptions(): ApiOptions {57  return parachainApiOptions(MOONBEAM_PORT);58}5960function relayOptions(): ApiOptions {61  return parachainApiOptions(RELAY_PORT);62}6364describe_xcm('Integration test: Exchanging tokens with Acala', () => {65  let alice: IKeyringPair;66  let randomAccount: IKeyringPair;67  68  let balanceUniqueTokenInit: bigint;69  let balanceUniqueTokenMiddle: bigint;70  let balanceUniqueTokenFinal: bigint;71  let balanceAcalaTokenInit: bigint;72  let balanceAcalaTokenMiddle: bigint;73  let balanceAcalaTokenFinal: bigint;74  let balanceUniqueForeignTokenInit: bigint;75  let balanceUniqueForeignTokenMiddle: bigint;76  let balanceUniqueForeignTokenFinal: bigint;77  78  before(async () => {79    await usingApi(async (api, privateKeyWrapper) => {80      alice = privateKeyWrapper('//Alice');81      randomAccount = generateKeyringPair();82    });8384    // Acala side85    await usingApi(86      async (api) => {87        const destination = {88          V0: {89            X2: [90              'Parent',91              {92                Parachain: UNIQUE_CHAIN,93              },94            ],95          },96        };97  98        const metadata = {99          name: 'UNQ',100          symbol: 'UNQ',101          decimals: 18,102          minimalBalance: 1,103        };104  105        const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);106        const sudoTx = api.tx.sudo.sudo(tx as any);107        const events = await submitTransactionAsync(alice, sudoTx);108        const result = getGenericResult(events);109        expect(result.success).to.be.true;110  111        const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n);112        const events1 = await submitTransactionAsync(alice, tx1);113        const result1 = getGenericResult(events1);114        expect(result1.success).to.be.true;115  116        [balanceAcalaTokenInit] = await getBalance(api, [randomAccount.address]);117        {118          const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;119          balanceUniqueForeignTokenInit = BigInt(free);120        }121      },122      acalaOptions(),123    );124  125    // Unique side126    await usingApi(async (api) => {127      const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT);128      const events0 = await submitTransactionAsync(alice, tx0);129      const result0 = getGenericResult(events0);130      expect(result0.success).to.be.true;131  132      [balanceUniqueTokenInit] = await getBalance(api, [randomAccount.address]);133    });134  });135  136  it('Should connect and send UNQ to Acala', async () => {137  138    // Unique side139    await usingApi(async (api) => {140  141      const destination = {142        V0: {143          X2: [144            'Parent',145            {146              Parachain: ACALA_CHAIN,147            },148          ],149        },150      };151  152      const beneficiary = {153        V0: {154          X1: {155            AccountId32: {156              network: 'Any',157              id: randomAccount.addressRaw,158            },159          },160        },161      };162  163      const assets = {164        V1: [165          {166            id: {167              Concrete: {168                parents: 0,169                interior: 'Here',170              },171            },172            fun: {173              Fungible: TRANSFER_AMOUNT,174            },175          },176        ],177      };178  179      const feeAssetItem = 0;180  181      const weightLimit = {182        Limited: 5000000000,183      };184  185      const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);186      const events = await submitTransactionAsync(randomAccount, tx);187      const result = getGenericResult(events);188      expect(result.success).to.be.true;189  190      [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccount.address]);191  192      const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;193      console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));194      expect(unqFees > 0n).to.be.true;195    });196  197    // Acala side198    await usingApi(199      async (api) => {200        await waitNewBlocks(api, 3);201        const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;202        balanceUniqueForeignTokenMiddle = BigInt(free);203  204        [balanceAcalaTokenMiddle] = await getBalance(api, [randomAccount.address]);205206        const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;207        const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;208209        console.log(210          '[Unique -> Acala] transaction fees on Acala: %s ACA',211          bigIntToDecimals(acaFees, ACALA_DECIMALS),212        );213        console.log('[Unique -> Acala] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));214        expect(acaFees == 0n).to.be.true;215        expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;216      },217      acalaOptions(),218    );219  });220  221  it('Should connect to Acala and send UNQ back', async () => {222  223    // Acala side224    await usingApi(225      async (api) => {226        const destination = {227          V1: {228            parents: 1,229            interior: {230              X2: [231                {Parachain: UNIQUE_CHAIN},232                {233                  AccountId32: {234                    network: 'Any',235                    id: randomAccount.addressRaw,236                  },237                },238              ],239            },240          },241        };242  243        const id = {244          ForeignAsset: 0,245        };246247        const destWeight = 50000000;248  249        const tx = api.tx.xTokens.transfer(id, TRANSFER_AMOUNT, destination, destWeight);250        const events = await submitTransactionAsync(randomAccount, tx);251        const result = getGenericResult(events);252        expect(result.success).to.be.true;253  254        [balanceAcalaTokenFinal] = await getBalance(api, [randomAccount.address]);255        {256          const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;257          balanceUniqueForeignTokenFinal = BigInt(free);258        }259  260        const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;261        const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;262263        console.log(264          '[Acala -> Unique] transaction fees on Acala: %s ACA',265          bigIntToDecimals(acaFees, ACALA_DECIMALS),266        );267        console.log('[Acala -> Unique] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));268269        expect(acaFees > 0).to.be.true;270        expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;271      },272      acalaOptions(),273    );274275    // Unique side276    await usingApi(async (api) => {277      await waitNewBlocks(api, 3);278  279      [balanceUniqueTokenFinal] = await getBalance(api, [randomAccount.address]);280      const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;281      expect(actuallyDelivered > 0).to.be.true;282283      console.log('[Acala -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));284  285      const unqFees = TRANSFER_AMOUNT - actuallyDelivered;286      console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));287      expect(unqFees == 0n).to.be.true;288    });289  });290});291292// These tests are relevant only when the foreign asset pallet is disabled293describe('Integration test: Unique rejects non-native tokens', () => {294  let alice: IKeyringPair;295296  before(async () => {297    await usingApi(async (api, privateKeyWrapper) => {298      alice = privateKeyWrapper('//Alice');299    });300  });301  302  it('Unique rejects tokens from the Relay', async () => {303    await usingApi(async (api) => {304      const destination = {305        V1: {306          parents: 0,307          interior: {X1: {308            Parachain: UNIQUE_CHAIN,309          },310          },311        }};312313      const beneficiary = {314        V1: {315          parents: 0,316          interior: {X1: {317            AccountId32: {318              network: 'Any',319              id: alice.addressRaw,320            },321          }},322        },323      };324325      const assets = {326        V1: [327          {328            id: {329              Concrete: {330                parents: 0,331                interior: 'Here',332              },333            },334            fun: {335              Fungible: 50_000_000_000_000_000n,336            },337          },338        ],339      };340341      const feeAssetItem = 0;342343      const weightLimit = {344        Limited: 5_000_000_000,345      };346347      const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);348      const events = await submitTransactionAsync(alice, tx);349      const result = getGenericResult(events);350      expect(result.success).to.be.true;351    }, relayOptions());352353    await usingApi(async api => {354      const maxWaitBlocks = 3;355      const dmpQueueExecutedDownward = await waitEvent(356        api,357        maxWaitBlocks,358        'dmpQueue',359        'ExecutedDownward',360      );361362      expect(363        dmpQueueExecutedDownward != null,364        '[Relay] dmpQueue.ExecutedDownward event is expected',365      ).to.be.true;366367      const event = dmpQueueExecutedDownward!.event;368      const outcome = event.data[1] as XcmV2TraitsOutcome;369370      expect(371        outcome.isIncomplete,372        '[Relay] The outcome of the XCM should be `Incomplete`',373      ).to.be.true;374375      const incomplete = outcome.asIncomplete;376      expect(377        incomplete[1].toString() == 'AssetNotFound',378        '[Relay] The XCM error should be `AssetNotFound`',379      ).to.be.true;380    });381  });382383  it('Unique rejects ACA tokens from Acala', async () => {384    await usingApi(async (api) => {385      const destination = {386        V1: {387          parents: 1,388          interior: {389            X2: [390              {Parachain: UNIQUE_CHAIN},391              {392                AccountId32: {393                  network: 'Any',394                  id: alice.addressRaw,395                },396              },397            ],398          },399        },400      };401402      const id = {403        Token: 'ACA',404      };405406      const destWeight = 50000000;407408      const tx = api.tx.xTokens.transfer(id, 100_000_000_000, destination, destWeight);409      const events = await submitTransactionAsync(alice, tx);410      const result = getGenericResult(events);411      expect(result.success).to.be.true;412    }, acalaOptions());413414    await usingApi(async api => {415      const maxWaitBlocks = 3;416      const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail');417418      expect(419        xcmpQueueFailEvent != null,420        '[Acala] xcmpQueue.FailEvent event is expected',421      ).to.be.true;422423      const event = xcmpQueueFailEvent!.event;424      const outcome = event.data[1] as XcmV2TraitsError;425426      expect(427        outcome.isUntrustedReserveLocation,428        '[Acala] The XCM error should be `UntrustedReserveLocation`',429      ).to.be.true;430    });431  });432});433434describe_xcm('Integration test: Exchanging UNQ with Moonbeam', () => {435436  // Unique constants437  let uniqueAlice: IKeyringPair;438  let uniqueAssetLocation;439440  let randomAccountUnique: IKeyringPair;441  let randomAccountMoonbeam: IKeyringPair;442443  // Moonbeam constants444  let assetId: string;445446  const moonbeamKeyring = new Keyring({type: 'ethereum'});447  const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';448  const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';449  const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';450451  const alithAccount = moonbeamKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum');452  const baltatharAccount = moonbeamKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');453  const dorothyAccount = moonbeamKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');454455  const councilVotingThreshold = 2;456  const technicalCommitteeThreshold = 2;457  const votingPeriod = 3;458  const delayPeriod = 0;459460  const uniqueAssetMetadata = {461    name: 'xcUnique',462    symbol: 'xcUNQ',463    decimals: 18,464    isFrozen: false,465    minimalBalance: 1,466  };467468  let balanceUniqueTokenInit: bigint;469  let balanceUniqueTokenMiddle: bigint;470  let balanceUniqueTokenFinal: bigint;471  let balanceForeignUnqTokenInit: bigint;472  let balanceForeignUnqTokenMiddle: bigint;473  let balanceForeignUnqTokenFinal: bigint;474  let balanceGlmrTokenInit: bigint;475  let balanceGlmrTokenMiddle: bigint;476  let balanceGlmrTokenFinal: bigint;477478  before(async () => {479    await usingApi(async (api, privateKeyWrapper) => {480      uniqueAlice = privateKeyWrapper('//Alice');481      randomAccountUnique = generateKeyringPair();482      randomAccountMoonbeam = generateKeyringPair('ethereum');483484      balanceForeignUnqTokenInit = 0n;485    });486487    await usingApi(488      async (api) => {489490        // >>> Sponsoring Dorothy >>>491        console.log('Sponsoring Dorothy.......');492        const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n);493        const events0 = await submitTransactionAsync(alithAccount, tx0);494        const result0 = getGenericResult(events0);495        expect(result0.success).to.be.true;496        console.log('Sponsoring Dorothy.......DONE');497        // <<< Sponsoring Dorothy <<<498499        const sourceLocation: MultiLocation = api.createType(500          'MultiLocation',501          {502            parents: 1,503            interior: {X1: {Parachain: UNIQUE_CHAIN}},504          },505        );506507        uniqueAssetLocation = {XCM: sourceLocation};508        const existentialDeposit = 1;509        const isSufficient = true;510        const unitsPerSecond = '1';511        const numAssetsWeightHint = 0;512513        const registerTx = api.tx.assetManager.registerForeignAsset(514          uniqueAssetLocation,515          uniqueAssetMetadata,516          existentialDeposit,517          isSufficient,518        );519        console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');520521        const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(522          uniqueAssetLocation,523          unitsPerSecond,524          numAssetsWeightHint,525        );526        console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');527528        const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);529        console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');530531        // >>> Note motion preimage >>>532        console.log('Note motion preimage.......');533        const encodedProposal = batchCall?.method.toHex() || '';534        const proposalHash = blake2AsHex(encodedProposal);535        console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);536        console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);537        console.log('Encoded length %d', encodedProposal.length);538539        const tx1 = api.tx.democracy.notePreimage(encodedProposal);540        const events1 = await submitTransactionAsync(baltatharAccount, tx1);541        const result1 = getGenericResult(events1);542        expect(result1.success).to.be.true;543        console.log('Note motion preimage.......DONE');544        // <<< Note motion preimage <<<545546        // >>> Propose external motion through council >>>547        console.log('Propose external motion through council.......');548        const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash);549        const tx2 = api.tx.councilCollective.propose(550          councilVotingThreshold,551          externalMotion,552          externalMotion.encodedLength,553        );554        const events2 = await submitTransactionAsync(baltatharAccount, tx2);555        const result2 = getGenericResult(events2);556        expect(result2.success).to.be.true;557558        const encodedMotion = externalMotion?.method.toHex() || '';559        const motionHash = blake2AsHex(encodedMotion);560        console.log('Motion hash is %s', motionHash);561562        const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);563        {564          const events3 = await submitTransactionAsync(dorothyAccount, tx3);565          const result3 = getGenericResult(events3);566          expect(result3.success).to.be.true;567        }568        {569          const events3 = await submitTransactionAsync(baltatharAccount, tx3);570          const result3 = getGenericResult(events3);571          expect(result3.success).to.be.true;572        }573574        const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);575        const events4 = await submitTransactionAsync(dorothyAccount, tx4);576        const result4 = getGenericResult(events4);577        expect(result4.success).to.be.true;578        console.log('Propose external motion through council.......DONE');579        // <<< Propose external motion through council <<<580581        // >>> Fast track proposal through technical committee >>>582        console.log('Fast track proposal through technical committee.......');583        const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);584        const tx5 = api.tx.techCommitteeCollective.propose(585          technicalCommitteeThreshold,586          fastTrack,587          fastTrack.encodedLength,588        );589        const events5 = await submitTransactionAsync(alithAccount, tx5);590        const result5 = getGenericResult(events5);591        expect(result5.success).to.be.true;592593        const encodedFastTrack = fastTrack?.method.toHex() || '';594        const fastTrackHash = blake2AsHex(encodedFastTrack);595        console.log('FastTrack hash is %s', fastTrackHash);596597        const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;598        const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);599        {600          const events6 = await submitTransactionAsync(baltatharAccount, tx6);601          const result6 = getGenericResult(events6);602          expect(result6.success).to.be.true;603        }604        {605          const events6 = await submitTransactionAsync(alithAccount, tx6);606          const result6 = getGenericResult(events6);607          expect(result6.success).to.be.true;608        }609610        const tx7 = api.tx.techCommitteeCollective611          .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);612        const events7 = await submitTransactionAsync(baltatharAccount, tx7);613        const result7 = getGenericResult(events7);614        expect(result7.success).to.be.true;615        console.log('Fast track proposal through technical committee.......DONE');616        // <<< Fast track proposal through technical committee <<<617618        // >>> Referendum voting >>>619        console.log('Referendum voting.......');620        const tx8 = api.tx.democracy.vote(621          0,622          {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}},623        );624        const events8 = await submitTransactionAsync(dorothyAccount, tx8);625        const result8 = getGenericResult(events8);626        expect(result8.success).to.be.true;627        console.log('Referendum voting.......DONE');628        // <<< Referendum voting <<<629630        // >>> Acquire Unique AssetId Info on Moonbeam >>>631        console.log('Acquire Unique AssetId Info on Moonbeam.......');632633        // Wait for the democracy execute634        await waitNewBlocks(api, 5);635636        assetId = (await api.query.assetManager.assetTypeId({637          XCM: sourceLocation,638        })).toString();639640        console.log('UNQ asset ID is %s', assetId);641        console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');642        // >>> Acquire Unique AssetId Info on Moonbeam >>>643644        // >>> Sponsoring random Account >>>645        console.log('Sponsoring random Account.......');646        const tx10 = api.tx.balances.transfer(randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);647        const events10 = await submitTransactionAsync(baltatharAccount, tx10);648        const result10 = getGenericResult(events10);649        expect(result10.success).to.be.true;650        console.log('Sponsoring random Account.......DONE');651        // <<< Sponsoring random Account <<<652653        [balanceGlmrTokenInit] = await getBalance(api, [randomAccountMoonbeam.address]);654      },655      moonbeamOptions(),656    );657658    await usingApi(async (api) => {659      const tx0 = api.tx.balances.transfer(randomAccountUnique.address, 10n * TRANSFER_AMOUNT);660      const events0 = await submitTransactionAsync(uniqueAlice, tx0);661      const result0 = getGenericResult(events0);662      expect(result0.success).to.be.true;663664      [balanceUniqueTokenInit] = await getBalance(api, [randomAccountUnique.address]);665    });666  });667668  it('Should connect and send UNQ to Moonbeam', async () => {669    await usingApi(async (api) => {670      const currencyId = {671        NativeAssetId: 'Here',672      };673      const dest = {674        V1: {675          parents: 1,676          interior: {677            X2: [678              {Parachain: MOONBEAM_CHAIN},679              {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},680            ],681          },682        },683      };684      const amount = TRANSFER_AMOUNT;685      const destWeight = 850000000;686687      const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight);688      const events = await submitTransactionAsync(randomAccountUnique, tx);689      const result = getGenericResult(events);690      expect(result.success).to.be.true;691692      [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccountUnique.address]);693      expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;694695      const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;696      console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', bigIntToDecimals(transactionFees));697      expect(transactionFees > 0).to.be.true;698    });699700    await usingApi(701      async (api) => {702        await waitNewBlocks(api, 3);703704        [balanceGlmrTokenMiddle] = await getBalance(api, [randomAccountMoonbeam.address]);705706        const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;707        console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));708        expect(glmrFees == 0n).to.be.true;709710        const unqRandomAccountAsset = (711          await api.query.assets.account(assetId, randomAccountMoonbeam.address)712        ).toJSON()! as any;713714        balanceForeignUnqTokenMiddle = BigInt(unqRandomAccountAsset['balance']);715        const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;716        console.log('[Unique -> Moonbeam] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));717        expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;718      },719      moonbeamOptions(),720    );721  });722723  it('Should connect to Moonbeam and send UNQ back', async () => {724    await usingApi(725      async (api) => {726        const asset = {727          V1: {728            id: {729              Concrete: {730                parents: 1,731                interior: {732                  X1: {Parachain: UNIQUE_CHAIN},733                },734              },735            },736            fun: {737              Fungible: TRANSFER_AMOUNT,738            },739          },740        };741        const destination = {742          V1: {743            parents: 1,744            interior: {745              X2: [746                {Parachain: UNIQUE_CHAIN},747                {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},748              ],749            },750          },751        };752        const destWeight = 50000000;753754        const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight);755        const events = await submitTransactionAsync(randomAccountMoonbeam, tx);756        const result = getGenericResult(events);757        expect(result.success).to.be.true;758759        [balanceGlmrTokenFinal] = await getBalance(api, [randomAccountMoonbeam.address]);760761        const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;762        console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));763        expect(glmrFees > 0).to.be.true;764765        const unqRandomAccountAsset = (766          await api.query.assets.account(assetId, randomAccountMoonbeam.address)767        ).toJSON()! as any;768769        expect(unqRandomAccountAsset).to.be.null;770771        balanceForeignUnqTokenFinal = 0n;772773        const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;774        console.log('[Unique -> Moonbeam] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));775        expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;776      },777      moonbeamOptions(),778    );779780    await usingApi(async (api) => {781      await waitNewBlocks(api, 3);782783      [balanceUniqueTokenFinal] = await getBalance(api, [randomAccountUnique.address]);784      const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;785      expect(actuallyDelivered > 0).to.be.true;786787      console.log('[Moonbeam -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));788789      const unqFees = TRANSFER_AMOUNT - actuallyDelivered;790      console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));791      expect(unqFees == 0n).to.be.true;792    });793  });794});