git.delta.rocks / unique-network / refs/commits / 33d49899f36c

difftreelog

Merge pull request #985 from UniqueNetwork/fix/xcm-release-60

Yaroslav Bolyukin2023-09-06parents: #432eea3 #bf4df5f.patch.diff
in: master

4 files changed

modified.baedeker/xcm-quartz.jsonnetdiffbeforeafterboth
--- a/.baedeker/xcm-quartz.jsonnet
+++ b/.baedeker/xcm-quartz.jsonnet
@@ -82,7 +82,7 @@
 			wantedKeys: 'para-nimbus',
 			legacyRpc: true,
 		},
-		for name in ['alice', 'bob']
+		for name in ['alith', 'baltathar']
 	},
 };
 
modified.baedeker/xcm-unique.jsonnetdiffbeforeafterboth
--- a/.baedeker/xcm-unique.jsonnet
+++ b/.baedeker/xcm-unique.jsonnet
@@ -83,7 +83,7 @@
 			wantedKeys: 'para-nimbus',
 			legacyRpc: true,
 		},
-		for name in ['alice', 'bob']
+		for name in ['alith', 'baltathar']
 	},
 };
 
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;470471  const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;472473  before(async () => {474    await usingPlaygrounds(async (helper, privateKey) => {475      alice = await privateKey('//Alice');476      [randomAccount] = await helper.arrange.createAccounts([0n], alice);477478      // Set the default version to wrap the first message to other chains.479      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);480    });481482    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {483      const destination = {484        V2: {485          parents: 1,486          interior: {487            X1: {488              Parachain: QUARTZ_CHAIN,489            },490          },491        },492      };493494      const metadata = {495        name: 'Quartz',496        symbol: 'QTZ',497        decimals: 18,498        minimalBalance: 1000000000000000000n,499      };500501      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);502      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);503      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);504      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});505    });506507    await usingPlaygrounds(async (helper) => {508      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);509      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);510    });511  });512513  itSub('Should connect and send QTZ to Karura', async ({helper}) => {514    const destination = {515      V2: {516        parents: 1,517        interior: {518          X1: {519            Parachain: KARURA_CHAIN,520          },521        },522      },523    };524525    const beneficiary = {526      V2: {527        parents: 0,528        interior: {529          X1: {530            AccountId32: {531              network: 'Any',532              id: randomAccount.addressRaw,533            },534          },535        },536      },537    };538539    const assets = {540      V2: [541        {542          id: {543            Concrete: {544              parents: 0,545              interior: 'Here',546            },547          },548          fun: {549            Fungible: TRANSFER_AMOUNT,550          },551        },552      ],553    };554555    const feeAssetItem = 0;556557    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');558    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);559560    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;561    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;562    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));563564    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {565      await helper.wait.newBlocks(3);566567      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});568      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);569570      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;571      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;572      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;573574      console.log(575        '[Quartz -> Karura] transaction fees on Karura: %s KAR',576        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),577      );578      console.log(579        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',580        helper.util.bigIntToDecimals(karUnqFees),581      );582      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));583      expect(karFees == 0n).to.be.true;584      expect(585        karUnqFees == expectedKaruraIncomeFee,586        'Karura took different income fee, check the Karura foreign asset config',587      ).to.be.true;588    });589  });590591  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {592    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {593      const destination = {594        V2: {595          parents: 1,596          interior: {597            X2: [598              {Parachain: QUARTZ_CHAIN},599              {600                AccountId32: {601                  network: 'Any',602                  id: randomAccount.addressRaw,603                },604              },605            ],606          },607        },608      };609610      const id = {611        ForeignAsset: 0,612      };613614      await helper.xTokens.transfer(randomAccount, id, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');615      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);616      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);617618      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;619      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;620621      console.log(622        '[Karura -> Quartz] transaction fees on Karura: %s KAR',623        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),624      );625      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));626627      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;628      expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;629    });630631    await helper.wait.newBlocks(3);632633    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);634    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;635    expect(actuallyDelivered > 0).to.be.true;636637    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));638639    const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;640    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));641    expect(qtzFees == 0n).to.be.true;642  });643644  itSub('Karura can send only up to its balance', async ({helper}) => {645    // set Karura's sovereign account's balance646    const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);647    const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);648    await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);649650    const moreThanKaruraHas = karuraBalance * 2n;651652    let targetAccountBalance = 0n;653    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);654655    const quartzMultilocation = {656      V2: {657        parents: 1,658        interior: {659          X1: {Parachain: QUARTZ_CHAIN},660        },661      },662    };663664    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(665      targetAccount.addressRaw,666      {667        Concrete: {668          parents: 0,669          interior: 'Here',670        },671      },672      moreThanKaruraHas,673    );674675    let maliciousXcmProgramSent: any;676    const maxWaitBlocks = 3;677678    // Try to trick Quartz679    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {680      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);681682      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);683    });684685    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash686        && event.outcome().isFailedToTransactAsset);687688    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);689    expect(targetAccountBalance).to.be.equal(0n);690691    // But Karura still can send the correct amount692    const validTransferAmount = karuraBalance / 2n;693    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(694      targetAccount.addressRaw,695      {696        Concrete: {697          parents: 0,698          interior: 'Here',699        },700      },701      validTransferAmount,702    );703704    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {705      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);706    });707708    await helper.wait.newBlocks(maxWaitBlocks);709710    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);711    expect(targetAccountBalance).to.be.equal(validTransferAmount);712  });713714  itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {715    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);716    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);717718    const quartzMultilocation = {719      V2: {720        parents: 1,721        interior: {722          X1: {723            Parachain: QUARTZ_CHAIN,724          },725        },726      },727    };728729    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(730      targetAccount.addressRaw,731      {732        Concrete: {733          parents: 1,734          interior: {735            X1: {736              Parachain: QUARTZ_CHAIN,737            },738          },739        },740      },741      testAmount,742    );743744    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(745      targetAccount.addressRaw,746      {747        Concrete: {748          parents: 0,749          interior: 'Here',750        },751      },752      testAmount,753    );754755    let maliciousXcmProgramFullIdSent: any;756    let maliciousXcmProgramHereIdSent: any;757    const maxWaitBlocks = 3;758759    // Try to trick Quartz using full QTZ identification760    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {761      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);762763      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);764    });765766    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash767        && event.outcome().isUntrustedReserveLocation);768769    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);770    expect(accountBalance).to.be.equal(0n);771772    // Try to trick Quartz using shortened QTZ identification773    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {774      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);775776      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);777    });778779    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash780        && event.outcome().isUntrustedReserveLocation);781782    accountBalance = await helper.balance.getSubstrate(targetAccount.address);783    expect(accountBalance).to.be.equal(0n);784  });785});786787// These tests are relevant only when788// the the corresponding foreign assets are not registered789describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {790  let alice: IKeyringPair;791  let alith: IKeyringPair;792793  const testAmount = 100_000_000_000n;794  let quartzParachainJunction;795  let quartzAccountJunction;796797  let quartzParachainMultilocation: any;798  let quartzAccountMultilocation: any;799  let quartzCombinedMultilocation: any;800801  let messageSent: any;802803  const maxWaitBlocks = 3;804805  before(async () => {806    await usingPlaygrounds(async (helper, privateKey) => {807      alice = await privateKey('//Alice');808809      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};810      quartzAccountJunction = {811        AccountId32: {812          network: 'Any',813          id: alice.addressRaw,814        },815      };816817      quartzParachainMultilocation = {818        V2: {819          parents: 1,820          interior: {821            X1: quartzParachainJunction,822          },823        },824      };825826      quartzAccountMultilocation = {827        V2: {828          parents: 0,829          interior: {830            X1: quartzAccountJunction,831          },832        },833      };834835      quartzCombinedMultilocation = {836        V2: {837          parents: 1,838          interior: {839            X2: [quartzParachainJunction, quartzAccountJunction],840          },841        },842      };843844      // Set the default version to wrap the first message to other chains.845      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);846    });847848    // eslint-disable-next-line require-await849    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {850      alith = helper.account.alithAccount();851    });852  });853854  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {855    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash856        && event.outcome().isFailedToTransactAsset);857  };858859  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {860    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {861      const id = {862        Token: 'KAR',863      };864      const destination = quartzCombinedMultilocation;865      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');866867      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);868    });869870    await expectFailedToTransact(helper, messageSent);871  });872873  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {874    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {875      const id = 'SelfReserve';876      const destination = quartzCombinedMultilocation;877      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');878879      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);880    });881882    await expectFailedToTransact(helper, messageSent);883  });884885  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {886    await usingShidenPlaygrounds(shidenUrl, async (helper) => {887      const destinationParachain = quartzParachainMultilocation;888      const beneficiary = quartzAccountMultilocation;889      const assets = {890        V2: [{891          id: {892            Concrete: {893              parents: 0,894              interior: 'Here',895            },896          },897          fun: {898            Fungible: testAmount,899          },900        }],901      };902      const feeAssetItem = 0;903904      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [905        destinationParachain,906        beneficiary,907        assets,908        feeAssetItem,909      ]);910911      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);912    });913914    await expectFailedToTransact(helper, messageSent);915  });916});917918describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {919  // Quartz constants920  let alice: IKeyringPair;921  let quartzAssetLocation;922923  let randomAccountQuartz: IKeyringPair;924  let randomAccountMoonriver: IKeyringPair;925926  // Moonriver constants927  let assetId: string;928929  const quartzAssetMetadata = {930    name: 'xcQuartz',931    symbol: 'xcQTZ',932    decimals: 18,933    isFrozen: false,934    minimalBalance: 1n,935  };936937  let balanceQuartzTokenInit: bigint;938  let balanceQuartzTokenMiddle: bigint;939  let balanceQuartzTokenFinal: bigint;940  let balanceForeignQtzTokenInit: bigint;941  let balanceForeignQtzTokenMiddle: bigint;942  let balanceForeignQtzTokenFinal: bigint;943  let balanceMovrTokenInit: bigint;944  let balanceMovrTokenMiddle: bigint;945  let balanceMovrTokenFinal: bigint;946947  before(async () => {948    await usingPlaygrounds(async (helper, privateKey) => {949      alice = await privateKey('//Alice');950      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);951952      balanceForeignQtzTokenInit = 0n;953954      // Set the default version to wrap the first message to other chains.955      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);956    });957958    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {959      const alithAccount = helper.account.alithAccount();960      const baltatharAccount = helper.account.baltatharAccount();961      const dorothyAccount = helper.account.dorothyAccount();962963      randomAccountMoonriver = helper.account.create();964965      // >>> Sponsoring Dorothy >>>966      console.log('Sponsoring Dorothy.......');967      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);968      console.log('Sponsoring Dorothy.......DONE');969      // <<< Sponsoring Dorothy <<<970971      quartzAssetLocation = {972        XCM: {973          parents: 1,974          interior: {X1: {Parachain: QUARTZ_CHAIN}},975        },976      };977      const existentialDeposit = 1n;978      const isSufficient = true;979      const unitsPerSecond = 1n;980      const numAssetsWeightHint = 0;981982      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({983        location: quartzAssetLocation,984        metadata: quartzAssetMetadata,985        existentialDeposit,986        isSufficient,987        unitsPerSecond,988        numAssetsWeightHint,989      });990991      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);992993      await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);994995      // >>> Acquire Quartz AssetId Info on Moonriver >>>996      console.log('Acquire Quartz AssetId Info on Moonriver.......');997998      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();9991000      console.log('QTZ asset ID is %s', assetId);1001      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1002      // >>> Acquire Quartz AssetId Info on Moonriver >>>10031004      // >>> Sponsoring random Account >>>1005      console.log('Sponsoring random Account.......');1006      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1007      console.log('Sponsoring random Account.......DONE');1008      // <<< Sponsoring random Account <<<10091010      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1011    });10121013    await usingPlaygrounds(async (helper) => {1014      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1015      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1016    });1017  });10181019  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1020    const currencyId = {1021      NativeAssetId: 'Here',1022    };1023    const dest = {1024      V2: {1025        parents: 1,1026        interior: {1027          X2: [1028            {Parachain: MOONRIVER_CHAIN},1029            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1030          ],1031        },1032      },1033    };1034    const amount = TRANSFER_AMOUNT;10351036    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10371038    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1039    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10401041    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1042    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1043    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10441045    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1046      await helper.wait.newBlocks(3);10471048      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10491050      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1051      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1052      expect(movrFees == 0n).to.be.true;10531054      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1055      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1056      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1057      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1058    });1059  });10601061  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1062    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1063      const asset = {1064        V2: {1065          id: {1066            Concrete: {1067              parents: 1,1068              interior: {1069                X1: {Parachain: QUARTZ_CHAIN},1070              },1071            },1072          },1073          fun: {1074            Fungible: TRANSFER_AMOUNT,1075          },1076        },1077      };1078      const destination = {1079        V2: {1080          parents: 1,1081          interior: {1082            X2: [1083              {Parachain: QUARTZ_CHAIN},1084              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1085            ],1086          },1087        },1088      };10891090      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10911092      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10931094      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1095      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1096      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10971098      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);10991100      expect(qtzRandomAccountAsset).to.be.null;11011102      balanceForeignQtzTokenFinal = 0n;11031104      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1105      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1106      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1107    });11081109    await helper.wait.newBlocks(3);11101111    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1112    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1113    expect(actuallyDelivered > 0).to.be.true;11141115    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11161117    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1118    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1119    expect(qtzFees == 0n).to.be.true;1120  });11211122  itSub('Moonriver can send only up to its balance', async ({helper}) => {1123    // set Moonriver's sovereign account's balance1124    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1125    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1126    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11271128    const moreThanMoonriverHas = moonriverBalance * 2n;11291130    let targetAccountBalance = 0n;1131    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11321133    const quartzMultilocation = {1134      V2: {1135        parents: 1,1136        interior: {1137          X1: {Parachain: QUARTZ_CHAIN},1138        },1139      },1140    };11411142    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1143      targetAccount.addressRaw,1144      {1145        Concrete: {1146          parents: 0,1147          interior: 'Here',1148        },1149      },1150      moreThanMoonriverHas,1151    );11521153    let maliciousXcmProgramSent: any;1154    const maxWaitBlocks = 3;11551156    // Try to trick Quartz1157    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1158      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11591160      // Needed to bypass the call filter.1161      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1162      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11631164      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1165    });11661167    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1168        && event.outcome().isFailedToTransactAsset);11691170    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1171    expect(targetAccountBalance).to.be.equal(0n);11721173    // But Moonriver still can send the correct amount1174    const validTransferAmount = moonriverBalance / 2n;1175    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1176      targetAccount.addressRaw,1177      {1178        Concrete: {1179          parents: 0,1180          interior: 'Here',1181        },1182      },1183      validTransferAmount,1184    );11851186    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1187      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11881189      // Needed to bypass the call filter.1190      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1191      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1192    });11931194    await helper.wait.newBlocks(maxWaitBlocks);11951196    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1197    expect(targetAccountBalance).to.be.equal(validTransferAmount);1198  });11991200  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1201    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1202    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12031204    const quartzMultilocation = {1205      V2: {1206        parents: 1,1207        interior: {1208          X1: {1209            Parachain: QUARTZ_CHAIN,1210          },1211        },1212      },1213    };12141215    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1216      targetAccount.addressRaw,1217      {1218        Concrete: {1219          parents: 0,1220          interior: {1221            X1: {1222              Parachain: QUARTZ_CHAIN,1223            },1224          },1225        },1226      },1227      testAmount,1228    );12291230    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1231      targetAccount.addressRaw,1232      {1233        Concrete: {1234          parents: 0,1235          interior: 'Here',1236        },1237      },1238      testAmount,1239    );12401241    let maliciousXcmProgramFullIdSent: any;1242    let maliciousXcmProgramHereIdSent: any;1243    const maxWaitBlocks = 3;12441245    // Try to trick Quartz using full QTZ identification1246    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1247      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12481249      // Needed to bypass the call filter.1250      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1251      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12521253      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1254    });12551256    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1257        && event.outcome().isUntrustedReserveLocation);12581259    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1260    expect(accountBalance).to.be.equal(0n);12611262    // Try to trick Quartz using shortened QTZ identification1263    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1264      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12651266      // Needed to bypass the call filter.1267      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1268      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12691270      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1271    });12721273    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1274        && event.outcome().isUntrustedReserveLocation);12751276    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1277    expect(accountBalance).to.be.equal(0n);1278  });1279});12801281describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1282  let alice: IKeyringPair;1283  let sender: IKeyringPair;12841285  const QTZ_ASSET_ID_ON_SHIDEN = 1;1286  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12871288  // Quartz -> Shiden1289  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1290  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1291  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1292  const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens12931294  // Shiden -> Quartz1295  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1296  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ12971298  let balanceAfterQuartzToShidenXCM: bigint;12991300  before(async () => {1301    await usingPlaygrounds(async (helper, privateKey) => {1302      alice = await privateKey('//Alice');1303      [sender] = await helper.arrange.createAccounts([100n], alice);1304      console.log('sender', sender.address);13051306      // Set the default version to wrap the first message to other chains.1307      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1308    });13091310    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1311      console.log('1. Create foreign asset and metadata');1312      // TODO update metadata with values from production1313      await helper.assets.create(1314        alice,1315        QTZ_ASSET_ID_ON_SHIDEN,1316        alice.address,1317        QTZ_MINIMAL_BALANCE_ON_SHIDEN,1318      );13191320      await helper.assets.setMetadata(1321        alice,1322        QTZ_ASSET_ID_ON_SHIDEN,1323        'Cross chain QTZ',1324        'xcQTZ',1325        Number(QTZ_DECIMALS),1326      );13271328      console.log('2. Register asset location on Shiden');1329      const assetLocation = {1330        V2: {1331          parents: 1,1332          interior: {1333            X1: {1334              Parachain: QUARTZ_CHAIN,1335            },1336          },1337        },1338      };13391340      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13411342      console.log('3. Set QTZ payment for XCM execution on Shiden');1343      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13441345      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1346      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1347    });1348  });13491350  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1351    const destination = {1352      V2: {1353        parents: 1,1354        interior: {1355          X1: {1356            Parachain: SHIDEN_CHAIN,1357          },1358        },1359      },1360    };13611362    const beneficiary = {1363      V2: {1364        parents: 0,1365        interior: {1366          X1: {1367            AccountId32: {1368              network: 'Any',1369              id: sender.addressRaw,1370            },1371          },1372        },1373      },1374    };13751376    const assets = {1377      V2: [1378        {1379          id: {1380            Concrete: {1381              parents: 0,1382              interior: 'Here',1383            },1384          },1385          fun: {1386            Fungible: qtzToShidenTransferred,1387          },1388        },1389      ],1390    };13911392    // Initial balance is 100 QTZ1393    const balanceBefore = await helper.balance.getSubstrate(sender.address);1394    console.log(`Initial balance is: ${balanceBefore}`);13951396    const feeAssetItem = 0;1397    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');13981399    // Balance after reserve transfer is less than 901400    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1401    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1402    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1403    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14041405    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1406      await helper.wait.newBlocks(3);1407      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1408      const shidenBalance = await helper.balance.getSubstrate(sender.address);14091410      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1411      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14121413      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1414      // SHD balance does not changed:1415      expect(shidenBalance).to.eq(shidenInitialBalance);1416    });1417  });14181419  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1420    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1421      const destination = {1422        V2: {1423          parents: 1,1424          interior: {1425            X1: {1426              Parachain: QUARTZ_CHAIN,1427            },1428          },1429        },1430      };14311432      const beneficiary = {1433        V2: {1434          parents: 0,1435          interior: {1436            X1: {1437              AccountId32: {1438                network: 'Any',1439                id: sender.addressRaw,1440              },1441            },1442          },1443        },1444      };14451446      const assets = {1447        V2: [1448          {1449            id: {1450              Concrete: {1451                parents: 1,1452                interior: {1453                  X1: {1454                    Parachain: QUARTZ_CHAIN,1455                  },1456                },1457              },1458            },1459            fun: {1460              Fungible: qtzFromShidenTransfered,1461            },1462          },1463        ],1464      };14651466      // Initial balance is 1 SDN1467      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1468      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1469      expect(balanceSDNbefore).to.eq(shidenInitialBalance);14701471      const feeAssetItem = 0;1472      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1473      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14741475      // Balance after reserve transfer is less than 1 SDN1476      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1477      const balanceSDN = await helper.balance.getSubstrate(sender.address);1478      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14791480      // Assert: xcQTZ balance correctly decreased1481      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1482      // Assert: SDN balance is 0.996...1483      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1484    });14851486    await helper.wait.newBlocks(3);1487    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1488    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1489    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1490  });14911492  itSub('Shiden can send only up to its balance', async ({helper}) => {1493    // set Shiden's sovereign account's balance1494    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1495    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1496    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);14971498    const moreThanShidenHas = shidenBalance * 2n;14991500    let targetAccountBalance = 0n;1501    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15021503    const quartzMultilocation = {1504      V2: {1505        parents: 1,1506        interior: {1507          X1: {Parachain: QUARTZ_CHAIN},1508        },1509      },1510    };15111512    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1513      targetAccount.addressRaw,1514      {1515        Concrete: {1516          parents: 0,1517          interior: 'Here',1518        },1519      },1520      moreThanShidenHas,1521    );15221523    let maliciousXcmProgramSent: any;1524    const maxWaitBlocks = 3;15251526    // Try to trick Quartz1527    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1528      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15291530      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1531    });15321533    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1534        && event.outcome().isFailedToTransactAsset);15351536    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1537    expect(targetAccountBalance).to.be.equal(0n);15381539    // But Shiden still can send the correct amount1540    const validTransferAmount = shidenBalance / 2n;1541    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1542      targetAccount.addressRaw,1543      {1544        Concrete: {1545          parents: 0,1546          interior: 'Here',1547        },1548      },1549      validTransferAmount,1550    );15511552    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1553      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1554    });15551556    await helper.wait.newBlocks(maxWaitBlocks);15571558    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1559    expect(targetAccountBalance).to.be.equal(validTransferAmount);1560  });15611562  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1563    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1564    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15651566    const quartzMultilocation = {1567      V2: {1568        parents: 1,1569        interior: {1570          X1: {1571            Parachain: QUARTZ_CHAIN,1572          },1573        },1574      },1575    };15761577    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1578      targetAccount.addressRaw,1579      {1580        Concrete: {1581          parents: 1,1582          interior: {1583            X1: {1584              Parachain: QUARTZ_CHAIN,1585            },1586          },1587        },1588      },1589      testAmount,1590    );15911592    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1593      targetAccount.addressRaw,1594      {1595        Concrete: {1596          parents: 0,1597          interior: 'Here',1598        },1599      },1600      testAmount,1601    );16021603    let maliciousXcmProgramFullIdSent: any;1604    let maliciousXcmProgramHereIdSent: any;1605    const maxWaitBlocks = 3;16061607    // Try to trick Quartz using full QTZ identification1608    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1609      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16101611      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1612    });16131614    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1615        && event.outcome().isUntrustedReserveLocation);16161617    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1618    expect(accountBalance).to.be.equal(0n);16191620    // Try to trick Quartz using shortened QTZ identification1621    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1622      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16231624      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1625    });16261627    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1628        && event.outcome().isUntrustedReserveLocation);16291630    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1631    expect(accountBalance).to.be.equal(0n);1632  });1633});
modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -84,9 +84,8 @@
 
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
       // Fund accounts on Statemint
-      const relayXcmVersion = 2;
-      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT, relayXcmVersion);
-      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT, relayXcmVersion);
+      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);
+      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);
     });
 
     await usingStatemintPlaygrounds(statemintUrl, async (helper) => {
@@ -154,7 +153,7 @@
     // (fee for USDT XCM are paid in relay tokens)
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
       const destination = {
-        V1: {
+        V2: {
           parents: 0,
           interior: {X1: {
             Parachain: UNIQUE_CHAIN,
@@ -163,7 +162,7 @@
         }};
 
       const beneficiary = {
-        V1: {
+        V2: {
           parents: 0,
           interior: {X1: {
             AccountId32: {
@@ -175,7 +174,7 @@
       };
 
       const assets = {
-        V1: [
+        V2: [
           {
             id: {
               Concrete: {
@@ -200,7 +199,7 @@
   itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {
     await usingStatemintPlaygrounds(statemintUrl, async (helper) => {
       const dest = {
-        V1: {
+        V2: {
           parents: 1,
           interior: {X1: {
             Parachain: UNIQUE_CHAIN,
@@ -209,7 +208,7 @@
         }};
 
       const beneficiary = {
-        V1: {
+        V2: {
           parents: 0,
           interior: {X1: {
             AccountId32: {
@@ -221,7 +220,7 @@
       };
 
       const assets = {
-        V1: [
+        V2: [
           {
             id: {
               Concrete: {
@@ -341,7 +340,7 @@
 
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
       const destination = {
-        V1: {
+        V2: {
           parents: 0,
           interior: {X1: {
             Parachain: UNIQUE_CHAIN,
@@ -350,7 +349,7 @@
         }};
 
       const beneficiary = {
-        V1: {
+        V2: {
           parents: 0,
           interior: {X1: {
             AccountId32: {
@@ -362,7 +361,7 @@
       };
 
       const assets = {
-        V1: [
+        V2: [
           {
             id: {
               Concrete: {
@@ -468,8 +467,9 @@
   // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48
   // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)
   const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;
+  const acalaEps = 8n * 10n ** 16n;
 
-  const ACALA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedAcalaIncomeFee;
+  let acalaBackwardTransferAmount: bigint;
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
@@ -482,7 +482,7 @@
 
     await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
       const destination = {
-        V1: {
+        V2: {
           parents: 1,
           interior: {
             X1: {
@@ -571,6 +571,8 @@
 
       const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;
       const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;
+      acalaBackwardTransferAmount = unqIncomeTransfer;
+
       const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;
 
       console.log(
@@ -583,8 +585,11 @@
       );
       console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));
       expect(acaFees == 0n).to.be.true;
+
+      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;
+
       expect(
-        acaUnqFees == expectedAcalaIncomeFee,
+        bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,
         'Acala took different income fee, check the Acala foreign asset config',
       ).to.be.true;
     });
@@ -593,7 +598,7 @@
   itSub('Should connect to Acala and send UNQ back', async ({helper}) => {
     await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
       const destination = {
-        V1: {
+        V2: {
           parents: 1,
           interior: {
             X2: [
@@ -613,7 +618,7 @@
         ForeignAsset: 0,
       };
 
-      await helper.xTokens.transfer(randomAccount, id, ACALA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');
+      await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');
       balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
       balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);
 
@@ -627,7 +632,7 @@
       console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));
 
       expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;
-      expect(unqOutcomeTransfer == ACALA_BACKWARD_TRANSFER_AMOUNT).to.be.true;
+      expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;
     });
 
     await helper.wait.newBlocks(3);
@@ -638,7 +643,7 @@
 
     console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));
 
-    const unqFees = ACALA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;
+    const unqFees = acalaBackwardTransferAmount - actuallyDelivered;
     console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));
     expect(unqFees == 0n).to.be.true;
   });
@@ -655,7 +660,7 @@
     const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
 
     const uniqueMultilocation = {
-      V1: {
+      V2: {
         parents: 1,
         interior: {
           X1: {Parachain: UNIQUE_CHAIN},
@@ -685,7 +690,7 @@
     });
 
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash
-        && event.outcome().isFailedToTransactAsset);
+        && event.outcome.isFailedToTransactAsset);
 
     targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(targetAccountBalance).to.be.equal(0n);
@@ -718,7 +723,7 @@
     const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
 
     const uniqueMultilocation = {
-      V1: {
+      V2: {
         parents: 1,
         interior: {
           X1: {
@@ -766,7 +771,7 @@
     });
 
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash
-        && event.outcome().isUntrustedReserveLocation);
+        && event.outcome.isUntrustedReserveLocation);
 
     let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(accountBalance).to.be.equal(0n);
@@ -779,7 +784,7 @@
     });
 
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash
-        && event.outcome().isUntrustedReserveLocation);
+        && event.outcome.isUntrustedReserveLocation);
 
     accountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(accountBalance).to.be.equal(0n);
@@ -845,7 +850,7 @@
       };
 
       uniqueCombinedMultilocationAcala = {
-        V1: {
+        V2: {
           parents: 1,
           interior: {
             X2: [uniqueParachainJunction, uniqueAccountJunction],
@@ -865,7 +870,7 @@
 
   const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
-        && event.outcome().isFailedToTransactAsset);
+        && event.outcome.isFailedToTransactAsset);
   };
 
   itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {
@@ -1178,7 +1183,7 @@
     });
 
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash
-        && event.outcome().isFailedToTransactAsset);
+        && event.outcome.isFailedToTransactAsset);
 
     targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(targetAccountBalance).to.be.equal(0n);
@@ -1267,7 +1272,7 @@
     });
 
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash
-        && event.outcome().isUntrustedReserveLocation);
+        && event.outcome.isUntrustedReserveLocation);
 
     let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(accountBalance).to.be.equal(0n);
@@ -1284,7 +1289,7 @@
     });
 
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash
-        && event.outcome().isUntrustedReserveLocation);
+        && event.outcome.isUntrustedReserveLocation);
 
     accountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(accountBalance).to.be.equal(0n);
@@ -1543,7 +1548,7 @@
     });
 
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash
-        && event.outcome().isFailedToTransactAsset);
+        && event.outcome.isFailedToTransactAsset);
 
     targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(targetAccountBalance).to.be.equal(0n);
@@ -1624,7 +1629,7 @@
     });
 
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash
-        && event.outcome().isUntrustedReserveLocation);
+        && event.outcome.isUntrustedReserveLocation);
 
     let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(accountBalance).to.be.equal(0n);
@@ -1637,7 +1642,7 @@
     });
 
     await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash
-        && event.outcome().isUntrustedReserveLocation);
+        && event.outcome.isUntrustedReserveLocation);
 
     accountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(accountBalance).to.be.equal(0n);