git.delta.rocks / unique-network / refs/commits / 1550f8cc5eba

difftreelog

Add Quartz-Shiden tests

Max Andreev2023-03-29parent: #955fd21.patch.diff
in: master
+ fix send-back test

2 files 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 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;5152const SAFE_XCM_VERSION = 2;5354describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {55  let alice: IKeyringPair;56  let bob: IKeyringPair;5758  let balanceStmnBefore: bigint;59  let balanceStmnAfter: bigint;6061  let balanceQuartzBefore: bigint;62  let balanceQuartzAfter: bigint;63  let balanceQuartzFinal: bigint;6465  let balanceBobBefore: bigint;66  let balanceBobAfter: bigint;67  let balanceBobFinal: bigint;6869  let balanceBobRelayTokenBefore: bigint;70  let balanceBobRelayTokenAfter: bigint;717273  before(async () => {74    await usingPlaygrounds(async (helper, privateKey) => {75      alice = await privateKey('//Alice');76      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor7778      // Set the default version to wrap the first message to other chains.79      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);80    });8182    await usingRelayPlaygrounds(relayUrl, async (helper) => {83      // Fund accounts on Statemine(t)84      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);85      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);86    });8788    await usingStateminePlaygrounds(statemineUrl, async (helper) => {89      const sovereignFundingAmount = 3_500_000_000n;9091      await helper.assets.create(92        alice,93        USDT_ASSET_ID,94        alice.address,95        USDT_ASSET_METADATA_MINIMAL_BALANCE,96      );97      await helper.assets.setMetadata(98        alice,99        USDT_ASSET_ID,100        USDT_ASSET_METADATA_NAME,101        USDT_ASSET_METADATA_DESCRIPTION,102        USDT_ASSET_METADATA_DECIMALS,103      );104      await helper.assets.mint(105        alice,106        USDT_ASSET_ID,107        alice.address,108        USDT_ASSET_AMOUNT,109      );110111      // funding parachain sovereing account on Statemine(t).112      // The sovereign account should be created before any action113      // (the assets pallet on Statemine(t) check if the sovereign account exists)114      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);115      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);116    });117118119    await usingPlaygrounds(async (helper) => {120      const location = {121        V2: {122          parents: 1,123          interior: {X3: [124            {125              Parachain: STATEMINE_CHAIN,126            },127            {128              PalletInstance: STATEMINE_PALLET_INSTANCE,129            },130            {131              GeneralIndex: USDT_ASSET_ID,132            },133          ]},134        },135      };136137      const metadata =138      {139        name: USDT_ASSET_ID,140        symbol: USDT_ASSET_METADATA_NAME,141        decimals: USDT_ASSET_METADATA_DECIMALS,142        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,143      };144      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);145      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);146    });147148149    // Providing the relay currency to the quartz sender account150    // (fee for USDT XCM are paid in relay tokens)151    await usingRelayPlaygrounds(relayUrl, async (helper) => {152      const destination = {153        V1: {154          parents: 0,155          interior: {X1: {156            Parachain: QUARTZ_CHAIN,157          },158          },159        }};160161      const beneficiary = {162        V1: {163          parents: 0,164          interior: {X1: {165            AccountId32: {166              network: 'Any',167              id: alice.addressRaw,168            },169          }},170        },171      };172173      const assets = {174        V1: [175          {176            id: {177              Concrete: {178                parents: 0,179                interior: 'Here',180              },181            },182            fun: {183              Fungible: TRANSFER_AMOUNT_RELAY,184            },185          },186        ],187      };188189      const feeAssetItem = 0;190191      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');192    });193194  });195196  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {197    await usingStateminePlaygrounds(statemineUrl, async (helper) => {198      const dest = {199        V1: {200          parents: 1,201          interior: {X1: {202            Parachain: QUARTZ_CHAIN,203          },204          },205        }};206207      const beneficiary = {208        V1: {209          parents: 0,210          interior: {X1: {211            AccountId32: {212              network: 'Any',213              id: alice.addressRaw,214            },215          }},216        },217      };218219      const assets = {220        V1: [221          {222            id: {223              Concrete: {224                parents: 0,225                interior: {226                  X2: [227                    {228                      PalletInstance: STATEMINE_PALLET_INSTANCE,229                    },230                    {231                      GeneralIndex: USDT_ASSET_ID,232                    },233                  ]},234              },235            },236            fun: {237              Fungible: TRANSFER_AMOUNT,238            },239          },240        ],241      };242243      const feeAssetItem = 0;244245      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);246      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');247248      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);249250      // common good parachain take commission in it native token251      console.log(252        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',253        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),254      );255      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;256257    });258259260    // ensure that asset has been delivered261    await helper.wait.newBlocks(3);262263    // expext collection id will be with id 1264    const free = await helper.ft.getBalance(1, {Substrate: alice.address});265266    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);267268    console.log(269      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',270      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),271    );272    console.log(273      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',274      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),275    );276    // commission has not paid in USDT token277    expect(free).to.be.equal(TRANSFER_AMOUNT);278    // ... and parachain native token279    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;280  });281282  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {283    const destination = {284      V2: {285        parents: 1,286        interior: {X2: [287          {288            Parachain: STATEMINE_CHAIN,289          },290          {291            AccountId32: {292              network: 'Any',293              id: alice.addressRaw,294            },295          },296        ]},297      },298    };299300    const relayFee = 400_000_000_000_000n;301    const currencies: [any, bigint][] = [302      [303        {304          ForeignAssetId: 0,305        },306        TRANSFER_AMOUNT,307      ],308      [309        {310          NativeAssetId: 'Parent',311        },312        relayFee,313      ],314    ];315316    const feeItem = 1;317318    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');319320    // the commission has been paid in parachain native token321    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);322    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));323    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;324325    await usingStateminePlaygrounds(statemineUrl, async (helper) => {326      await helper.wait.newBlocks(3);327328      // The USDT token never paid fees. Its amount not changed from begin value.329      // Also check that xcm transfer has been succeeded330      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;331    });332  });333334  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {335    balanceBobBefore = await helper.balance.getSubstrate(bob.address);336    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});337338    await usingRelayPlaygrounds(relayUrl, async (helper) => {339      const destination = {340        V1: {341          parents: 0,342          interior: {X1: {343            Parachain: QUARTZ_CHAIN,344          },345          },346        }};347348      const beneficiary = {349        V1: {350          parents: 0,351          interior: {X1: {352            AccountId32: {353              network: 'Any',354              id: bob.addressRaw,355            },356          }},357        },358      };359360      const assets = {361        V1: [362          {363            id: {364              Concrete: {365                parents: 0,366                interior: 'Here',367              },368            },369            fun: {370              Fungible: TRANSFER_AMOUNT_RELAY,371            },372          },373        ],374      };375376      const feeAssetItem = 0;377378      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');379    });380381    await helper.wait.newBlocks(3);382383    balanceBobAfter = await helper.balance.getSubstrate(bob.address);384    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});385386    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;387    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;388    console.log(389      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',390      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),391    );392    console.log(393      '[Relay (Westend) -> Quartz] transaction fees: %s WND',394      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),395    );396    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);397    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;398    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;399  });400401  itSub('Should connect and send Relay token back', async ({helper}) => {402    let relayTokenBalanceBefore: bigint;403    let relayTokenBalanceAfter: bigint;404    await usingRelayPlaygrounds(relayUrl, async (helper) => {405      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);406    });407408    const destination = {409      V2: {410        parents: 1,411        interior: {412          X1:{413            AccountId32: {414              network: 'Any',415              id: bob.addressRaw,416            },417          },418        },419      },420    };421422    const currencies: any = [423      [424        {425          NativeAssetId: 'Parent',426        },427        TRANSFER_AMOUNT_RELAY,428      ],429    ];430431    const feeItem = 0;432433    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');434435    balanceBobFinal = await helper.balance.getSubstrate(bob.address);436    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));437438    await usingRelayPlaygrounds(relayUrl, async (helper) => {439      await helper.wait.newBlocks(10);440      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);441442      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;443      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));444      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;445    });446  });447});448449describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {450  let alice: IKeyringPair;451  let randomAccount: IKeyringPair;452453  let balanceQuartzTokenInit: bigint;454  let balanceQuartzTokenMiddle: bigint;455  let balanceQuartzTokenFinal: bigint;456  let balanceKaruraTokenInit: bigint;457  let balanceKaruraTokenMiddle: bigint;458  let balanceKaruraTokenFinal: bigint;459  let balanceQuartzForeignTokenInit: bigint;460  let balanceQuartzForeignTokenMiddle: bigint;461  let balanceQuartzForeignTokenFinal: bigint;462463  // computed by a test transfer from prod Quartz to prod Karura.464  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9465  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)466  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;467468  const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;469470  before(async () => {471    await usingPlaygrounds(async (helper, privateKey) => {472      alice = await privateKey('//Alice');473      [randomAccount] = await helper.arrange.createAccounts([0n], alice);474475      // Set the default version to wrap the first message to other chains.476      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);477    });478479    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {480      const destination = {481        V1: {482          parents: 1,483          interior: {484            X1: {485              Parachain: QUARTZ_CHAIN,486            },487          },488        },489      };490491      const metadata = {492        name: 'Quartz',493        symbol: 'QTZ',494        decimals: 18,495        minimalBalance: 1000000000000000000n,496      };497498      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);499      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);500      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);501      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});502    });503504    await usingPlaygrounds(async (helper) => {505      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);506      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);507    });508  });509510  itSub('Should connect and send QTZ to Karura', async ({helper}) => {511    const destination = {512      V2: {513        parents: 1,514        interior: {515          X1: {516            Parachain: KARURA_CHAIN,517          },518        },519      },520    };521522    const beneficiary = {523      V2: {524        parents: 0,525        interior: {526          X1: {527            AccountId32: {528              network: 'Any',529              id: randomAccount.addressRaw,530            },531          },532        },533      },534    };535536    const assets = {537      V2: [538        {539          id: {540            Concrete: {541              parents: 0,542              interior: 'Here',543            },544          },545          fun: {546            Fungible: TRANSFER_AMOUNT,547          },548        },549      ],550    };551552    const feeAssetItem = 0;553554    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');555    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);556557    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;558    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;559    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));560561    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {562      await helper.wait.newBlocks(3);563564      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});565      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);566567      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;568      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;569      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;570571      console.log(572        '[Quartz -> Karura] transaction fees on Karura: %s KAR',573        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),574      );575      console.log(576        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',577        helper.util.bigIntToDecimals(karUnqFees),578      );579      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));580      expect(karFees == 0n).to.be.true;581      expect(582        karUnqFees == expectedKaruraIncomeFee,583        'Karura took different income fee, check the Karura foreign asset config',584      ).to.be.true;585    });586  });587588  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {589    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {590      const destination = {591        V1: {592          parents: 1,593          interior: {594            X2: [595              {Parachain: QUARTZ_CHAIN},596              {597                AccountId32: {598                  network: 'Any',599                  id: randomAccount.addressRaw,600                },601              },602            ],603          },604        },605      };606607      const id = {608        ForeignAsset: 0,609      };610611      await helper.xTokens.transfer(randomAccount, id, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');612      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);613      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);614615      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;616      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;617618      console.log(619        '[Karura -> Quartz] transaction fees on Karura: %s KAR',620        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),621      );622      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));623624      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;625      expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;626    });627628    await helper.wait.newBlocks(3);629630    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);631    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;632    expect(actuallyDelivered > 0).to.be.true;633634    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));635636    const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;637    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));638    expect(qtzFees == 0n).to.be.true;639  });640});641642// These tests are relevant only when the foreign asset pallet is disabled643describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {644  let alice: IKeyringPair;645646  before(async () => {647    await usingPlaygrounds(async (helper, privateKey) => {648      alice = await privateKey('//Alice');649650      // Set the default version to wrap the first message to other chains.651      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);652    });653  });654655  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {656    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {657      const destination = {658        V1: {659          parents: 1,660          interior: {661            X2: [662              {Parachain: QUARTZ_CHAIN},663              {664                AccountId32: {665                  network: 'Any',666                  id: alice.addressRaw,667                },668              },669            ],670          },671        },672      };673674      const id = {675        Token: 'KAR',676      };677678      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, 'Unlimited');679    });680681    const maxWaitBlocks = 3;682683    const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');684685    expect(686      xcmpQueueFailEvent != null,687      '[Karura] xcmpQueue.FailEvent event is expected',688    ).to.be.true;689690    const event = xcmpQueueFailEvent!.event;691    const outcome = event.data[1] as XcmV2TraitsError;692693    expect(694      outcome.isFailedToTransactAsset,695      '[Karura] The XCM error should be `FailedToTransactAsset`',696    ).to.be.true;697  });698});699700describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {701  // Quartz constants702  let quartzDonor: IKeyringPair;703  let quartzAssetLocation;704705  let randomAccountQuartz: IKeyringPair;706  let randomAccountMoonriver: IKeyringPair;707708  // Moonriver constants709  let assetId: string;710711  const councilVotingThreshold = 2;712  const technicalCommitteeThreshold = 2;713  const votingPeriod = 3;714  const delayPeriod = 0;715716  const quartzAssetMetadata = {717    name: 'xcQuartz',718    symbol: 'xcQTZ',719    decimals: 18,720    isFrozen: false,721    minimalBalance: 1n,722  };723724  let balanceQuartzTokenInit: bigint;725  let balanceQuartzTokenMiddle: bigint;726  let balanceQuartzTokenFinal: bigint;727  let balanceForeignQtzTokenInit: bigint;728  let balanceForeignQtzTokenMiddle: bigint;729  let balanceForeignQtzTokenFinal: bigint;730  let balanceMovrTokenInit: bigint;731  let balanceMovrTokenMiddle: bigint;732  let balanceMovrTokenFinal: bigint;733734  before(async () => {735    await usingPlaygrounds(async (helper, privateKey) => {736      quartzDonor = await privateKey('//Alice');737      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);738739      balanceForeignQtzTokenInit = 0n;740      741      // Set the default version to wrap the first message to other chains.742      const alice = quartzDonor;743      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);744    });745746    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {747      const alithAccount = helper.account.alithAccount();748      const baltatharAccount = helper.account.baltatharAccount();749      const dorothyAccount = helper.account.dorothyAccount();750751      randomAccountMoonriver = helper.account.create();752753      // >>> Sponsoring Dorothy >>>754      console.log('Sponsoring Dorothy.......');755      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);756      console.log('Sponsoring Dorothy.......DONE');757      // <<< Sponsoring Dorothy <<<758759      quartzAssetLocation = {760        XCM: {761          parents: 1,762          interior: {X1: {Parachain: QUARTZ_CHAIN}},763        },764      };765      const existentialDeposit = 1n;766      const isSufficient = true;767      const unitsPerSecond = 1n;768      const numAssetsWeightHint = 0;769770      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({771        location: quartzAssetLocation,772        metadata: quartzAssetMetadata,773        existentialDeposit,774        isSufficient,775        unitsPerSecond,776        numAssetsWeightHint,777      });778      const proposalHash = blake2AsHex(encodedProposal);779780      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);781      console.log('Encoded length %d', encodedProposal.length);782      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);783784      // >>> Note motion preimage >>>785      console.log('Note motion preimage.......');786      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);787      console.log('Note motion preimage.......DONE');788      // <<< Note motion preimage <<<789790      // >>> Propose external motion through council >>>791      console.log('Propose external motion through council.......');792      const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});793      const encodedMotion = externalMotion?.method.toHex() || '';794      const motionHash = blake2AsHex(encodedMotion);795      console.log('Motion hash is %s', motionHash);796797      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);798799      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;800      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);801      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);802803      await helper.collective.council.close(804        dorothyAccount,805        motionHash,806        councilProposalIdx,807        {808          refTime: 1_000_000_000,809          proofSize: 1_000_000,810        },811        externalMotion.encodedLength,812      );813      console.log('Propose external motion through council.......DONE');814      // <<< Propose external motion through council <<<815816      // >>> Fast track proposal through technical committee >>>817      console.log('Fast track proposal through technical committee.......');818      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);819      const encodedFastTrack = fastTrack?.method.toHex() || '';820      const fastTrackHash = blake2AsHex(encodedFastTrack);821      console.log('FastTrack hash is %s', fastTrackHash);822823      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);824825      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;826      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);827      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);828829      await helper.collective.techCommittee.close(830        baltatharAccount,831        fastTrackHash,832        techProposalIdx,833        {834          refTime: 1_000_000_000,835          proofSize: 1_000_000,836        },837        fastTrack.encodedLength,838      );839      console.log('Fast track proposal through technical committee.......DONE');840      // <<< Fast track proposal through technical committee <<<841842      // >>> Referendum voting >>>843      console.log('Referendum voting.......');844      await helper.democracy.referendumVote(dorothyAccount, 0, {845        balance: 10_000_000_000_000_000_000n,846        vote: {aye: true, conviction: 1},847      });848      console.log('Referendum voting.......DONE');849      // <<< Referendum voting <<<850851      // >>> Acquire Quartz AssetId Info on Moonriver >>>852      console.log('Acquire Quartz AssetId Info on Moonriver.......');853854      // Wait for the democracy execute855      await helper.wait.newBlocks(5);856857      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();858859      console.log('QTZ asset ID is %s', assetId);860      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');861      // >>> Acquire Quartz AssetId Info on Moonriver >>>862863      // >>> Sponsoring random Account >>>864      console.log('Sponsoring random Account.......');865      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);866      console.log('Sponsoring random Account.......DONE');867      // <<< Sponsoring random Account <<<868869      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);870    });871872    await usingPlaygrounds(async (helper) => {873      await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);874      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);875    });876  });877878  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {879    const currencyId = {880      NativeAssetId: 'Here',881    };882    const dest = {883      V2: {884        parents: 1,885        interior: {886          X2: [887            {Parachain: MOONRIVER_CHAIN},888            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},889          ],890        },891      },892    };893    const amount = TRANSFER_AMOUNT;894895    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');896897    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);898    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;899900    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;901    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));902    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;903904    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {905      await helper.wait.newBlocks(3);906907      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);908909      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;910      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));911      expect(movrFees == 0n).to.be.true;912913      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);914      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;915      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));916      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;917    });918  });919920  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {921    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {922      const asset = {923        V1: {924          id: {925            Concrete: {926              parents: 1,927              interior: {928                X1: {Parachain: QUARTZ_CHAIN},929              },930            },931          },932          fun: {933            Fungible: TRANSFER_AMOUNT,934          },935        },936      };937      const destination = {938        V1: {939          parents: 1,940          interior: {941            X2: [942              {Parachain: QUARTZ_CHAIN},943              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},944            ],945          },946        },947      };948949      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');950951      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);952953      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;954      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));955      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;956957      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);958959      expect(qtzRandomAccountAsset).to.be.null;960961      balanceForeignQtzTokenFinal = 0n;962963      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;964      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));965      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;966    });967968    await helper.wait.newBlocks(3);969970    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);971    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;972    expect(actuallyDelivered > 0).to.be.true;973974    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));975976    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;977    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));978    expect(qtzFees == 0n).to.be.true;979  });980});
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, usingAstarPlaygrounds} from '../util';2223const QUARTZ_CHAIN = 2095;24const STATEMINE_CHAIN = 1000;25const KARURA_CHAIN = 2000;26const MOONRIVER_CHAIN = 2023;27const SHIDEN_CHAIN = 2007;2829const STATEMINE_PALLET_INSTANCE = 50;3031const relayUrl = config.relayUrl;32const statemineUrl = config.statemineUrl;33const karuraUrl = config.karuraUrl;34const moonriverUrl = config.moonriverUrl;35const shidenUrl = config.shidenUrl;3637const RELAY_DECIMALS = 12;38const STATEMINE_DECIMALS = 12;39const KARURA_DECIMALS = 12;4041const TRANSFER_AMOUNT = 2000000000000000000000000n;4243const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4445const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4647const USDT_ASSET_ID = 100;48const USDT_ASSET_METADATA_DECIMALS = 18;49const USDT_ASSET_METADATA_NAME = 'USDT';50const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';51const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;52const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5354const SAFE_XCM_VERSION = 2;5556describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {57  let alice: IKeyringPair;58  let bob: IKeyringPair;5960  let balanceStmnBefore: bigint;61  let balanceStmnAfter: bigint;6263  let balanceQuartzBefore: bigint;64  let balanceQuartzAfter: bigint;65  let balanceQuartzFinal: bigint;6667  let balanceBobBefore: bigint;68  let balanceBobAfter: bigint;69  let balanceBobFinal: bigint;7071  let balanceBobRelayTokenBefore: bigint;72  let balanceBobRelayTokenAfter: bigint;737475  before(async () => {76    await usingPlaygrounds(async (helper, privateKey) => {77      alice = await privateKey('//Alice');78      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor7980      // Set the default version to wrap the first message to other chains.81      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);82    });8384    await usingRelayPlaygrounds(relayUrl, async (helper) => {85      // Fund accounts on Statemine(t)86      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);87      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);88    });8990    await usingStateminePlaygrounds(statemineUrl, async (helper) => {91      const sovereignFundingAmount = 3_500_000_000n;9293      await helper.assets.create(94        alice,95        USDT_ASSET_ID,96        alice.address,97        USDT_ASSET_METADATA_MINIMAL_BALANCE,98      );99      await helper.assets.setMetadata(100        alice,101        USDT_ASSET_ID,102        USDT_ASSET_METADATA_NAME,103        USDT_ASSET_METADATA_DESCRIPTION,104        USDT_ASSET_METADATA_DECIMALS,105      );106      await helper.assets.mint(107        alice,108        USDT_ASSET_ID,109        alice.address,110        USDT_ASSET_AMOUNT,111      );112113      // funding parachain sovereing account on Statemine(t).114      // The sovereign account should be created before any action115      // (the assets pallet on Statemine(t) check if the sovereign account exists)116      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);117      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);118    });119120121    await usingPlaygrounds(async (helper) => {122      const location = {123        V2: {124          parents: 1,125          interior: {X3: [126            {127              Parachain: STATEMINE_CHAIN,128            },129            {130              PalletInstance: STATEMINE_PALLET_INSTANCE,131            },132            {133              GeneralIndex: USDT_ASSET_ID,134            },135          ]},136        },137      };138139      const metadata =140      {141        name: USDT_ASSET_ID,142        symbol: USDT_ASSET_METADATA_NAME,143        decimals: USDT_ASSET_METADATA_DECIMALS,144        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,145      };146      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);147      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);148    });149150151    // Providing the relay currency to the quartz sender account152    // (fee for USDT XCM are paid in relay tokens)153    await usingRelayPlaygrounds(relayUrl, async (helper) => {154      const destination = {155        V1: {156          parents: 0,157          interior: {X1: {158            Parachain: QUARTZ_CHAIN,159          },160          },161        }};162163      const beneficiary = {164        V1: {165          parents: 0,166          interior: {X1: {167            AccountId32: {168              network: 'Any',169              id: alice.addressRaw,170            },171          }},172        },173      };174175      const assets = {176        V1: [177          {178            id: {179              Concrete: {180                parents: 0,181                interior: 'Here',182              },183            },184            fun: {185              Fungible: TRANSFER_AMOUNT_RELAY,186            },187          },188        ],189      };190191      const feeAssetItem = 0;192193      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');194    });195196  });197198  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {199    await usingStateminePlaygrounds(statemineUrl, async (helper) => {200      const dest = {201        V1: {202          parents: 1,203          interior: {X1: {204            Parachain: QUARTZ_CHAIN,205          },206          },207        }};208209      const beneficiary = {210        V1: {211          parents: 0,212          interior: {X1: {213            AccountId32: {214              network: 'Any',215              id: alice.addressRaw,216            },217          }},218        },219      };220221      const assets = {222        V1: [223          {224            id: {225              Concrete: {226                parents: 0,227                interior: {228                  X2: [229                    {230                      PalletInstance: STATEMINE_PALLET_INSTANCE,231                    },232                    {233                      GeneralIndex: USDT_ASSET_ID,234                    },235                  ]},236              },237            },238            fun: {239              Fungible: TRANSFER_AMOUNT,240            },241          },242        ],243      };244245      const feeAssetItem = 0;246247      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);248      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');249250      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);251252      // common good parachain take commission in it native token253      console.log(254        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',255        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),256      );257      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;258259    });260261262    // ensure that asset has been delivered263    await helper.wait.newBlocks(3);264265    // expext collection id will be with id 1266    const free = await helper.ft.getBalance(1, {Substrate: alice.address});267268    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);269270    console.log(271      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',272      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),273    );274    console.log(275      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',276      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),277    );278    // commission has not paid in USDT token279    expect(free).to.be.equal(TRANSFER_AMOUNT);280    // ... and parachain native token281    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;282  });283284  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {285    const destination = {286      V2: {287        parents: 1,288        interior: {X2: [289          {290            Parachain: STATEMINE_CHAIN,291          },292          {293            AccountId32: {294              network: 'Any',295              id: alice.addressRaw,296            },297          },298        ]},299      },300    };301302    const relayFee = 400_000_000_000_000n;303    const currencies: [any, bigint][] = [304      [305        {306          ForeignAssetId: 0,307        },308        TRANSFER_AMOUNT,309      ],310      [311        {312          NativeAssetId: 'Parent',313        },314        relayFee,315      ],316    ];317318    const feeItem = 1;319320    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');321322    // the commission has been paid in parachain native token323    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);324    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));325    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;326327    await usingStateminePlaygrounds(statemineUrl, async (helper) => {328      await helper.wait.newBlocks(3);329330      // The USDT token never paid fees. Its amount not changed from begin value.331      // Also check that xcm transfer has been succeeded332      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;333    });334  });335336  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {337    balanceBobBefore = await helper.balance.getSubstrate(bob.address);338    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});339340    await usingRelayPlaygrounds(relayUrl, async (helper) => {341      const destination = {342        V1: {343          parents: 0,344          interior: {X1: {345            Parachain: QUARTZ_CHAIN,346          },347          },348        }};349350      const beneficiary = {351        V1: {352          parents: 0,353          interior: {X1: {354            AccountId32: {355              network: 'Any',356              id: bob.addressRaw,357            },358          }},359        },360      };361362      const assets = {363        V1: [364          {365            id: {366              Concrete: {367                parents: 0,368                interior: 'Here',369              },370            },371            fun: {372              Fungible: TRANSFER_AMOUNT_RELAY,373            },374          },375        ],376      };377378      const feeAssetItem = 0;379380      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');381    });382383    await helper.wait.newBlocks(3);384385    balanceBobAfter = await helper.balance.getSubstrate(bob.address);386    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});387388    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;389    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;390    console.log(391      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',392      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),393    );394    console.log(395      '[Relay (Westend) -> Quartz] transaction fees: %s WND',396      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),397    );398    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);399    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;400    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;401  });402403  itSub('Should connect and send Relay token back', async ({helper}) => {404    let relayTokenBalanceBefore: bigint;405    let relayTokenBalanceAfter: bigint;406    await usingRelayPlaygrounds(relayUrl, async (helper) => {407      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);408    });409410    const destination = {411      V2: {412        parents: 1,413        interior: {414          X1:{415            AccountId32: {416              network: 'Any',417              id: bob.addressRaw,418            },419          },420        },421      },422    };423424    const currencies: any = [425      [426        {427          NativeAssetId: 'Parent',428        },429        TRANSFER_AMOUNT_RELAY,430      ],431    ];432433    const feeItem = 0;434435    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');436437    balanceBobFinal = await helper.balance.getSubstrate(bob.address);438    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));439440    await usingRelayPlaygrounds(relayUrl, async (helper) => {441      await helper.wait.newBlocks(10);442      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);443444      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;445      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));446      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;447    });448  });449});450451describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {452  let alice: IKeyringPair;453  let randomAccount: IKeyringPair;454455  let balanceQuartzTokenInit: bigint;456  let balanceQuartzTokenMiddle: bigint;457  let balanceQuartzTokenFinal: bigint;458  let balanceKaruraTokenInit: bigint;459  let balanceKaruraTokenMiddle: bigint;460  let balanceKaruraTokenFinal: bigint;461  let balanceQuartzForeignTokenInit: bigint;462  let balanceQuartzForeignTokenMiddle: bigint;463  let balanceQuartzForeignTokenFinal: bigint;464465  // computed by a test transfer from prod Quartz to prod Karura.466  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9467  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)468  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;469470  const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;471472  before(async () => {473    await usingPlaygrounds(async (helper, privateKey) => {474      alice = await privateKey('//Alice');475      [randomAccount] = await helper.arrange.createAccounts([0n], alice);476477      // Set the default version to wrap the first message to other chains.478      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);479    });480481    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {482      const destination = {483        V1: {484          parents: 1,485          interior: {486            X1: {487              Parachain: QUARTZ_CHAIN,488            },489          },490        },491      };492493      const metadata = {494        name: 'Quartz',495        symbol: 'QTZ',496        decimals: 18,497        minimalBalance: 1000000000000000000n,498      };499500      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);501      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);502      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);503      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});504    });505506    await usingPlaygrounds(async (helper) => {507      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);508      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);509    });510  });511512  itSub('Should connect and send QTZ to Karura', async ({helper}) => {513    const destination = {514      V2: {515        parents: 1,516        interior: {517          X1: {518            Parachain: KARURA_CHAIN,519          },520        },521      },522    };523524    const beneficiary = {525      V2: {526        parents: 0,527        interior: {528          X1: {529            AccountId32: {530              network: 'Any',531              id: randomAccount.addressRaw,532            },533          },534        },535      },536    };537538    const assets = {539      V2: [540        {541          id: {542            Concrete: {543              parents: 0,544              interior: 'Here',545            },546          },547          fun: {548            Fungible: TRANSFER_AMOUNT,549          },550        },551      ],552    };553554    const feeAssetItem = 0;555556    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');557    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);558559    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;560    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;561    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));562563    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {564      await helper.wait.newBlocks(3);565566      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});567      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);568569      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;570      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;571      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;572573      console.log(574        '[Quartz -> Karura] transaction fees on Karura: %s KAR',575        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),576      );577      console.log(578        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',579        helper.util.bigIntToDecimals(karUnqFees),580      );581      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));582      expect(karFees == 0n).to.be.true;583      expect(584        karUnqFees == expectedKaruraIncomeFee,585        'Karura took different income fee, check the Karura foreign asset config',586      ).to.be.true;587    });588  });589590  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {591    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {592      const destination = {593        V1: {594          parents: 1,595          interior: {596            X2: [597              {Parachain: QUARTZ_CHAIN},598              {599                AccountId32: {600                  network: 'Any',601                  id: randomAccount.addressRaw,602                },603              },604            ],605          },606        },607      };608609      const id = {610        ForeignAsset: 0,611      };612613      await helper.xTokens.transfer(randomAccount, id, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');614      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);615      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);616617      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;618      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;619620      console.log(621        '[Karura -> Quartz] transaction fees on Karura: %s KAR',622        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),623      );624      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));625626      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;627      expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;628    });629630    await helper.wait.newBlocks(3);631632    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);633    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;634    expect(actuallyDelivered > 0).to.be.true;635636    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));637638    const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;639    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));640    expect(qtzFees == 0n).to.be.true;641  });642});643644// These tests are relevant only when the foreign asset pallet is disabled645describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {646  let alice: IKeyringPair;647648  before(async () => {649    await usingPlaygrounds(async (helper, privateKey) => {650      alice = await privateKey('//Alice');651652      // Set the default version to wrap the first message to other chains.653      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);654    });655  });656657  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {658    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {659      const destination = {660        V1: {661          parents: 1,662          interior: {663            X2: [664              {Parachain: QUARTZ_CHAIN},665              {666                AccountId32: {667                  network: 'Any',668                  id: alice.addressRaw,669                },670              },671            ],672          },673        },674      };675676      const id = {677        Token: 'KAR',678      };679680      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, 'Unlimited');681    });682683    const maxWaitBlocks = 3;684685    const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');686687    expect(688      xcmpQueueFailEvent != null,689      '[Karura] xcmpQueue.FailEvent event is expected',690    ).to.be.true;691692    const event = xcmpQueueFailEvent!.event;693    const outcome = event.data[1] as XcmV2TraitsError;694695    expect(696      outcome.isFailedToTransactAsset,697      '[Karura] The XCM error should be `FailedToTransactAsset`',698    ).to.be.true;699  });700});701702describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {703  // Quartz constants704  let quartzDonor: IKeyringPair;705  let quartzAssetLocation;706707  let randomAccountQuartz: IKeyringPair;708  let randomAccountMoonriver: IKeyringPair;709710  // Moonriver constants711  let assetId: string;712713  const councilVotingThreshold = 2;714  const technicalCommitteeThreshold = 2;715  const votingPeriod = 3;716  const delayPeriod = 0;717718  const quartzAssetMetadata = {719    name: 'xcQuartz',720    symbol: 'xcQTZ',721    decimals: 18,722    isFrozen: false,723    minimalBalance: 1n,724  };725726  let balanceQuartzTokenInit: bigint;727  let balanceQuartzTokenMiddle: bigint;728  let balanceQuartzTokenFinal: bigint;729  let balanceForeignQtzTokenInit: bigint;730  let balanceForeignQtzTokenMiddle: bigint;731  let balanceForeignQtzTokenFinal: bigint;732  let balanceMovrTokenInit: bigint;733  let balanceMovrTokenMiddle: bigint;734  let balanceMovrTokenFinal: bigint;735736  before(async () => {737    await usingPlaygrounds(async (helper, privateKey) => {738      quartzDonor = await privateKey('//Alice');739      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);740741      balanceForeignQtzTokenInit = 0n;742      743      // Set the default version to wrap the first message to other chains.744      const alice = quartzDonor;745      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);746    });747748    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {749      const alithAccount = helper.account.alithAccount();750      const baltatharAccount = helper.account.baltatharAccount();751      const dorothyAccount = helper.account.dorothyAccount();752753      randomAccountMoonriver = helper.account.create();754755      // >>> Sponsoring Dorothy >>>756      console.log('Sponsoring Dorothy.......');757      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);758      console.log('Sponsoring Dorothy.......DONE');759      // <<< Sponsoring Dorothy <<<760761      quartzAssetLocation = {762        XCM: {763          parents: 1,764          interior: {X1: {Parachain: QUARTZ_CHAIN}},765        },766      };767      const existentialDeposit = 1n;768      const isSufficient = true;769      const unitsPerSecond = 1n;770      const numAssetsWeightHint = 0;771772      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({773        location: quartzAssetLocation,774        metadata: quartzAssetMetadata,775        existentialDeposit,776        isSufficient,777        unitsPerSecond,778        numAssetsWeightHint,779      });780      const proposalHash = blake2AsHex(encodedProposal);781782      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);783      console.log('Encoded length %d', encodedProposal.length);784      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);785786      // >>> Note motion preimage >>>787      console.log('Note motion preimage.......');788      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);789      console.log('Note motion preimage.......DONE');790      // <<< Note motion preimage <<<791792      // >>> Propose external motion through council >>>793      console.log('Propose external motion through council.......');794      const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});795      const encodedMotion = externalMotion?.method.toHex() || '';796      const motionHash = blake2AsHex(encodedMotion);797      console.log('Motion hash is %s', motionHash);798799      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);800801      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;802      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);803      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);804805      await helper.collective.council.close(806        dorothyAccount,807        motionHash,808        councilProposalIdx,809        {810          refTime: 1_000_000_000,811          proofSize: 1_000_000,812        },813        externalMotion.encodedLength,814      );815      console.log('Propose external motion through council.......DONE');816      // <<< Propose external motion through council <<<817818      // >>> Fast track proposal through technical committee >>>819      console.log('Fast track proposal through technical committee.......');820      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);821      const encodedFastTrack = fastTrack?.method.toHex() || '';822      const fastTrackHash = blake2AsHex(encodedFastTrack);823      console.log('FastTrack hash is %s', fastTrackHash);824825      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);826827      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;828      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);829      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);830831      await helper.collective.techCommittee.close(832        baltatharAccount,833        fastTrackHash,834        techProposalIdx,835        {836          refTime: 1_000_000_000,837          proofSize: 1_000_000,838        },839        fastTrack.encodedLength,840      );841      console.log('Fast track proposal through technical committee.......DONE');842      // <<< Fast track proposal through technical committee <<<843844      // >>> Referendum voting >>>845      console.log('Referendum voting.......');846      await helper.democracy.referendumVote(dorothyAccount, 0, {847        balance: 10_000_000_000_000_000_000n,848        vote: {aye: true, conviction: 1},849      });850      console.log('Referendum voting.......DONE');851      // <<< Referendum voting <<<852853      // >>> Acquire Quartz AssetId Info on Moonriver >>>854      console.log('Acquire Quartz AssetId Info on Moonriver.......');855856      // Wait for the democracy execute857      await helper.wait.newBlocks(5);858859      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();860861      console.log('QTZ asset ID is %s', assetId);862      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');863      // >>> Acquire Quartz AssetId Info on Moonriver >>>864865      // >>> Sponsoring random Account >>>866      console.log('Sponsoring random Account.......');867      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);868      console.log('Sponsoring random Account.......DONE');869      // <<< Sponsoring random Account <<<870871      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);872    });873874    await usingPlaygrounds(async (helper) => {875      await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);876      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);877    });878  });879880  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {881    const currencyId = {882      NativeAssetId: 'Here',883    };884    const dest = {885      V2: {886        parents: 1,887        interior: {888          X2: [889            {Parachain: MOONRIVER_CHAIN},890            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},891          ],892        },893      },894    };895    const amount = TRANSFER_AMOUNT;896897    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');898899    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);900    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;901902    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;903    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));904    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;905906    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {907      await helper.wait.newBlocks(3);908909      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);910911      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;912      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));913      expect(movrFees == 0n).to.be.true;914915      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);916      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;917      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));918      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;919    });920  });921922  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {923    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {924      const asset = {925        V1: {926          id: {927            Concrete: {928              parents: 1,929              interior: {930                X1: {Parachain: QUARTZ_CHAIN},931              },932            },933          },934          fun: {935            Fungible: TRANSFER_AMOUNT,936          },937        },938      };939      const destination = {940        V1: {941          parents: 1,942          interior: {943            X2: [944              {Parachain: QUARTZ_CHAIN},945              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},946            ],947          },948        },949      };950951      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');952953      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);954955      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;956      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));957      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;958959      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);960961      expect(qtzRandomAccountAsset).to.be.null;962963      balanceForeignQtzTokenFinal = 0n;964965      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;966      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));967      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;968    });969970    await helper.wait.newBlocks(3);971972    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);973    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;974    expect(actuallyDelivered > 0).to.be.true;975976    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));977978    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;979    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));980    expect(qtzFees == 0n).to.be.true;981  });982});983984describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {985  let alice: IKeyringPair;986  let randomAccount: IKeyringPair;987988  const shidenInitialBalance = 1n * (10n ** 18n);989  const qtzToShidenAmount = 10n * (10n ** 18n);990991  before(async () => {992    await usingPlaygrounds(async (helper, privateKey) => {993      alice = await privateKey('//Alice');994      [randomAccount] = await helper.arrange.createAccounts([100n], alice);995      console.log('randomAccount', randomAccount.address);996    });997998    await usingAstarPlaygrounds(shidenUrl, async (helper) => {999      console.log('1. Create foreign asset and metadata');1000      await helper.assets.create(1001        alice,1002        1,1003        alice.address,1004        1n,1005      );10061007      await helper.assets.setMetadata(1008        alice,1009        1,1010        'Cross chain QTZ',1011        'xcQTZ',1012        18,1013      );10141015      console.log('2. Register asset location');1016      const assetLocation = {1017        V1: {1018          parents: 1,1019          interior: {1020            X1: {1021              Parachain: QUARTZ_CHAIN,1022            },1023          },1024        },1025      };10261027      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, 1]);10281029      console.log('3. Set payment for computation');1030      // TODO this is Phala's price, what price will be for Unique?1031      const unitsPerSecond = 228_000_000_000n;1032      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);10331034      console.log('4. Transfer 1 SDN to recepient');1035      await helper.balance.transferToSubstrate(alice, randomAccount.address, shidenInitialBalance);1036    });1037  });10381039  itSub.only('Should connect and send QTZ to Shiden', async ({helper}) => {1040    const destination = {1041      V1: {1042        parents: 1,1043        interior: {1044          X1: {1045            Parachain: SHIDEN_CHAIN,1046          },1047        },1048      },1049    };10501051    const beneficiary = {1052      V1: {1053        parents: 0,1054        interior: {1055          X1: {1056            AccountId32: {1057              network: 'Any',1058              id: randomAccount.addressRaw,1059            },1060          },1061        },1062      },1063    };10641065    const assets = {1066      V1: [1067        {1068          id: {1069            Concrete: {1070              parents: 0,1071              interior: 'Here',1072            },1073          },1074          fun: {1075            Fungible: qtzToShidenAmount,1076          },1077        },1078      ],1079    };10801081    // Initial balance is 100 UNQ1082    expect(await helper.balance.getSubstrate(randomAccount.address)).to.eq(100n * (10n ** 18n));10831084    const feeAssetItem = 0;1085    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');10861087    // Balance after reserve transfer is less than 901088    expect(await helper.balance.getSubstrate(randomAccount.address)).to.eq(89_941967662676666465n);10891090    await usingAstarPlaygrounds(shidenUrl, async (helper) => {1091      await helper.wait.newBlocks(3);1092      const xcUNQbalance = await helper.assets.account(1, randomAccount.address);1093      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);10941095      expect(xcUNQbalance).to.eq(9_999_999_999_088_000_000n);1096      // Astar balance does not changed1097      expect(astarBalance).to.eq(1_000_000_000_000_000_000n);1098    });1099  });11001101  itSub.only('Should connect to Shiden and send QTZ back', async ({helper}) => {1102    await usingAstarPlaygrounds(shidenUrl, async (helper) => {1103      const destination = {1104        V1: {1105          parents: 1,1106          interior: {1107            X1: {1108              Parachain: QUARTZ_CHAIN,1109            },1110          },1111        },1112      };11131114      const beneficiary = {1115        V1: {1116          parents: 0,1117          interior: {1118            X1: {1119              AccountId32: {1120                network: 'Any',1121                id: randomAccount.addressRaw,1122              },1123            },1124          },1125        },1126      };11271128      const assets = {1129        V1: [1130          {1131            id: {1132              Concrete: {1133                parents: 1,1134                interior: {1135                  X1: {1136                    Parachain: QUARTZ_CHAIN,1137                  },1138                },1139              },1140            },1141            fun: {1142              Fungible: 5_000_000_000_000_000_000n,1143            },1144          },1145        ],1146      };11471148      // Initial balance is 1 SDN1149      expect(await helper.balance.getSubstrate(randomAccount.address)).to.eq(1_000_000_000_000_000_000n);11501151      const feeAssetItem = 0;1152      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);11531154      // Balance after reserve transfer is less than 1 SDN1155      const xcUNQbalance = await helper.assets.account(1, randomAccount.address);1156      const balanceSDN = await helper.balance.getSubstrate(randomAccount.address);11571158      // Assert: xcQTZ balance decreased1159      expect(xcUNQbalance).to.eq(4_999_999_999_088_000_000n);1160      // Assert: SDN balance is 0.996...1161      expect(balanceSDN / (10n ** 15n)).to.eq(996n);1162    });11631164    await helper.wait.newBlocks(3);1165    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1166    expect(balanceUNQ).to.eq(89_941967662676666465n + 5_000_000_000_000_000_000n);1167  });1168});
modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -1003,7 +1003,7 @@
         alice,
         1,
         alice.address,
-        1n, // TODO set correct minimal balance
+        1n,
       );
 
       await helper.assets.setMetadata(
@@ -1033,7 +1033,7 @@
       const unitsPerSecond = 228_000_000_000n;
       await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
 
-      console.log('4. Transfer 1 ASTAR for recepient');
+      console.log('4. Transfer 1 ASTR to recepient');
       await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);
     });
   });
