git.delta.rocks / unique-network / refs/commits / 68bf2bb2d784

difftreelog

source

tests/src/xcm/xcmQuartz.test.ts20.7 KiBsourcehistory
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 {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXcm, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds} from '../util/playgrounds';2223const QUARTZ_CHAIN = 2095;24const KARURA_CHAIN = 2000;25const MOONRIVER_CHAIN = 2023;2627const relayUrl = config.relayUrl;28const karuraUrl = config.karuraUrl;29const moonriverUrl = config.moonriverUrl;3031const KARURA_DECIMALS = 12;3233const TRANSFER_AMOUNT = 2000000000000000000000000n;3435describeXcm('[XCM] Integration test: Exchanging tokens with Karura', () => {36  let alice: IKeyringPair;37  let randomAccount: IKeyringPair;3839  let balanceQuartzTokenInit: bigint;40  let balanceQuartzTokenMiddle: bigint;41  let balanceQuartzTokenFinal: bigint;42  let balanceKaruraTokenInit: bigint;43  let balanceKaruraTokenMiddle: bigint;44  let balanceKaruraTokenFinal: bigint;45  let balanceQuartzForeignTokenInit: bigint;46  let balanceQuartzForeignTokenMiddle: bigint;47  let balanceQuartzForeignTokenFinal: bigint;4849  before(async () => {50    await usingPlaygrounds(async (helper, privateKey) => {51      alice = privateKey('//Alice');52      [randomAccount] = await helper.arrange.createAccounts([0n], alice);53    });5455    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {56      const destination = {57        V0: {58          X2: [59            'Parent',60            {61              Parachain: QUARTZ_CHAIN,62            },63          ],64        },65      };6667      const metadata = {68        name: 'QTZ',69        symbol: 'QTZ',70        decimals: 18,71        minimalBalance: 1n,72      };7374      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);75      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);76      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);77      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});78    });7980    await usingPlaygrounds(async (helper) => {81      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);82      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);83    });84  });8586  itSub('Should connect and send QTZ to Karura', async ({helper}) => {87    const destination = {88      V0: {89        X2: [90          'Parent',91          {92            Parachain: KARURA_CHAIN,93          },94        ],95      },96    };9798    const beneficiary = {99      V0: {100        X1: {101          AccountId32: {102            network: 'Any',103            id: randomAccount.addressRaw,104          },105        },106      },107    };108109    const assets = {110      V1: [111        {112          id: {113            Concrete: {114              parents: 0,115              interior: 'Here',116            },117          },118          fun: {119            Fungible: TRANSFER_AMOUNT,120          },121        },122      ],123    };124125    const feeAssetItem = 0;126    const weightLimit = 5000000000;127128    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);129    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);130131    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;132    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));133    expect(qtzFees > 0n).to.be.true;134135    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {136      await helper.wait.newBlocks(3);137      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});138      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);139140      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;141      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;142143      console.log(144        '[Quartz -> Karura] transaction fees on Karura: %s KAR',145        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),146      );147      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));148      expect(karFees == 0n).to.be.true;149      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;150    });151  });152153  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {154    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {155      const destination = {156        V1: {157          parents: 1,158          interior: {159            X2: [160              {Parachain: QUARTZ_CHAIN},161              {162                AccountId32: {163                  network: 'Any',164                  id: randomAccount.addressRaw,165                },166              },167            ],168          },169        },170      };171172      const id = {173        ForeignAsset: 0,174      };175176      const destWeight = 50000000;177178      await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);179      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);180      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);181182      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;183      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;184185      console.log(186        '[Karura -> Quartz] transaction fees on Karura: %s KAR',187        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),188      );189      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));190191      expect(karFees > 0).to.be.true;192      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;193    });194195    await helper.wait.newBlocks(3);196197    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);198    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;199    expect(actuallyDelivered > 0).to.be.true;200201    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));202203    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;204    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));205    expect(qtzFees == 0n).to.be.true;206  });207});208209// These tests are relevant only when the foreign asset pallet is disabled210describeXcm('[XCM] Integration test: Quartz rejects non-native tokens', () => {211  let alice: IKeyringPair;212213  before(async () => {214    await usingPlaygrounds(async (_helper, privateKey) => {215      alice = privateKey('//Alice');216    });217  });218219  itSub('Quartz rejects tokens from the Relay', async ({helper}) => {220    await usingRelayPlaygrounds(relayUrl, async (helper) => {221      const destination = {222        V1: {223          parents: 0,224          interior: {X1: {225            Parachain: QUARTZ_CHAIN,226          },227          },228        }};229230      const beneficiary = {231        V1: {232          parents: 0,233          interior: {X1: {234            AccountId32: {235              network: 'Any',236              id: alice.addressRaw,237            },238          }},239        },240      };241242      const assets = {243        V1: [244          {245            id: {246              Concrete: {247                parents: 0,248                interior: 'Here',249              },250            },251            fun: {252              Fungible: 50_000_000_000_000_000n,253            },254          },255        ],256      };257258      const feeAssetItem = 0;259      const weightLimit = 5_000_000_000;260261      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);262    });263264    const maxWaitBlocks = 3;265266    const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');267268    expect(269      dmpQueueExecutedDownward != null,270      '[Relay] dmpQueue.ExecutedDownward event is expected',271    ).to.be.true;272273    const event = dmpQueueExecutedDownward!.event;274    const outcome = event.data[1] as XcmV2TraitsOutcome;275276    expect(277      outcome.isIncomplete,278      '[Relay] The outcome of the XCM should be `Incomplete`',279    ).to.be.true;280281    const incomplete = outcome.asIncomplete;282    expect(283      incomplete[1].toString() == 'AssetNotFound',284      '[Relay] The XCM error should be `AssetNotFound`',285    ).to.be.true;286  });287288  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {289    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {290      const destination = {291        V1: {292          parents: 1,293          interior: {294            X2: [295              {Parachain: QUARTZ_CHAIN},296              {297                AccountId32: {298                  network: 'Any',299                  id: alice.addressRaw,300                },301              },302            ],303          },304        },305      };306307      const id = {308        Token: 'KAR',309      };310311      const destWeight = 50000000;312313      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);314    });315316    const maxWaitBlocks = 3;317318    const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');319320    expect(321      xcmpQueueFailEvent != null,322      '[Karura] xcmpQueue.FailEvent event is expected',323    ).to.be.true;324325    const event = xcmpQueueFailEvent!.event;326    const outcome = event.data[1] as XcmV2TraitsError;327328    expect(329      outcome.isUntrustedReserveLocation,330      '[Karura] The XCM error should be `UntrustedReserveLocation`',331    ).to.be.true;332  });333});334335describeXcm('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {336337  // Quartz constants338  let quartzDonor: IKeyringPair;339  let quartzAssetLocation;340341  let randomAccountQuartz: IKeyringPair;342  let randomAccountMoonriver: IKeyringPair;343344  // Moonriver constants345  let assetId: string;346347  const councilVotingThreshold = 2;348  const technicalCommitteeThreshold = 2;349  const votingPeriod = 3;350  const delayPeriod = 0;351352  const quartzAssetMetadata = {353    name: 'xcQuartz',354    symbol: 'xcQTZ',355    decimals: 18,356    isFrozen: false,357    minimalBalance: 1n,358  };359360  let balanceQuartzTokenInit: bigint;361  let balanceQuartzTokenMiddle: bigint;362  let balanceQuartzTokenFinal: bigint;363  let balanceForeignQtzTokenInit: bigint;364  let balanceForeignQtzTokenMiddle: bigint;365  let balanceForeignQtzTokenFinal: bigint;366  let balanceMovrTokenInit: bigint;367  let balanceMovrTokenMiddle: bigint;368  let balanceMovrTokenFinal: bigint;369370  before(async () => {371    await usingPlaygrounds(async (helper, privateKey) => {372      quartzDonor = privateKey('//Alice');373      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);374375      balanceForeignQtzTokenInit = 0n;376    });377378    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {379      const alithAccount = helper.account.alithAccount();380      const baltatharAccount = helper.account.baltatharAccount();381      const dorothyAccount = helper.account.dorothyAccount();382383      randomAccountMoonriver = helper.account.create();384385      // >>> Sponsoring Dorothy >>>386      console.log('Sponsoring Dorothy.......');387      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);388      console.log('Sponsoring Dorothy.......DONE');389      // <<< Sponsoring Dorothy <<<390391      quartzAssetLocation = {392        XCM: {393          parents: 1,394          interior: {X1: {Parachain: QUARTZ_CHAIN}},395        },396      };397      const existentialDeposit = 1n;398      const isSufficient = true;399      const unitsPerSecond = 1n;400      const numAssetsWeightHint = 0;401402      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({403        location: quartzAssetLocation,404        metadata: quartzAssetMetadata,405        existentialDeposit,406        isSufficient,407        unitsPerSecond,408        numAssetsWeightHint,409      });410      const proposalHash = blake2AsHex(encodedProposal);411412      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);413      console.log('Encoded length %d', encodedProposal.length);414      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);415416      // >>> Note motion preimage >>>417      console.log('Note motion preimage.......');418      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);419      console.log('Note motion preimage.......DONE');420      // <<< Note motion preimage <<<421422      // >>> Propose external motion through council >>>423      console.log('Propose external motion through council.......');424      const externalMotion = helper.democracy.externalProposeMajority(proposalHash);425      const encodedMotion = externalMotion?.method.toHex() || '';426      const motionHash = blake2AsHex(encodedMotion);427      console.log('Motion hash is %s', motionHash);428429      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);430431      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;432      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);433      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);434435      await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);436      console.log('Propose external motion through council.......DONE');437      // <<< Propose external motion through council <<<438439      // >>> Fast track proposal through technical committee >>>440      console.log('Fast track proposal through technical committee.......');441      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);442      const encodedFastTrack = fastTrack?.method.toHex() || '';443      const fastTrackHash = blake2AsHex(encodedFastTrack);444      console.log('FastTrack hash is %s', fastTrackHash);445446      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);447448      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;449      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);450      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);451452      await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);453      console.log('Fast track proposal through technical committee.......DONE');454      // <<< Fast track proposal through technical committee <<<455456      // >>> Referendum voting >>>457      console.log('Referendum voting.......');458      await helper.democracy.referendumVote(dorothyAccount, 0, {459        balance: 10_000_000_000_000_000_000n,460        vote: {aye: true, conviction: 1},461      });462      console.log('Referendum voting.......DONE');463      // <<< Referendum voting <<<464465      // >>> Acquire Quartz AssetId Info on Moonriver >>>466      console.log('Acquire Quartz AssetId Info on Moonriver.......');467468      // Wait for the democracy execute469      await helper.wait.newBlocks(5);470471      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();472473      console.log('QTZ asset ID is %s', assetId);474      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');475      // >>> Acquire Quartz AssetId Info on Moonriver >>>476477      // >>> Sponsoring random Account >>>478      console.log('Sponsoring random Account.......');479      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);480      console.log('Sponsoring random Account.......DONE');481      // <<< Sponsoring random Account <<<482483      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);484    });485486    await usingPlaygrounds(async (helper) => {487      await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);488      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);489    });490  });491492  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {493    const currencyId = {494      NativeAssetId: 'Here',495    };496    const dest = {497      V1: {498        parents: 1,499        interior: {500          X2: [501            {Parachain: MOONRIVER_CHAIN},502            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},503          ],504        },505      },506    };507    const amount = TRANSFER_AMOUNT;508    const destWeight = 850000000;509510    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, destWeight);511512    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);513    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;514515    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;516    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));517    expect(transactionFees > 0).to.be.true;518519    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {520      await helper.wait.newBlocks(3);521522      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);523524      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;525      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));526      expect(movrFees == 0n).to.be.true;527528      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);529      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;530      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));531      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;532    });533  });534535  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {536    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {537      const asset = {538        V1: {539          id: {540            Concrete: {541              parents: 1,542              interior: {543                X1: {Parachain: QUARTZ_CHAIN},544              },545            },546          },547          fun: {548            Fungible: TRANSFER_AMOUNT,549          },550        },551      };552      const destination = {553        V1: {554          parents: 1,555          interior: {556            X2: [557              {Parachain: QUARTZ_CHAIN},558              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},559            ],560          },561        },562      };563      const destWeight = 50000000;564565      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, destWeight);566567      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);568569      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;570      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));571      expect(movrFees > 0).to.be.true;572573      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);574575      expect(qtzRandomAccountAsset).to.be.null;576577      balanceForeignQtzTokenFinal = 0n;578579      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;580      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));581      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;582    });583584    await helper.wait.newBlocks(3);585586    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);587    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;588    expect(actuallyDelivered > 0).to.be.true;589590    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));591592    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;593    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));594    expect(qtzFees == 0n).to.be.true;595  });596});