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

difftreelog

fix Quartz send relay tokens back

Daniel Shiposha2022-12-22parent: #09ca6c2.patch.diff
in: master

1 file changed

modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
before · tests/src/xcm/xcmQuartz.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds} from '../util';2223const QUARTZ_CHAIN = 2095;24const STATEMINE_CHAIN = 1000;25const KARURA_CHAIN = 2000;26const MOONRIVER_CHAIN = 2023;2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;3435const STATEMINE_DECIMALS = 12;36const KARURA_DECIMALS = 12;3738const TRANSFER_AMOUNT = 2000000000000000000000000n;3940const FUNDING_AMOUNT = 3_500_000_0000_000_000n; 4142const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4344const USDT_ASSET_ID = 100;45const USDT_ASSET_METADATA_DECIMALS = 18;46const USDT_ASSET_METADATA_NAME = 'USDT';47const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';48const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;49const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5051describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {52  let alice: IKeyringPair;53  let bob: IKeyringPair;54  55  let balanceStmnBefore: bigint;56  let balanceStmnAfter: bigint;5758  let balanceQuartzBefore: bigint;59  let balanceQuartzAfter: bigint;60  let balanceQuartzFinal: bigint;6162  let balanceBobBefore: bigint;63  let balanceBobAfter: bigint;64  let balanceBobFinal: bigint;6566  let balanceBobRelayTokenBefore: bigint;67  let balanceBobRelayTokenAfter: bigint;686970  before(async () => {71    await usingPlaygrounds(async (_helper, privateKey) => {72      alice = await privateKey('//Alice');73      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor74    });7576    await usingRelayPlaygrounds(relayUrl, async (helper) => {77      // Fund accounts on Statemine(t)78      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);79      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);80    });8182    await usingStateminePlaygrounds(statemineUrl, async (helper) => {83      const sovereignFundingAmount = 3_500_000_000n; 8485      await helper.assets.create(86        alice,87        USDT_ASSET_ID,88        alice.address,89        USDT_ASSET_METADATA_MINIMAL_BALANCE,90      );91      await helper.assets.setMetadata(92        alice,93        USDT_ASSET_ID,94        USDT_ASSET_METADATA_NAME,95        USDT_ASSET_METADATA_DESCRIPTION,96        USDT_ASSET_METADATA_DECIMALS,97      );98      await helper.assets.mint(99        alice,100        USDT_ASSET_ID,101        alice.address,102        USDT_ASSET_AMOUNT,103      );104105      // funding parachain sovereing account on Statemine(t).106      // The sovereign account should be created before any action107      // (the assets pallet on Statemine(t) check if the sovereign account exists)108      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);109      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);110    });111112113    await usingPlaygrounds(async (helper) => {114      const location = {115        V1: {116          parents: 1,117          interior: {X3: [118            {119              Parachain: STATEMINE_CHAIN,120            },121            {122              PalletInstance: STATEMINE_PALLET_INSTANCE,123            },124            {125              GeneralIndex: USDT_ASSET_ID,126            },127          ]},128        },129      };130131      const metadata =132      {133        name: USDT_ASSET_ID,134        symbol: USDT_ASSET_METADATA_NAME,135        decimals: USDT_ASSET_METADATA_DECIMALS,136        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,137      };138      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);139      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);140    });141142143    // Providing the relay currency to the quartz sender account144    // (fee for USDT XCM are paid in relay tokens)145    await usingRelayPlaygrounds(relayUrl, async (helper) => {146      const destination = {147        V1: {148          parents: 0,149          interior: {X1: {150            Parachain: QUARTZ_CHAIN,151          },152          },153        }};154155      const beneficiary = {156        V1: {157          parents: 0,158          interior: {X1: {159            AccountId32: {160              network: 'Any',161              id: alice.addressRaw,162            },163          }},164        },165      };166167      const assets = {168        V1: [169          {170            id: {171              Concrete: {172                parents: 0,173                interior: 'Here',174              },175            },176            fun: {177              Fungible: TRANSFER_AMOUNT_RELAY,178            },179          },180        ],181      };182183      const feeAssetItem = 0;184185      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});186    });187  188  });189190  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {191    await usingStateminePlaygrounds(statemineUrl, async (helper) => {192      const dest = {193        V1: {194          parents: 1,195          interior: {X1: {196            Parachain: QUARTZ_CHAIN,197          },198          },199        }};200201      const beneficiary = {202        V1: {203          parents: 0,204          interior: {X1: {205            AccountId32: {206              network: 'Any',207              id: alice.addressRaw,208            },209          }},210        },211      };212213      const assets = {214        V1: [215          {216            id: {217              Concrete: {218                parents: 0,219                interior: {220                  X2: [221                    {222                      PalletInstance: STATEMINE_PALLET_INSTANCE,223                    },224                    {225                      GeneralIndex: USDT_ASSET_ID,226                    }, 227                  ]},228              },229            },230            fun: {231              Fungible: TRANSFER_AMOUNT,232            },233          },234        ],235      };236237      const feeAssetItem = 0;238239      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);240      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, {Unlimited: null});241242      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);243244      // common good parachain take commission in it native token245      console.log(246        '[Quartz -> Statemine] transaction fees on Statemine: %s WND',247        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),248      );249      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;250251    });252253254    // ensure that asset has been delivered255    await helper.wait.newBlocks(3);256257    // expext collection id will be with id 1258    const free = await helper.ft.getBalance(1, {Substrate: alice.address});259260    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);261262    console.log(263      '[Quartz -> Statemine] transaction fees on Quartz: %s USDT',264      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),265    );266    console.log(267      '[Quartz -> Statemine] transaction fees on Quartz: %s QTZ',268      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),269    );    270    // commission has not paid in USDT token271    expect(free).to.be.equal(TRANSFER_AMOUNT);272    // ... and parachain native token273    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;274  });275276  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {277    const destination = {278      V1: {279        parents: 1,280        interior: {X2: [281          {282            Parachain: STATEMINE_CHAIN,283          },284          {285            AccountId32: {286              network: 'Any',287              id: alice.addressRaw,288            },289          },290        ]},291      },292    };293294    const relayFee = 400_000_000_000_000n;295    const currencies: [any, bigint][] = [296      [297        {298          ForeignAssetId: 0,299        },300        TRANSFER_AMOUNT,301      ], 302      [303        {304          NativeAssetId: 'Parent',305        },306        relayFee,307      ],308    ];309310    const feeItem = 1;311312    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, {Unlimited: null});313    314    // the commission has been paid in parachain native token315    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);316    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;317318    await usingStateminePlaygrounds(statemineUrl, async (helper) => {319      await helper.wait.newBlocks(3);320      321      // The USDT token never paid fees. Its amount not changed from begin value.322      // Also check that xcm transfer has been succeeded 323      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;324    });325  });326327  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {328    balanceBobBefore = await helper.balance.getSubstrate(bob.address);329    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});330331    await usingRelayPlaygrounds(relayUrl, async (helper) => {332      const destination = {333        V1: {334          parents: 0,335          interior: {X1: {336            Parachain: QUARTZ_CHAIN,337          },338          },339        }};340341      const beneficiary = {342        V1: {343          parents: 0,344          interior: {X1: {345            AccountId32: {346              network: 'Any',347              id: bob.addressRaw,348            },349          }},350        },351      };352353      const assets = {354        V1: [355          {356            id: {357              Concrete: {358                parents: 0,359                interior: 'Here',360              },361            },362            fun: {363              Fungible: TRANSFER_AMOUNT_RELAY,364            },365          },366        ],367      };368369      const feeAssetItem = 0;370371      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});372    });373  374    await helper.wait.newBlocks(3);375376    balanceBobAfter = await helper.balance.getSubstrate(bob.address);  377    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});378379    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;380    console.log(381      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',382      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),383    );384    console.log(385      '[Relay (Westend) -> Quartz] transaction fees: %s WND',386      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),387    );388    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;389    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;390    expect(balanceBobRelayTokenBefore < balanceBobRelayTokenAfter).to.be.true;391  });392393  itSub('Should connect and send Relay token back', async ({helper}) => {394    const destination = {395      V1: {396        parents: 1,397        interior: {X2: [398          {399            Parachain: STATEMINE_CHAIN,400          },401          {402            AccountId32: {403              network: 'Any',404              id: bob.addressRaw,405            },406          },407        ]},408      },409    };410411    const currencies: any = [412      [413        {414          NativeAssetId: 'Parent',415        },416        TRANSFER_AMOUNT_RELAY,417      ],418    ];419420    const feeItem = 0;421422    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, {Unlimited: null});423424    balanceBobFinal = await helper.balance.getSubstrate(bob.address);425    console.log('[Relay (Westend) to Quartz] transaction fees: %s QTZ', balanceBobAfter - balanceBobFinal);426  });427});428429describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {430  let alice: IKeyringPair;431  let randomAccount: IKeyringPair;432433  let balanceQuartzTokenInit: bigint;434  let balanceQuartzTokenMiddle: bigint;435  let balanceQuartzTokenFinal: bigint;436  let balanceKaruraTokenInit: bigint;437  let balanceKaruraTokenMiddle: bigint;438  let balanceKaruraTokenFinal: bigint;439  let balanceQuartzForeignTokenInit: bigint;440  let balanceQuartzForeignTokenMiddle: bigint;441  let balanceQuartzForeignTokenFinal: bigint;442443  before(async () => {444    await usingPlaygrounds(async (helper, privateKey) => {445      alice = await privateKey('//Alice');446      [randomAccount] = await helper.arrange.createAccounts([0n], alice);447    });448449    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {450      const destination = {451        V0: {452          X2: [453            'Parent',454            {455              Parachain: QUARTZ_CHAIN,456            },457          ],458        },459      };460461      const metadata = {462        name: 'QTZ',463        symbol: 'QTZ',464        decimals: 18,465        minimalBalance: 1n,466      };467468      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);469      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);470      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);471      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});472    });473474    await usingPlaygrounds(async (helper) => {475      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);476      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);477    });478  });479480  itSub('Should connect and send QTZ to Karura', async ({helper}) => {481    const destination = {482      V0: {483        X2: [484          'Parent',485          {486            Parachain: KARURA_CHAIN,487          },488        ],489      },490    };491492    const beneficiary = {493      V0: {494        X1: {495          AccountId32: {496            network: 'Any',497            id: randomAccount.addressRaw,498          },499        },500      },501    };502503    const assets = {504      V1: [505        {506          id: {507            Concrete: {508              parents: 0,509              interior: 'Here',510            },511          },512          fun: {513            Fungible: TRANSFER_AMOUNT,514          },515        },516      ],517    };518519    const feeAssetItem = 0;520521    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});522    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);523524    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;525    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;526    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));527528    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {529      await helper.wait.newBlocks(3);530      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});531      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);532533      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;534      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;535536      console.log(537        '[Quartz -> Karura] transaction fees on Karura: %s KAR',538        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),539      );540      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));541      expect(karFees == 0n).to.be.true;542      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;543    });544  });545546  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {547    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {548      const destination = {549        V1: {550          parents: 1,551          interior: {552            X2: [553              {Parachain: QUARTZ_CHAIN},554              {555                AccountId32: {556                  network: 'Any',557                  id: randomAccount.addressRaw,558                },559              },560            ],561          },562        },563      };564565      const id = {566        ForeignAsset: 0,567      };568569      await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, {Unlimited: null});570      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);571      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);572573      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;574      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;575576      console.log(577        '[Karura -> Quartz] transaction fees on Karura: %s KAR',578        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),579      );580      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));581582      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;583      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;584    });585586    await helper.wait.newBlocks(3);587588    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);589    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;590    expect(actuallyDelivered > 0).to.be.true;591592    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));593594    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;595    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));596    expect(qtzFees == 0n).to.be.true;597  });598});599600// These tests are relevant only when the foreign asset pallet is disabled601describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {602  let alice: IKeyringPair;603604  before(async () => {605    await usingPlaygrounds(async (_helper, privateKey) => {606      alice = await privateKey('//Alice');607    });608  });609610  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {611    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {612      const destination = {613        V1: {614          parents: 1,615          interior: {616            X2: [617              {Parachain: QUARTZ_CHAIN},618              {619                AccountId32: {620                  network: 'Any',621                  id: alice.addressRaw,622                },623              },624            ],625          },626        },627      };628629      const id = {630        Token: 'KAR',631      };632633      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, {Unlimited: null});634    });635636    const maxWaitBlocks = 3;637638    const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');639640    expect(641      xcmpQueueFailEvent != null,642      '[Karura] xcmpQueue.FailEvent event is expected',643    ).to.be.true;644645    const event = xcmpQueueFailEvent!.event;646    const outcome = event.data[1] as XcmV2TraitsError;647648    expect(649      outcome.isFailedToTransactAsset,650      '[Karura] The XCM error should be `FailedToTransactAsset`',651    ).to.be.true;652  });653});654655describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {656  // Quartz constants657  let quartzDonor: IKeyringPair;658  let quartzAssetLocation;659660  let randomAccountQuartz: IKeyringPair;661  let randomAccountMoonriver: IKeyringPair;662663  // Moonriver constants664  let assetId: string;665666  const councilVotingThreshold = 2;667  const technicalCommitteeThreshold = 2;668  const votingPeriod = 3;669  const delayPeriod = 0;670671  const quartzAssetMetadata = {672    name: 'xcQuartz',673    symbol: 'xcQTZ',674    decimals: 18,675    isFrozen: false,676    minimalBalance: 1n,677  };678679  let balanceQuartzTokenInit: bigint;680  let balanceQuartzTokenMiddle: bigint;681  let balanceQuartzTokenFinal: bigint;682  let balanceForeignQtzTokenInit: bigint;683  let balanceForeignQtzTokenMiddle: bigint;684  let balanceForeignQtzTokenFinal: bigint;685  let balanceMovrTokenInit: bigint;686  let balanceMovrTokenMiddle: bigint;687  let balanceMovrTokenFinal: bigint;688689  before(async () => {690    await usingPlaygrounds(async (helper, privateKey) => {691      quartzDonor = await privateKey('//Alice');692      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);693694      balanceForeignQtzTokenInit = 0n;695    });696697    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {698      const alithAccount = helper.account.alithAccount();699      const baltatharAccount = helper.account.baltatharAccount();700      const dorothyAccount = helper.account.dorothyAccount();701702      randomAccountMoonriver = helper.account.create();703704      // >>> Sponsoring Dorothy >>>705      console.log('Sponsoring Dorothy.......');706      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);707      console.log('Sponsoring Dorothy.......DONE');708      // <<< Sponsoring Dorothy <<<709710      quartzAssetLocation = {711        XCM: {712          parents: 1,713          interior: {X1: {Parachain: QUARTZ_CHAIN}},714        },715      };716      const existentialDeposit = 1n;717      const isSufficient = true;718      const unitsPerSecond = 1n;719      const numAssetsWeightHint = 0;720721      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({722        location: quartzAssetLocation,723        metadata: quartzAssetMetadata,724        existentialDeposit,725        isSufficient,726        unitsPerSecond,727        numAssetsWeightHint,728      });729      const proposalHash = blake2AsHex(encodedProposal);730731      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);732      console.log('Encoded length %d', encodedProposal.length);733      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);734735      // >>> Note motion preimage >>>736      console.log('Note motion preimage.......');737      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);738      console.log('Note motion preimage.......DONE');739      // <<< Note motion preimage <<<740741      // >>> Propose external motion through council >>>742      console.log('Propose external motion through council.......');743      const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});744      const encodedMotion = externalMotion?.method.toHex() || '';745      const motionHash = blake2AsHex(encodedMotion);746      console.log('Motion hash is %s', motionHash);747748      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);749750      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;751      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);752      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);753754      await helper.collective.council.close(755        dorothyAccount,756        motionHash,757        councilProposalIdx,758        {759          refTime: 1_000_000_000,760          proofSize: 1_000_000,761        },762        externalMotion.encodedLength,763      );764      console.log('Propose external motion through council.......DONE');765      // <<< Propose external motion through council <<<766767      // >>> Fast track proposal through technical committee >>>768      console.log('Fast track proposal through technical committee.......');769      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);770      const encodedFastTrack = fastTrack?.method.toHex() || '';771      const fastTrackHash = blake2AsHex(encodedFastTrack);772      console.log('FastTrack hash is %s', fastTrackHash);773774      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);775776      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;777      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);778      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);779780      await helper.collective.techCommittee.close(781        baltatharAccount,782        fastTrackHash,783        techProposalIdx,784        {785          refTime: 1_000_000_000,786          proofSize: 1_000_000,787        },788        fastTrack.encodedLength,789      );790      console.log('Fast track proposal through technical committee.......DONE');791      // <<< Fast track proposal through technical committee <<<792793      // >>> Referendum voting >>>794      console.log('Referendum voting.......');795      await helper.democracy.referendumVote(dorothyAccount, 0, {796        balance: 10_000_000_000_000_000_000n,797        vote: {aye: true, conviction: 1},798      });799      console.log('Referendum voting.......DONE');800      // <<< Referendum voting <<<801802      // >>> Acquire Quartz AssetId Info on Moonriver >>>803      console.log('Acquire Quartz AssetId Info on Moonriver.......');804805      // Wait for the democracy execute806      await helper.wait.newBlocks(5);807808      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();809810      console.log('QTZ asset ID is %s', assetId);811      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');812      // >>> Acquire Quartz AssetId Info on Moonriver >>>813814      // >>> Sponsoring random Account >>>815      console.log('Sponsoring random Account.......');816      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);817      console.log('Sponsoring random Account.......DONE');818      // <<< Sponsoring random Account <<<819820      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);821    });822823    await usingPlaygrounds(async (helper) => {824      await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);825      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);826    });827  });828829  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {830    const currencyId = {831      NativeAssetId: 'Here',832    };833    const dest = {834      V1: {835        parents: 1,836        interior: {837          X2: [838            {Parachain: MOONRIVER_CHAIN},839            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},840          ],841        },842      },843    };844    const amount = TRANSFER_AMOUNT;845846    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, {Unlimited: null});847848    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);849    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;850851    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;852    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));853    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;854855    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {856      await helper.wait.newBlocks(3);857858      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);859860      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;861      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));862      expect(movrFees == 0n).to.be.true;863864      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);865      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;866      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));867      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;868    });869  });870871  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {872    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {873      const asset = {874        V1: {875          id: {876            Concrete: {877              parents: 1,878              interior: {879                X1: {Parachain: QUARTZ_CHAIN},880              },881            },882          },883          fun: {884            Fungible: TRANSFER_AMOUNT,885          },886        },887      };888      const destination = {889        V1: {890          parents: 1,891          interior: {892            X2: [893              {Parachain: QUARTZ_CHAIN},894              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},895            ],896          },897        },898      };899900      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, {Unlimited: null});901902      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);903904      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;905      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));906      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;907908      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);909910      expect(qtzRandomAccountAsset).to.be.null;911912      balanceForeignQtzTokenFinal = 0n;913914      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;915      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));916      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;917    });918919    await helper.wait.newBlocks(3);920921    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);922    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;923    expect(actuallyDelivered > 0).to.be.true;924925    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));926927    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;928    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));929    expect(qtzFees == 0n).to.be.true;930  });931});
after · tests/src/xcm/xcmQuartz.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds} from '../util';2223const QUARTZ_CHAIN = 2095;24const STATEMINE_CHAIN = 1000;25const KARURA_CHAIN = 2000;26const MOONRIVER_CHAIN = 2023;2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;3435const RELAY_DECIMALS = 12;36const STATEMINE_DECIMALS = 12;37const KARURA_DECIMALS = 12;3839const TRANSFER_AMOUNT = 2000000000000000000000000n;4041const FUNDING_AMOUNT = 3_500_000_0000_000_000n; 4243const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4445const USDT_ASSET_ID = 100;46const USDT_ASSET_METADATA_DECIMALS = 18;47const USDT_ASSET_METADATA_NAME = 'USDT';48const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';49const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;50const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5152describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {53  let alice: IKeyringPair;54  let bob: IKeyringPair;55  56  let balanceStmnBefore: bigint;57  let balanceStmnAfter: bigint;5859  let balanceQuartzBefore: bigint;60  let balanceQuartzAfter: bigint;61  let balanceQuartzFinal: bigint;6263  let balanceBobBefore: bigint;64  let balanceBobAfter: bigint;65  let balanceBobFinal: bigint;6667  let balanceBobRelayTokenBefore: bigint;68  let balanceBobRelayTokenAfter: bigint;697071  before(async () => {72    await usingPlaygrounds(async (_helper, privateKey) => {73      alice = await privateKey('//Alice');74      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor75    });7677    await usingRelayPlaygrounds(relayUrl, async (helper) => {78      // Fund accounts on Statemine(t)79      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);80      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);81    });8283    await usingStateminePlaygrounds(statemineUrl, async (helper) => {84      const sovereignFundingAmount = 3_500_000_000n; 8586      await helper.assets.create(87        alice,88        USDT_ASSET_ID,89        alice.address,90        USDT_ASSET_METADATA_MINIMAL_BALANCE,91      );92      await helper.assets.setMetadata(93        alice,94        USDT_ASSET_ID,95        USDT_ASSET_METADATA_NAME,96        USDT_ASSET_METADATA_DESCRIPTION,97        USDT_ASSET_METADATA_DECIMALS,98      );99      await helper.assets.mint(100        alice,101        USDT_ASSET_ID,102        alice.address,103        USDT_ASSET_AMOUNT,104      );105106      // funding parachain sovereing account on Statemine(t).107      // The sovereign account should be created before any action108      // (the assets pallet on Statemine(t) check if the sovereign account exists)109      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);110      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);111    });112113114    await usingPlaygrounds(async (helper) => {115      const location = {116        V1: {117          parents: 1,118          interior: {X3: [119            {120              Parachain: STATEMINE_CHAIN,121            },122            {123              PalletInstance: STATEMINE_PALLET_INSTANCE,124            },125            {126              GeneralIndex: USDT_ASSET_ID,127            },128          ]},129        },130      };131132      const metadata =133      {134        name: USDT_ASSET_ID,135        symbol: USDT_ASSET_METADATA_NAME,136        decimals: USDT_ASSET_METADATA_DECIMALS,137        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,138      };139      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);140      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);141    });142143144    // Providing the relay currency to the quartz sender account145    // (fee for USDT XCM are paid in relay tokens)146    await usingRelayPlaygrounds(relayUrl, async (helper) => {147      const destination = {148        V1: {149          parents: 0,150          interior: {X1: {151            Parachain: QUARTZ_CHAIN,152          },153          },154        }};155156      const beneficiary = {157        V1: {158          parents: 0,159          interior: {X1: {160            AccountId32: {161              network: 'Any',162              id: alice.addressRaw,163            },164          }},165        },166      };167168      const assets = {169        V1: [170          {171            id: {172              Concrete: {173                parents: 0,174                interior: 'Here',175              },176            },177            fun: {178              Fungible: TRANSFER_AMOUNT_RELAY,179            },180          },181        ],182      };183184      const feeAssetItem = 0;185186      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});187    });188  189  });190191  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {192    await usingStateminePlaygrounds(statemineUrl, async (helper) => {193      const dest = {194        V1: {195          parents: 1,196          interior: {X1: {197            Parachain: QUARTZ_CHAIN,198          },199          },200        }};201202      const beneficiary = {203        V1: {204          parents: 0,205          interior: {X1: {206            AccountId32: {207              network: 'Any',208              id: alice.addressRaw,209            },210          }},211        },212      };213214      const assets = {215        V1: [216          {217            id: {218              Concrete: {219                parents: 0,220                interior: {221                  X2: [222                    {223                      PalletInstance: STATEMINE_PALLET_INSTANCE,224                    },225                    {226                      GeneralIndex: USDT_ASSET_ID,227                    }, 228                  ]},229              },230            },231            fun: {232              Fungible: TRANSFER_AMOUNT,233            },234          },235        ],236      };237238      const feeAssetItem = 0;239240      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);241      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, {Unlimited: null});242243      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);244245      // common good parachain take commission in it native token246      console.log(247        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',248        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),249      );250      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;251252    });253254255    // ensure that asset has been delivered256    await helper.wait.newBlocks(3);257258    // expext collection id will be with id 1259    const free = await helper.ft.getBalance(1, {Substrate: alice.address});260261    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);262263    console.log(264      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',265      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),266    );267    console.log(268      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',269      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),270    );    271    // commission has not paid in USDT token272    expect(free).to.be.equal(TRANSFER_AMOUNT);273    // ... and parachain native token274    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;275  });276277  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {278    const destination = {279      V1: {280        parents: 1,281        interior: {X2: [282          {283            Parachain: STATEMINE_CHAIN,284          },285          {286            AccountId32: {287              network: 'Any',288              id: alice.addressRaw,289            },290          },291        ]},292      },293    };294295    const relayFee = 400_000_000_000_000n;296    const currencies: [any, bigint][] = [297      [298        {299          ForeignAssetId: 0,300        },301        TRANSFER_AMOUNT,302      ], 303      [304        {305          NativeAssetId: 'Parent',306        },307        relayFee,308      ],309    ];310311    const feeItem = 1;312313    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, {Unlimited: null});314    315    // the commission has been paid in parachain native token316    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);317    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzFinal - balanceQuartzAfter));318    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;319320    await usingStateminePlaygrounds(statemineUrl, async (helper) => {321      await helper.wait.newBlocks(3);322      323      // The USDT token never paid fees. Its amount not changed from begin value.324      // Also check that xcm transfer has been succeeded 325      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;326    });327  });328329  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {330    balanceBobBefore = await helper.balance.getSubstrate(bob.address);331    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});332333    await usingRelayPlaygrounds(relayUrl, async (helper) => {334      const destination = {335        V1: {336          parents: 0,337          interior: {X1: {338            Parachain: QUARTZ_CHAIN,339          },340          },341        }};342343      const beneficiary = {344        V1: {345          parents: 0,346          interior: {X1: {347            AccountId32: {348              network: 'Any',349              id: bob.addressRaw,350            },351          }},352        },353      };354355      const assets = {356        V1: [357          {358            id: {359              Concrete: {360                parents: 0,361                interior: 'Here',362              },363            },364            fun: {365              Fungible: TRANSFER_AMOUNT_RELAY,366            },367          },368        ],369      };370371      const feeAssetItem = 0;372373      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});374    });375  376    await helper.wait.newBlocks(3);377378    balanceBobAfter = await helper.balance.getSubstrate(bob.address);  379    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});380381    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;382    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;383    console.log(384      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',385      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),386    );387    console.log(388      '[Relay (Westend) -> Quartz] transaction fees: %s WND',389      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),390    );391    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);392    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;393    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;394  });395396  itSub('Should connect and send Relay token back', async ({helper}) => {397    let relayTokenBalanceBefore: bigint;398    let relayTokenBalanceAfter: bigint;399    await usingRelayPlaygrounds(relayUrl, async (helper) => {400      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);401    });402403    const destination = {404      V1: {405        parents: 1,406        interior: {407          X1:{408            AccountId32: {409              network: 'Any',410              id: bob.addressRaw,411            },412          },413        },414      },415    };416417    const currencies: any = [418      [419        {420          NativeAssetId: 'Parent',421        },422        TRANSFER_AMOUNT_RELAY,423      ],424    ];425426    const feeItem = 0;427428    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, {Unlimited: null});429430    balanceBobFinal = await helper.balance.getSubstrate(bob.address);431    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));432433    await usingRelayPlaygrounds(relayUrl, async (helper) => {434      await helper.wait.newBlocks(10);435      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);436437      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;438      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));439      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;440    });441  });442});443444describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {445  let alice: IKeyringPair;446  let randomAccount: IKeyringPair;447448  let balanceQuartzTokenInit: bigint;449  let balanceQuartzTokenMiddle: bigint;450  let balanceQuartzTokenFinal: bigint;451  let balanceKaruraTokenInit: bigint;452  let balanceKaruraTokenMiddle: bigint;453  let balanceKaruraTokenFinal: bigint;454  let balanceQuartzForeignTokenInit: bigint;455  let balanceQuartzForeignTokenMiddle: bigint;456  let balanceQuartzForeignTokenFinal: bigint;457458  before(async () => {459    await usingPlaygrounds(async (helper, privateKey) => {460      alice = await privateKey('//Alice');461      [randomAccount] = await helper.arrange.createAccounts([0n], alice);462    });463464    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {465      const destination = {466        V0: {467          X2: [468            'Parent',469            {470              Parachain: QUARTZ_CHAIN,471            },472          ],473        },474      };475476      const metadata = {477        name: 'QTZ',478        symbol: 'QTZ',479        decimals: 18,480        minimalBalance: 1n,481      };482483      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);484      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);485      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);486      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});487    });488489    await usingPlaygrounds(async (helper) => {490      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);491      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);492    });493  });494495  itSub('Should connect and send QTZ to Karura', async ({helper}) => {496    const destination = {497      V0: {498        X2: [499          'Parent',500          {501            Parachain: KARURA_CHAIN,502          },503        ],504      },505    };506507    const beneficiary = {508      V0: {509        X1: {510          AccountId32: {511            network: 'Any',512            id: randomAccount.addressRaw,513          },514        },515      },516    };517518    const assets = {519      V1: [520        {521          id: {522            Concrete: {523              parents: 0,524              interior: 'Here',525            },526          },527          fun: {528            Fungible: TRANSFER_AMOUNT,529          },530        },531      ],532    };533534    const feeAssetItem = 0;535536    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});537    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);538539    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;540    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;541    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));542543    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {544      await helper.wait.newBlocks(3);545      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});546      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);547548      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;549      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;550551      console.log(552        '[Quartz -> Karura] transaction fees on Karura: %s KAR',553        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),554      );555      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));556      expect(karFees == 0n).to.be.true;557      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;558    });559  });560561  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {562    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {563      const destination = {564        V1: {565          parents: 1,566          interior: {567            X2: [568              {Parachain: QUARTZ_CHAIN},569              {570                AccountId32: {571                  network: 'Any',572                  id: randomAccount.addressRaw,573                },574              },575            ],576          },577        },578      };579580      const id = {581        ForeignAsset: 0,582      };583584      await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, {Unlimited: null});585      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);586      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);587588      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;589      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;590591      console.log(592        '[Karura -> Quartz] transaction fees on Karura: %s KAR',593        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),594      );595      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));596597      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;598      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;599    });600601    await helper.wait.newBlocks(3);602603    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);604    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;605    expect(actuallyDelivered > 0).to.be.true;606607    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));608609    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;610    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));611    expect(qtzFees == 0n).to.be.true;612  });613});614615// These tests are relevant only when the foreign asset pallet is disabled616describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {617  let alice: IKeyringPair;618619  before(async () => {620    await usingPlaygrounds(async (_helper, privateKey) => {621      alice = await privateKey('//Alice');622    });623  });624625  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {626    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {627      const destination = {628        V1: {629          parents: 1,630          interior: {631            X2: [632              {Parachain: QUARTZ_CHAIN},633              {634                AccountId32: {635                  network: 'Any',636                  id: alice.addressRaw,637                },638              },639            ],640          },641        },642      };643644      const id = {645        Token: 'KAR',646      };647648      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, {Unlimited: null});649    });650651    const maxWaitBlocks = 3;652653    const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');654655    expect(656      xcmpQueueFailEvent != null,657      '[Karura] xcmpQueue.FailEvent event is expected',658    ).to.be.true;659660    const event = xcmpQueueFailEvent!.event;661    const outcome = event.data[1] as XcmV2TraitsError;662663    expect(664      outcome.isFailedToTransactAsset,665      '[Karura] The XCM error should be `FailedToTransactAsset`',666    ).to.be.true;667  });668});669670describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {671  // Quartz constants672  let quartzDonor: IKeyringPair;673  let quartzAssetLocation;674675  let randomAccountQuartz: IKeyringPair;676  let randomAccountMoonriver: IKeyringPair;677678  // Moonriver constants679  let assetId: string;680681  const councilVotingThreshold = 2;682  const technicalCommitteeThreshold = 2;683  const votingPeriod = 3;684  const delayPeriod = 0;685686  const quartzAssetMetadata = {687    name: 'xcQuartz',688    symbol: 'xcQTZ',689    decimals: 18,690    isFrozen: false,691    minimalBalance: 1n,692  };693694  let balanceQuartzTokenInit: bigint;695  let balanceQuartzTokenMiddle: bigint;696  let balanceQuartzTokenFinal: bigint;697  let balanceForeignQtzTokenInit: bigint;698  let balanceForeignQtzTokenMiddle: bigint;699  let balanceForeignQtzTokenFinal: bigint;700  let balanceMovrTokenInit: bigint;701  let balanceMovrTokenMiddle: bigint;702  let balanceMovrTokenFinal: bigint;703704  before(async () => {705    await usingPlaygrounds(async (helper, privateKey) => {706      quartzDonor = await privateKey('//Alice');707      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);708709      balanceForeignQtzTokenInit = 0n;710    });711712    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {713      const alithAccount = helper.account.alithAccount();714      const baltatharAccount = helper.account.baltatharAccount();715      const dorothyAccount = helper.account.dorothyAccount();716717      randomAccountMoonriver = helper.account.create();718719      // >>> Sponsoring Dorothy >>>720      console.log('Sponsoring Dorothy.......');721      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);722      console.log('Sponsoring Dorothy.......DONE');723      // <<< Sponsoring Dorothy <<<724725      quartzAssetLocation = {726        XCM: {727          parents: 1,728          interior: {X1: {Parachain: QUARTZ_CHAIN}},729        },730      };731      const existentialDeposit = 1n;732      const isSufficient = true;733      const unitsPerSecond = 1n;734      const numAssetsWeightHint = 0;735736      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({737        location: quartzAssetLocation,738        metadata: quartzAssetMetadata,739        existentialDeposit,740        isSufficient,741        unitsPerSecond,742        numAssetsWeightHint,743      });744      const proposalHash = blake2AsHex(encodedProposal);745746      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);747      console.log('Encoded length %d', encodedProposal.length);748      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);749750      // >>> Note motion preimage >>>751      console.log('Note motion preimage.......');752      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);753      console.log('Note motion preimage.......DONE');754      // <<< Note motion preimage <<<755756      // >>> Propose external motion through council >>>757      console.log('Propose external motion through council.......');758      const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});759      const encodedMotion = externalMotion?.method.toHex() || '';760      const motionHash = blake2AsHex(encodedMotion);761      console.log('Motion hash is %s', motionHash);762763      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);764765      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;766      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);767      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);768769      await helper.collective.council.close(770        dorothyAccount,771        motionHash,772        councilProposalIdx,773        {774          refTime: 1_000_000_000,775          proofSize: 1_000_000,776        },777        externalMotion.encodedLength,778      );779      console.log('Propose external motion through council.......DONE');780      // <<< Propose external motion through council <<<781782      // >>> Fast track proposal through technical committee >>>783      console.log('Fast track proposal through technical committee.......');784      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);785      const encodedFastTrack = fastTrack?.method.toHex() || '';786      const fastTrackHash = blake2AsHex(encodedFastTrack);787      console.log('FastTrack hash is %s', fastTrackHash);788789      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);790791      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;792      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);793      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);794795      await helper.collective.techCommittee.close(796        baltatharAccount,797        fastTrackHash,798        techProposalIdx,799        {800          refTime: 1_000_000_000,801          proofSize: 1_000_000,802        },803        fastTrack.encodedLength,804      );805      console.log('Fast track proposal through technical committee.......DONE');806      // <<< Fast track proposal through technical committee <<<807808      // >>> Referendum voting >>>809      console.log('Referendum voting.......');810      await helper.democracy.referendumVote(dorothyAccount, 0, {811        balance: 10_000_000_000_000_000_000n,812        vote: {aye: true, conviction: 1},813      });814      console.log('Referendum voting.......DONE');815      // <<< Referendum voting <<<816817      // >>> Acquire Quartz AssetId Info on Moonriver >>>818      console.log('Acquire Quartz AssetId Info on Moonriver.......');819820      // Wait for the democracy execute821      await helper.wait.newBlocks(5);822823      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();824825      console.log('QTZ asset ID is %s', assetId);826      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');827      // >>> Acquire Quartz AssetId Info on Moonriver >>>828829      // >>> Sponsoring random Account >>>830      console.log('Sponsoring random Account.......');831      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);832      console.log('Sponsoring random Account.......DONE');833      // <<< Sponsoring random Account <<<834835      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);836    });837838    await usingPlaygrounds(async (helper) => {839      await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);840      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);841    });842  });843844  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {845    const currencyId = {846      NativeAssetId: 'Here',847    };848    const dest = {849      V1: {850        parents: 1,851        interior: {852          X2: [853            {Parachain: MOONRIVER_CHAIN},854            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},855          ],856        },857      },858    };859    const amount = TRANSFER_AMOUNT;860861    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, {Unlimited: null});862863    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);864    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;865866    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;867    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));868    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;869870    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {871      await helper.wait.newBlocks(3);872873      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);874875      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;876      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));877      expect(movrFees == 0n).to.be.true;878879      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);880      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;881      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));882      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;883    });884  });885886  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {887    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {888      const asset = {889        V1: {890          id: {891            Concrete: {892              parents: 1,893              interior: {894                X1: {Parachain: QUARTZ_CHAIN},895              },896            },897          },898          fun: {899            Fungible: TRANSFER_AMOUNT,900          },901        },902      };903      const destination = {904        V1: {905          parents: 1,906          interior: {907            X2: [908              {Parachain: QUARTZ_CHAIN},909              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},910            ],911          },912        },913      };914915      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, {Unlimited: null});916917      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);918919      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;920      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));921      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;922923      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);924925      expect(qtzRandomAccountAsset).to.be.null;926927      balanceForeignQtzTokenFinal = 0n;928929      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;930      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));931      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;932    });933934    await helper.wait.newBlocks(3);935936    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);937    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;938    expect(actuallyDelivered > 0).to.be.true;939940    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));941942    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;943    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));944    expect(qtzFees == 0n).to.be.true;945  });946});