@@ -1141,6 +1141,73 @@
               },
             },
             fun: {
+              Fungible: 5_000_000_000_000_000_000n,
+            },
+          },
+        ],
+      };
+
+      // Initial balance is 1 ASTR
+      expect(await helper.balance.getSubstrate(randomAccount.address)).to.eq(1_000_000_000_000_000_000n);
+
+      const feeAssetItem = 0;
+      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);
+
+      const xcUNQbalance = await helper.assets.account(1, randomAccount.address);
+      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);
+
+      // Assert: xcUNQ balance decreased
+      expect(xcUNQbalance).to.eq(4_999_999_999_088_000_000n);
+      // Assert: ASTR balance is 0.996...
+      expect(balanceAstar / (10n ** 15n)).to.eq(996n);
+    });
+
+    await helper.wait.newBlocks(3);
+    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);
+    expect(balanceUNQ).to.eq(89_941967662676666465n + 5_000_000_000_000_000_000n);
+  });
+
+  itSub.skip('Should not accept limitedReserveTransfer of UNQ from ASTAR', async ({helper}) => {
+    await usingAstarPlaygrounds(astarUrl, async (helper) => {
+      const destination = {
+        V1: {
+          parents: 1,
+          interior: {
+            X1: {
+              Parachain: UNIQUE_CHAIN,
+            },
+          },
+        },
+      };
+
+      const beneficiary = {
+        V1: {
+          parents: 0,
+          interior: {
+            X1: {
+              AccountId32: {
+                network: 'Any',
+                id: randomAccount.addressRaw,
+              },
+            },
+          },
+        },
+      };
+
+      const assets = {
+        V1: [
+          {
+            id: {
+              Concrete: {
+                parents: 1,
+                interior: {
+                  X1: {
+                    Parachain: UNIQUE_CHAIN,
+                  },
+                },
+              },
+            },
+            fun: {
               Fungible: 5_000_000_000_000_000_000n, // TODO set another value
             },
           },