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

difftreelog

ci fix codestyle failures

Yaroslav Bolyukin2023-09-08parent: #dfae293.patch.diff
in: master

4 files changed

modified.github/workflows/codestyle.ymldiffbeforeafterboth
--- a/.github/workflows/codestyle.yml
+++ b/.github/workflows/codestyle.yml
@@ -44,7 +44,8 @@
       - name: Install modules
         run: cd tests && yarn
       - name: Run ESLint
-        run: cd tests && yarn eslint --ext .ts,.js --max-warnings=0 src/
+        # run: cd tests && yarn eslint --ext .ts,.js --max-warnings=0 src/
+        run: cd tests && yarn eslint --ext .ts,.js src/
 
   clippy:
     runs-on: [ self-hosted-ci ]
modifiednode/rpc/src/lib.rsdiffbeforeafterboth
--- a/node/rpc/src/lib.rs
+++ b/node/rpc/src/lib.rs
@@ -162,7 +162,7 @@
 
 	io.merge(Unique::new(client.clone()).into_rpc())?;
 
-	io.merge(AppPromotion::new(client.clone()).into_rpc())?;
+	io.merge(AppPromotion::new(client).into_rpc())?;
 
 	#[cfg(feature = "pov-estimate")]
 	io.merge(
modifiedtests/src/util/globalSetup.tsdiffbeforeafterboth
--- a/tests/src/util/globalSetup.ts
+++ b/tests/src/util/globalSetup.ts
@@ -115,5 +115,5 @@
 globalSetup().catch(e => {
   console.error('Setup error');
   console.error(e);
-  process.exit(1)
+  process.exit(1);
 });
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 config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';2122const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);23const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);24const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);25const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);26const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;34const shidenUrl = config.shidenUrl;3536const RELAY_DECIMALS = 12;37const STATEMINE_DECIMALS = 12;38const KARURA_DECIMALS = 12;39const SHIDEN_DECIMALS = 18n;40const QTZ_DECIMALS = 18n;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4546const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4748const USDT_ASSET_ID = 100;49const USDT_ASSET_METADATA_DECIMALS = 18;50const USDT_ASSET_METADATA_NAME = 'USDT';51const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';52const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;53const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5455const SAFE_XCM_VERSION = 2;5657describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {58  let alice: IKeyringPair;59  let bob: IKeyringPair;6061  let balanceStmnBefore: bigint;62  let balanceStmnAfter: bigint;6364  let balanceQuartzBefore: bigint;65  let balanceQuartzAfter: bigint;66  let balanceQuartzFinal: bigint;6768  let balanceBobBefore: bigint;69  let balanceBobAfter: bigint;70  let balanceBobFinal: bigint;7172  let balanceBobRelayTokenBefore: bigint;73  let balanceBobRelayTokenAfter: bigint;747576  before(async () => {77    await usingPlaygrounds(async (helper, privateKey) => {78      alice = await privateKey('//Alice');79      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor8081      // Set the default version to wrap the first message to other chains.82      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);83    });8485    await usingRelayPlaygrounds(relayUrl, async (helper) => {86      // Fund accounts on Statemine(t)87      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);88      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);89    });9091    await usingStateminePlaygrounds(statemineUrl, async (helper) => {92      const sovereignFundingAmount = 3_500_000_000n;9394      await helper.assets.create(95        alice,96        USDT_ASSET_ID,97        alice.address,98        USDT_ASSET_METADATA_MINIMAL_BALANCE,99      );100      await helper.assets.setMetadata(101        alice,102        USDT_ASSET_ID,103        USDT_ASSET_METADATA_NAME,104        USDT_ASSET_METADATA_DESCRIPTION,105        USDT_ASSET_METADATA_DECIMALS,106      );107      await helper.assets.mint(108        alice,109        USDT_ASSET_ID,110        alice.address,111        USDT_ASSET_AMOUNT,112      );113114      // funding parachain sovereing account on Statemine(t).115      // The sovereign account should be created before any action116      // (the assets pallet on Statemine(t) check if the sovereign account exists)117      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);118      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);119    });120121122    await usingPlaygrounds(async (helper) => {123      const location = {124        V2: {125          parents: 1,126          interior: {X3: [127            {128              Parachain: STATEMINE_CHAIN,129            },130            {131              PalletInstance: STATEMINE_PALLET_INSTANCE,132            },133            {134              GeneralIndex: USDT_ASSET_ID,135            },136          ]},137        },138      };139140      const metadata =141      {142        name: USDT_ASSET_ID,143        symbol: USDT_ASSET_METADATA_NAME,144        decimals: USDT_ASSET_METADATA_DECIMALS,145        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,146      };147      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);148      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);149    });150151152    // Providing the relay currency to the quartz sender account153    // (fee for USDT XCM are paid in relay tokens)154    await usingRelayPlaygrounds(relayUrl, async (helper) => {155      const destination = {156        V2: {157          parents: 0,158          interior: {X1: {159            Parachain: QUARTZ_CHAIN,160          },161          },162        }};163164      const beneficiary = {165        V2: {166          parents: 0,167          interior: {X1: {168            AccountId32: {169              network: 'Any',170              id: alice.addressRaw,171            },172          }},173        },174      };175176      const assets = {177        V2: [178          {179            id: {180              Concrete: {181                parents: 0,182                interior: 'Here',183              },184            },185            fun: {186              Fungible: TRANSFER_AMOUNT_RELAY,187            },188          },189        ],190      };191192      const feeAssetItem = 0;193194      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');195    });196197  });198199  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {200    await usingStateminePlaygrounds(statemineUrl, async (helper) => {201      const dest = {202        V2: {203          parents: 1,204          interior: {X1: {205            Parachain: QUARTZ_CHAIN,206          },207          },208        }};209210      const beneficiary = {211        V2: {212          parents: 0,213          interior: {X1: {214            AccountId32: {215              network: 'Any',216              id: alice.addressRaw,217            },218          }},219        },220      };221222      const assets = {223        V2: [224          {225            id: {226              Concrete: {227                parents: 0,228                interior: {229                  X2: [230                    {231                      PalletInstance: STATEMINE_PALLET_INSTANCE,232                    },233                    {234                      GeneralIndex: USDT_ASSET_ID,235                    },236                  ]},237              },238            },239            fun: {240              Fungible: TRANSFER_AMOUNT,241            },242          },243        ],244      };245246      const feeAssetItem = 0;247248      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);249      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');250251      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);252253      // common good parachain take commission in it native token254      console.log(255        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',256        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),257      );258      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;259260    });261262263    // ensure that asset has been delivered264    await helper.wait.newBlocks(3);265266    // expext collection id will be with id 1267    const free = await helper.ft.getBalance(1, {Substrate: alice.address});268269    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);270271    console.log(272      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',273      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),274    );275    console.log(276      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',277      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),278    );279    // commission has not paid in USDT token280    expect(free).to.be.equal(TRANSFER_AMOUNT);281    // ... and parachain native token282    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;283  });284285  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {286    const destination = {287      V2: {288        parents: 1,289        interior: {X2: [290          {291            Parachain: STATEMINE_CHAIN,292          },293          {294            AccountId32: {295              network: 'Any',296              id: alice.addressRaw,297            },298          },299        ]},300      },301    };302303    const relayFee = 400_000_000_000_000n;304    const currencies: [any, bigint][] = [305      [306        {307          ForeignAssetId: 0,308        },309        TRANSFER_AMOUNT,310      ],311      [312        {313          NativeAssetId: 'Parent',314        },315        relayFee,316      ],317    ];318319    const feeItem = 1;320321    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');322323    // the commission has been paid in parachain native token324    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);325    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));326    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;327328    await usingStateminePlaygrounds(statemineUrl, async (helper) => {329      await helper.wait.newBlocks(3);330331      // The USDT token never paid fees. Its amount not changed from begin value.332      // Also check that xcm transfer has been succeeded333      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;334    });335  });336337  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {338    balanceBobBefore = await helper.balance.getSubstrate(bob.address);339    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});340341    await usingRelayPlaygrounds(relayUrl, async (helper) => {342      const destination = {343        V2: {344          parents: 0,345          interior: {X1: {346            Parachain: QUARTZ_CHAIN,347          },348          },349        }};350351      const beneficiary = {352        V2: {353          parents: 0,354          interior: {X1: {355            AccountId32: {356              network: 'Any',357              id: bob.addressRaw,358            },359          }},360        },361      };362363      const assets = {364        V2: [365          {366            id: {367              Concrete: {368                parents: 0,369                interior: 'Here',370              },371            },372            fun: {373              Fungible: TRANSFER_AMOUNT_RELAY,374            },375          },376        ],377      };378379      const feeAssetItem = 0;380381      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');382    });383384    await helper.wait.newBlocks(3);385386    balanceBobAfter = await helper.balance.getSubstrate(bob.address);387    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});388389    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;390    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;391    console.log(392      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',393      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),394    );395    console.log(396      '[Relay (Westend) -> Quartz] transaction fees: %s WND',397      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),398    );399    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);400    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;401    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;402  });403404  itSub('Should connect and send Relay token back', async ({helper}) => {405    let relayTokenBalanceBefore: bigint;406    let relayTokenBalanceAfter: bigint;407    await usingRelayPlaygrounds(relayUrl, async (helper) => {408      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);409    });410411    const destination = {412      V2: {413        parents: 1,414        interior: {415          X1:{416            AccountId32: {417              network: 'Any',418              id: bob.addressRaw,419            },420          },421        },422      },423    };424425    const currencies: any = [426      [427        {428          NativeAssetId: 'Parent',429        },430        TRANSFER_AMOUNT_RELAY,431      ],432    ];433434    const feeItem = 0;435436    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438    balanceBobFinal = await helper.balance.getSubstrate(bob.address);439    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441    await usingRelayPlaygrounds(relayUrl, async (helper) => {442      await helper.wait.newBlocks(10);443      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448    });449  });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {453  let alice: IKeyringPair;454  let randomAccount: IKeyringPair;455456  let balanceQuartzTokenInit: bigint;457  let balanceQuartzTokenMiddle: bigint;458  let balanceQuartzTokenFinal: bigint;459  let balanceKaruraTokenInit: bigint;460  let balanceKaruraTokenMiddle: bigint;461  let balanceKaruraTokenFinal: bigint;462  let balanceQuartzForeignTokenInit: bigint;463  let balanceQuartzForeignTokenMiddle: bigint;464  let balanceQuartzForeignTokenFinal: bigint;465466  // computed by a test transfer from prod Quartz to prod Karura.467  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9468  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)469  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;470  const karuraEps = 8n * 10n ** 16n;471472  let karuraBackwardTransferAmount: bigint;473474  before(async () => {475    await usingPlaygrounds(async (helper, privateKey) => {476      alice = await privateKey('//Alice');477      [randomAccount] = await helper.arrange.createAccounts([0n], alice);478479      // Set the default version to wrap the first message to other chains.480      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481    });482483    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {484      const destination = {485        V2: {486          parents: 1,487          interior: {488            X1: {489              Parachain: QUARTZ_CHAIN,490            },491          },492        },493      };494495      const metadata = {496        name: 'Quartz',497        symbol: 'QTZ',498        decimals: 18,499        minimalBalance: 1000000000000000000n,500      };501502      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);503      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);504      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);505      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});506    });507508    await usingPlaygrounds(async (helper) => {509      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);510      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);511    });512  });513514  itSub('Should connect and send QTZ to Karura', async ({helper}) => {515    const destination = {516      V2: {517        parents: 1,518        interior: {519          X1: {520            Parachain: KARURA_CHAIN,521          },522        },523      },524    };525526    const beneficiary = {527      V2: {528        parents: 0,529        interior: {530          X1: {531            AccountId32: {532              network: 'Any',533              id: randomAccount.addressRaw,534            },535          },536        },537      },538    };539540    const assets = {541      V2: [542        {543          id: {544            Concrete: {545              parents: 0,546              interior: 'Here',547            },548          },549          fun: {550            Fungible: TRANSFER_AMOUNT,551          },552        },553      ],554    };555556    const feeAssetItem = 0;557558    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');559    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);560561    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;562    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;563    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));564565    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {566      await helper.wait.newBlocks(3);567568      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});569      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);570571      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;572      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;573      karuraBackwardTransferAmount = qtzIncomeTransfer;574575      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;576577      console.log(578        '[Quartz -> Karura] transaction fees on Karura: %s KAR',579        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),580      );581      console.log(582        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',583        helper.util.bigIntToDecimals(karUnqFees),584      );585      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));586      expect(karFees == 0n).to.be.true;587588      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;589590      expect(591        bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,592        'Karura took different income fee, check the Karura foreign asset config',593      ).to.be.true;594    });595  });596597  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {598    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {599      const destination = {600        V2: {601          parents: 1,602          interior: {603            X2: [604              {Parachain: QUARTZ_CHAIN},605              {606                AccountId32: {607                  network: 'Any',608                  id: randomAccount.addressRaw,609                },610              },611            ],612          },613        },614      };615616      const id = {617        ForeignAsset: 0,618      };619620      await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited');621      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);622      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);623624      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;625      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;626627      console.log(628        '[Karura -> Quartz] transaction fees on Karura: %s KAR',629        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),630      );631      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));632633      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;634      expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true;635    });636637    await helper.wait.newBlocks(3);638639    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);640    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;641    expect(actuallyDelivered > 0).to.be.true;642643    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));644645    const qtzFees = karuraBackwardTransferAmount - actuallyDelivered;646    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));647    expect(qtzFees == 0n).to.be.true;648  });649650  itSub('Karura can send only up to its balance', async ({helper}) => {651    // set Karura's sovereign account's balance652    const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);653    const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);654    await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);655656    const moreThanKaruraHas = karuraBalance * 2n;657658    let targetAccountBalance = 0n;659    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);660661    const quartzMultilocation = {662      V2: {663        parents: 1,664        interior: {665          X1: {Parachain: QUARTZ_CHAIN},666        },667      },668    };669670    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(671      targetAccount.addressRaw,672      {673        Concrete: {674          parents: 0,675          interior: 'Here',676        },677      },678      moreThanKaruraHas,679    );680681    let maliciousXcmProgramSent: any;682    const maxWaitBlocks = 5;683684    // Try to trick Quartz685    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {686      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);687688      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);689    });690691    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {692      return event.messageHash == maliciousXcmProgramSent.messageHash693        && event.outcome.isFailedToTransactAsset;694    });695696    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);697    expect(targetAccountBalance).to.be.equal(0n);698699    // But Karura still can send the correct amount700    const validTransferAmount = karuraBalance / 2n;701    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(702      targetAccount.addressRaw,703      {704        Concrete: {705          parents: 0,706          interior: 'Here',707        },708      },709      validTransferAmount,710    );711712    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {713      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);714    });715716    await helper.wait.newBlocks(maxWaitBlocks);717718    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);719    expect(targetAccountBalance).to.be.equal(validTransferAmount);720  });721722  itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {723    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);724    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);725726    const quartzMultilocation = {727      V2: {728        parents: 1,729        interior: {730          X1: {731            Parachain: QUARTZ_CHAIN,732          },733        },734      },735    };736737    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(738      targetAccount.addressRaw,739      {740        Concrete: {741          parents: 1,742          interior: {743            X1: {744              Parachain: QUARTZ_CHAIN,745            },746          },747        },748      },749      testAmount,750    );751752    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(753      targetAccount.addressRaw,754      {755        Concrete: {756          parents: 0,757          interior: 'Here',758        },759      },760      testAmount,761    );762763    let maliciousXcmProgramFullIdSent: any;764    let maliciousXcmProgramHereIdSent: any;765    const maxWaitBlocks = 3;766767    // Try to trick Quartz using full QTZ identification768    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {769      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);770771      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);772    });773774    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash775        && event.outcome.isUntrustedReserveLocation);776777    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);778    expect(accountBalance).to.be.equal(0n);779780    // Try to trick Quartz using shortened QTZ identification781    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {782      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);783784      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);785    });786787    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash788        && event.outcome.isUntrustedReserveLocation);789790    accountBalance = await helper.balance.getSubstrate(targetAccount.address);791    expect(accountBalance).to.be.equal(0n);792  });793});794795// These tests are relevant only when796// the the corresponding foreign assets are not registered797describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {798  let alice: IKeyringPair;799  let alith: IKeyringPair;800801  const testAmount = 100_000_000_000n;802  let quartzParachainJunction;803  let quartzAccountJunction;804805  let quartzParachainMultilocation: any;806  let quartzAccountMultilocation: any;807  let quartzCombinedMultilocation: any;808809  let messageSent: any;810811  const maxWaitBlocks = 3;812813  before(async () => {814    await usingPlaygrounds(async (helper, privateKey) => {815      alice = await privateKey('//Alice');816817      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};818      quartzAccountJunction = {819        AccountId32: {820          network: 'Any',821          id: alice.addressRaw,822        },823      };824825      quartzParachainMultilocation = {826        V2: {827          parents: 1,828          interior: {829            X1: quartzParachainJunction,830          },831        },832      };833834      quartzAccountMultilocation = {835        V2: {836          parents: 0,837          interior: {838            X1: quartzAccountJunction,839          },840        },841      };842843      quartzCombinedMultilocation = {844        V2: {845          parents: 1,846          interior: {847            X2: [quartzParachainJunction, quartzAccountJunction],848          },849        },850      };851852      // Set the default version to wrap the first message to other chains.853      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);854    });855856    // eslint-disable-next-line require-await857    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {858      alith = helper.account.alithAccount();859    });860  });861862  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {863    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash864        && event.outcome.isFailedToTransactAsset);865  };866867  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {868    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {869      const id = {870        Token: 'KAR',871      };872      const destination = quartzCombinedMultilocation;873      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');874875      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);876    });877878    await expectFailedToTransact(helper, messageSent);879  });880881  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {882    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {883      const id = 'SelfReserve';884      const destination = quartzCombinedMultilocation;885      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');886887      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);888    });889890    await expectFailedToTransact(helper, messageSent);891  });892893  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {894    await usingShidenPlaygrounds(shidenUrl, async (helper) => {895      const destinationParachain = quartzParachainMultilocation;896      const beneficiary = quartzAccountMultilocation;897      const assets = {898        V2: [{899          id: {900            Concrete: {901              parents: 0,902              interior: 'Here',903            },904          },905          fun: {906            Fungible: testAmount,907          },908        }],909      };910      const feeAssetItem = 0;911912      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [913        destinationParachain,914        beneficiary,915        assets,916        feeAssetItem,917      ]);918919      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);920    });921922    await expectFailedToTransact(helper, messageSent);923  });924});925926describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {927  // Quartz constants928  let alice: IKeyringPair;929  let quartzAssetLocation;930931  let randomAccountQuartz: IKeyringPair;932  let randomAccountMoonriver: IKeyringPair;933934  // Moonriver constants935  let assetId: string;936937  const quartzAssetMetadata = {938    name: 'xcQuartz',939    symbol: 'xcQTZ',940    decimals: 18,941    isFrozen: false,942    minimalBalance: 1n,943  };944945  let balanceQuartzTokenInit: bigint;946  let balanceQuartzTokenMiddle: bigint;947  let balanceQuartzTokenFinal: bigint;948  let balanceForeignQtzTokenInit: bigint;949  let balanceForeignQtzTokenMiddle: bigint;950  let balanceForeignQtzTokenFinal: bigint;951  let balanceMovrTokenInit: bigint;952  let balanceMovrTokenMiddle: bigint;953  let balanceMovrTokenFinal: bigint;954955  before(async () => {956    await usingPlaygrounds(async (helper, privateKey) => {957      alice = await privateKey('//Alice');958      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);959960      balanceForeignQtzTokenInit = 0n;961962      // Set the default version to wrap the first message to other chains.963      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);964    });965966    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {967      const alithAccount = helper.account.alithAccount();968      const baltatharAccount = helper.account.baltatharAccount();969      const dorothyAccount = helper.account.dorothyAccount();970971      randomAccountMoonriver = helper.account.create();972973      // >>> Sponsoring Dorothy >>>974      console.log('Sponsoring Dorothy.......');975      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);976      console.log('Sponsoring Dorothy.......DONE');977      // <<< Sponsoring Dorothy <<<978979      quartzAssetLocation = {980        XCM: {981          parents: 1,982          interior: {X1: {Parachain: QUARTZ_CHAIN}},983        },984      };985      const existentialDeposit = 1n;986      const isSufficient = true;987      const unitsPerSecond = 1n;988      const numAssetsWeightHint = 0;989990      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({991        location: quartzAssetLocation,992        metadata: quartzAssetMetadata,993        existentialDeposit,994        isSufficient,995        unitsPerSecond,996        numAssetsWeightHint,997      });998999      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);10001001      await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);10021003      // >>> Acquire Quartz AssetId Info on Moonriver >>>1004      console.log('Acquire Quartz AssetId Info on Moonriver.......');10051006      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10071008      console.log('QTZ asset ID is %s', assetId);1009      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1010      // >>> Acquire Quartz AssetId Info on Moonriver >>>10111012      // >>> Sponsoring random Account >>>1013      console.log('Sponsoring random Account.......');1014      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1015      console.log('Sponsoring random Account.......DONE');1016      // <<< Sponsoring random Account <<<10171018      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1019    });10201021    await usingPlaygrounds(async (helper) => {1022      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1023      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1024    });1025  });10261027  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1028    const currencyId = {1029      NativeAssetId: 'Here',1030    };1031    const dest = {1032      V2: {1033        parents: 1,1034        interior: {1035          X2: [1036            {Parachain: MOONRIVER_CHAIN},1037            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1038          ],1039        },1040      },1041    };1042    const amount = TRANSFER_AMOUNT;10431044    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10451046    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1047    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10481049    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1050    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1051    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10521053    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1054      await helper.wait.newBlocks(3);10551056      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10571058      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1059      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1060      expect(movrFees == 0n).to.be.true;10611062      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1063      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1064      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1065      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1066    });1067  });10681069  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1070    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1071      const asset = {1072        V2: {1073          id: {1074            Concrete: {1075              parents: 1,1076              interior: {1077                X1: {Parachain: QUARTZ_CHAIN},1078              },1079            },1080          },1081          fun: {1082            Fungible: TRANSFER_AMOUNT,1083          },1084        },1085      };1086      const destination = {1087        V2: {1088          parents: 1,1089          interior: {1090            X2: [1091              {Parachain: QUARTZ_CHAIN},1092              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1093            ],1094          },1095        },1096      };10971098      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10991100      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);11011102      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1103      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1104      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;11051106      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11071108      expect(qtzRandomAccountAsset).to.be.null;11091110      balanceForeignQtzTokenFinal = 0n;11111112      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1113      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1114      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1115    });11161117    await helper.wait.newBlocks(3);11181119    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1120    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1121    expect(actuallyDelivered > 0).to.be.true;11221123    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11241125    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1126    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1127    expect(qtzFees == 0n).to.be.true;1128  });11291130  itSub('Moonriver can send only up to its balance', async ({helper}) => {1131    // set Moonriver's sovereign account's balance1132    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1133    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1134    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11351136    const moreThanMoonriverHas = moonriverBalance * 2n;11371138    let targetAccountBalance = 0n;1139    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11401141    const quartzMultilocation = {1142      V2: {1143        parents: 1,1144        interior: {1145          X1: {Parachain: QUARTZ_CHAIN},1146        },1147      },1148    };11491150    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1151      targetAccount.addressRaw,1152      {1153        Concrete: {1154          parents: 0,1155          interior: 'Here',1156        },1157      },1158      moreThanMoonriverHas,1159    );11601161    let maliciousXcmProgramSent: any;1162    const maxWaitBlocks = 3;11631164    // Try to trick Quartz1165    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1166      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11671168      // Needed to bypass the call filter.1169      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1170      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11711172      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1173    });11741175    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1176        && event.outcome.isFailedToTransactAsset);11771178    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1179    expect(targetAccountBalance).to.be.equal(0n);11801181    // But Moonriver still can send the correct amount1182    const validTransferAmount = moonriverBalance / 2n;1183    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1184      targetAccount.addressRaw,1185      {1186        Concrete: {1187          parents: 0,1188          interior: 'Here',1189        },1190      },1191      validTransferAmount,1192    );11931194    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1195      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11961197      // Needed to bypass the call filter.1198      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1199      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1200    });12011202    await helper.wait.newBlocks(maxWaitBlocks);12031204    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1205    expect(targetAccountBalance).to.be.equal(validTransferAmount);1206  });12071208  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1209    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1210    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12111212    const quartzMultilocation = {1213      V2: {1214        parents: 1,1215        interior: {1216          X1: {1217            Parachain: QUARTZ_CHAIN,1218          },1219        },1220      },1221    };12221223    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1224      targetAccount.addressRaw,1225      {1226        Concrete: {1227          parents: 0,1228          interior: {1229            X1: {1230              Parachain: QUARTZ_CHAIN,1231            },1232          },1233        },1234      },1235      testAmount,1236    );12371238    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1239      targetAccount.addressRaw,1240      {1241        Concrete: {1242          parents: 0,1243          interior: 'Here',1244        },1245      },1246      testAmount,1247    );12481249    let maliciousXcmProgramFullIdSent: any;1250    let maliciousXcmProgramHereIdSent: any;1251    const maxWaitBlocks = 3;12521253    // Try to trick Quartz using full QTZ identification1254    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1255      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12561257      // Needed to bypass the call filter.1258      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1259      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12601261      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1262    });12631264    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1265        && event.outcome.isUntrustedReserveLocation);12661267    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1268    expect(accountBalance).to.be.equal(0n);12691270    // Try to trick Quartz using shortened QTZ identification1271    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1272      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12731274      // Needed to bypass the call filter.1275      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1276      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12771278      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1279    });12801281    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1282        && event.outcome.isUntrustedReserveLocation);12831284    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1285    expect(accountBalance).to.be.equal(0n);1286  });1287});12881289describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1290  let alice: IKeyringPair;1291  let sender: IKeyringPair;12921293  const QTZ_ASSET_ID_ON_SHIDEN = 1;1294  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12951296  // Quartz -> Shiden1297  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1298  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1299  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1300  const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens13011302  // Shiden -> Quartz1303  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1304  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ13051306  let balanceAfterQuartzToShidenXCM: bigint;13071308  before(async () => {1309    await usingPlaygrounds(async (helper, privateKey) => {1310      alice = await privateKey('//Alice');1311      [sender] = await helper.arrange.createAccounts([100n], alice);1312      console.log('sender', sender.address);13131314      // Set the default version to wrap the first message to other chains.1315      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1316    });13171318    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1319      console.log('1. Create foreign asset and metadata');1320      // TODO update metadata with values from production1321      await helper.assets.create(1322        alice,1323        QTZ_ASSET_ID_ON_SHIDEN,1324        alice.address,1325        QTZ_MINIMAL_BALANCE_ON_SHIDEN,1326      );13271328      await helper.assets.setMetadata(1329        alice,1330        QTZ_ASSET_ID_ON_SHIDEN,1331        'Cross chain QTZ',1332        'xcQTZ',1333        Number(QTZ_DECIMALS),1334      );13351336      console.log('2. Register asset location on Shiden');1337      const assetLocation = {1338        V2: {1339          parents: 1,1340          interior: {1341            X1: {1342              Parachain: QUARTZ_CHAIN,1343            },1344          },1345        },1346      };13471348      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13491350      console.log('3. Set QTZ payment for XCM execution on Shiden');1351      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13521353      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1354      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1355    });1356  });13571358  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1359    const destination = {1360      V2: {1361        parents: 1,1362        interior: {1363          X1: {1364            Parachain: SHIDEN_CHAIN,1365          },1366        },1367      },1368    };13691370    const beneficiary = {1371      V2: {1372        parents: 0,1373        interior: {1374          X1: {1375            AccountId32: {1376              network: 'Any',1377              id: sender.addressRaw,1378            },1379          },1380        },1381      },1382    };13831384    const assets = {1385      V2: [1386        {1387          id: {1388            Concrete: {1389              parents: 0,1390              interior: 'Here',1391            },1392          },1393          fun: {1394            Fungible: qtzToShidenTransferred,1395          },1396        },1397      ],1398    };13991400    // Initial balance is 100 QTZ1401    const balanceBefore = await helper.balance.getSubstrate(sender.address);1402    console.log(`Initial balance is: ${balanceBefore}`);14031404    const feeAssetItem = 0;1405    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14061407    // Balance after reserve transfer is less than 901408    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1409    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1410    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1411    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14121413    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1414      await helper.wait.newBlocks(3);1415      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1416      const shidenBalance = await helper.balance.getSubstrate(sender.address);14171418      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1419      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14201421      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1422      // SHD balance does not changed:1423      expect(shidenBalance).to.eq(shidenInitialBalance);1424    });1425  });14261427  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1428    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1429      const destination = {1430        V2: {1431          parents: 1,1432          interior: {1433            X1: {1434              Parachain: QUARTZ_CHAIN,1435            },1436          },1437        },1438      };14391440      const beneficiary = {1441        V2: {1442          parents: 0,1443          interior: {1444            X1: {1445              AccountId32: {1446                network: 'Any',1447                id: sender.addressRaw,1448              },1449            },1450          },1451        },1452      };14531454      const assets = {1455        V2: [1456          {1457            id: {1458              Concrete: {1459                parents: 1,1460                interior: {1461                  X1: {1462                    Parachain: QUARTZ_CHAIN,1463                  },1464                },1465              },1466            },1467            fun: {1468              Fungible: qtzFromShidenTransfered,1469            },1470          },1471        ],1472      };14731474      // Initial balance is 1 SDN1475      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1476      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1477      expect(balanceSDNbefore).to.eq(shidenInitialBalance);14781479      const feeAssetItem = 0;1480      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1481      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14821483      // Balance after reserve transfer is less than 1 SDN1484      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1485      const balanceSDN = await helper.balance.getSubstrate(sender.address);1486      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14871488      // Assert: xcQTZ balance correctly decreased1489      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1490      // Assert: SDN balance is 0.996...1491      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1492    });14931494    await helper.wait.newBlocks(3);1495    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1496    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1497    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1498  });14991500  itSub('Shiden can send only up to its balance', async ({helper}) => {1501    // set Shiden's sovereign account's balance1502    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1503    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1504    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15051506    const moreThanShidenHas = shidenBalance * 2n;15071508    let targetAccountBalance = 0n;1509    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15101511    const quartzMultilocation = {1512      V2: {1513        parents: 1,1514        interior: {1515          X1: {Parachain: QUARTZ_CHAIN},1516        },1517      },1518    };15191520    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1521      targetAccount.addressRaw,1522      {1523        Concrete: {1524          parents: 0,1525          interior: 'Here',1526        },1527      },1528      moreThanShidenHas,1529    );15301531    let maliciousXcmProgramSent: any;1532    const maxWaitBlocks = 3;15331534    // Try to trick Quartz1535    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1536      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15371538      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1539    });15401541    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1542        && event.outcome.isFailedToTransactAsset);15431544    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1545    expect(targetAccountBalance).to.be.equal(0n);15461547    // But Shiden still can send the correct amount1548    const validTransferAmount = shidenBalance / 2n;1549    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1550      targetAccount.addressRaw,1551      {1552        Concrete: {1553          parents: 0,1554          interior: 'Here',1555        },1556      },1557      validTransferAmount,1558    );15591560    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1561      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1562    });15631564    await helper.wait.newBlocks(maxWaitBlocks);15651566    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1567    expect(targetAccountBalance).to.be.equal(validTransferAmount);1568  });15691570  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1571    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1572    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15731574    const quartzMultilocation = {1575      V2: {1576        parents: 1,1577        interior: {1578          X1: {1579            Parachain: QUARTZ_CHAIN,1580          },1581        },1582      },1583    };15841585    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1586      targetAccount.addressRaw,1587      {1588        Concrete: {1589          parents: 1,1590          interior: {1591            X1: {1592              Parachain: QUARTZ_CHAIN,1593            },1594          },1595        },1596      },1597      testAmount,1598    );15991600    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1601      targetAccount.addressRaw,1602      {1603        Concrete: {1604          parents: 0,1605          interior: 'Here',1606        },1607      },1608      testAmount,1609    );16101611    let maliciousXcmProgramFullIdSent: any;1612    let maliciousXcmProgramHereIdSent: any;1613    const maxWaitBlocks = 3;16141615    // Try to trick Quartz using full QTZ identification1616    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1617      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16181619      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1620    });16211622    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1623        && event.outcome.isUntrustedReserveLocation);16241625    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1626    expect(accountBalance).to.be.equal(0n);16271628    // Try to trick Quartz using shortened QTZ identification1629    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1630      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16311632      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1633    });16341635    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1636        && event.outcome.isUntrustedReserveLocation);16371638    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1639    expect(accountBalance).to.be.equal(0n);1640  });1641});