git.delta.rocks / unique-network / refs/commits / 0f81eccfefee

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';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 = await 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 = await 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', () => {336  // Quartz constants337  let quartzDonor: IKeyringPair;338  let quartzAssetLocation;339340  let randomAccountQuartz: IKeyringPair;341  let randomAccountMoonriver: IKeyringPair;342343  // Moonriver constants344  let assetId: string;345346  const councilVotingThreshold = 2;347  const technicalCommitteeThreshold = 2;348  const votingPeriod = 3;349  const delayPeriod = 0;350351  const quartzAssetMetadata = {352    name: 'xcQuartz',353    symbol: 'xcQTZ',354    decimals: 18,355    isFrozen: false,356    minimalBalance: 1n,357  };358359  let balanceQuartzTokenInit: bigint;360  let balanceQuartzTokenMiddle: bigint;361  let balanceQuartzTokenFinal: bigint;362  let balanceForeignQtzTokenInit: bigint;363  let balanceForeignQtzTokenMiddle: bigint;364  let balanceForeignQtzTokenFinal: bigint;365  let balanceMovrTokenInit: bigint;366  let balanceMovrTokenMiddle: bigint;367  let balanceMovrTokenFinal: bigint;368369  before(async () => {370    await usingPlaygrounds(async (helper, privateKey) => {371      quartzDonor = await privateKey('//Alice');372      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);373374      balanceForeignQtzTokenInit = 0n;375    });376377    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {378      const alithAccount = helper.account.alithAccount();379      const baltatharAccount = helper.account.baltatharAccount();380      const dorothyAccount = helper.account.dorothyAccount();381382      randomAccountMoonriver = helper.account.create();383384      // >>> Sponsoring Dorothy >>>385      console.log('Sponsoring Dorothy.......');386      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);387      console.log('Sponsoring Dorothy.......DONE');388      // <<< Sponsoring Dorothy <<<389390      quartzAssetLocation = {391        XCM: {392          parents: 1,393          interior: {X1: {Parachain: QUARTZ_CHAIN}},394        },395      };396      const existentialDeposit = 1n;397      const isSufficient = true;398      const unitsPerSecond = 1n;399      const numAssetsWeightHint = 0;400401      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({402        location: quartzAssetLocation,403        metadata: quartzAssetMetadata,404        existentialDeposit,405        isSufficient,406        unitsPerSecond,407        numAssetsWeightHint,408      });409      const proposalHash = blake2AsHex(encodedProposal);410411      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);412      console.log('Encoded length %d', encodedProposal.length);413      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);414415      // >>> Note motion preimage >>>416      console.log('Note motion preimage.......');417      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);418      console.log('Note motion preimage.......DONE');419      // <<< Note motion preimage <<<420421      // >>> Propose external motion through council >>>422      console.log('Propose external motion through council.......');423      const externalMotion = helper.democracy.externalProposeMajority(proposalHash);424      const encodedMotion = externalMotion?.method.toHex() || '';425      const motionHash = blake2AsHex(encodedMotion);426      console.log('Motion hash is %s', motionHash);427428      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);429430      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;431      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);432      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);433434      await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);435      console.log('Propose external motion through council.......DONE');436      // <<< Propose external motion through council <<<437438      // >>> Fast track proposal through technical committee >>>439      console.log('Fast track proposal through technical committee.......');440      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);441      const encodedFastTrack = fastTrack?.method.toHex() || '';442      const fastTrackHash = blake2AsHex(encodedFastTrack);443      console.log('FastTrack hash is %s', fastTrackHash);444445      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);446447      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;448      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);449      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);450451      await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);452      console.log('Fast track proposal through technical committee.......DONE');453      // <<< Fast track proposal through technical committee <<<454455      // >>> Referendum voting >>>456      console.log('Referendum voting.......');457      await helper.democracy.referendumVote(dorothyAccount, 0, {458        balance: 10_000_000_000_000_000_000n,459        vote: {aye: true, conviction: 1},460      });461      console.log('Referendum voting.......DONE');462      // <<< Referendum voting <<<463464      // >>> Acquire Quartz AssetId Info on Moonriver >>>465      console.log('Acquire Quartz AssetId Info on Moonriver.......');466467      // Wait for the democracy execute468      await helper.wait.newBlocks(5);469470      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();471472      console.log('QTZ asset ID is %s', assetId);473      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');474      // >>> Acquire Quartz AssetId Info on Moonriver >>>475476      // >>> Sponsoring random Account >>>477      console.log('Sponsoring random Account.......');478      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);479      console.log('Sponsoring random Account.......DONE');480      // <<< Sponsoring random Account <<<481482      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);483    });484485    await usingPlaygrounds(async (helper) => {486      await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);487      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);488    });489  });490491  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {492    const currencyId = {493      NativeAssetId: 'Here',494    };495    const dest = {496      V1: {497        parents: 1,498        interior: {499          X2: [500            {Parachain: MOONRIVER_CHAIN},501            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},502          ],503        },504      },505    };506    const amount = TRANSFER_AMOUNT;507    const destWeight = 850000000;508509    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, destWeight);510511    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);512    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;513514    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;515    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));516    expect(transactionFees > 0).to.be.true;517518    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {519      await helper.wait.newBlocks(3);520521      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);522523      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;524      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));525      expect(movrFees == 0n).to.be.true;526527      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);528      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;529      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));530      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;531    });532  });533534  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {535    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {536      const asset = {537        V1: {538          id: {539            Concrete: {540              parents: 1,541              interior: {542                X1: {Parachain: QUARTZ_CHAIN},543              },544            },545          },546          fun: {547            Fungible: TRANSFER_AMOUNT,548          },549        },550      };551      const destination = {552        V1: {553          parents: 1,554          interior: {555            X2: [556              {Parachain: QUARTZ_CHAIN},557              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},558            ],559          },560        },561      };562      const destWeight = 50000000;563564      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, destWeight);565566      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);567568      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;569      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));570      expect(movrFees > 0).to.be.true;571572      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);573574      expect(qtzRandomAccountAsset).to.be.null;575576      balanceForeignQtzTokenFinal = 0n;577578      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;579      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));580      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;581    });582583    await helper.wait.newBlocks(3);584585    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);586    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;587    expect(actuallyDelivered > 0).to.be.true;588589    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));590591    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;592    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));593    expect(qtzFees == 0n).to.be.true;594  });595});