git.delta.rocks / unique-network / refs/commits / 668280b1206d

difftreelog

feat(xcm tests) `XcmHelper` integraion for QTZ\UNQ

PraetorP2023-09-29parent: #06b395f.patch.diff
in: master

6 files changed

modified.baedeker/.gitignorediffbeforeafterboth
--- a/.baedeker/.gitignore
+++ b/.baedeker/.gitignore
@@ -1,4 +1,5 @@
 /.bdk-env
-/rewrites.jsonnet
+/rewrites*.jsonnet
 /vendor
 /baedeker-library
+!/rewrites.example.jsonnet
\ No newline at end of file
modified.github/workflows/xcm.ymldiffbeforeafterboth
--- a/.github/workflows/xcm.yml
+++ b/.github/workflows/xcm.yml
@@ -38,7 +38,7 @@
         with:
           matrix: |
             network {opal}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.WESTMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmOpal}, runtime_features {opal-runtime}
-            network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmQuartz}, runtime_features {quartz-runtime}
+            network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmQuartz}, runtime_features {quartz-runtime}
             network {unique}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime}
 
   xcm:
modifiedtests/src/xcm/lowLevelXcmQuartz.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/lowLevelXcmQuartz.test.ts
+++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts
@@ -15,35 +15,16 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {IKeyringPair} from '@polkadot/types/types';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds,  usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util';
-import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
-import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT} from './xcm.types';
-
+import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds,  usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util';
+import {QUARTZ_CHAIN,  QTZ_DECIMALS,  SHIDEN_DECIMALS, karuraUrl, moonriverUrl,  shidenUrl,  SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT, SENDER_BUDGET, relayUrl} from './xcm.types';
+import {hexToString} from '@polkadot/util';
 
 const testHelper = new XcmTestHelper('quartz');
 
 describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => {
   let alice: IKeyringPair;
   let randomAccount: IKeyringPair;
-
-  let balanceQuartzTokenInit: bigint;
-  let balanceQuartzTokenMiddle: bigint;
-  let balanceQuartzTokenFinal: bigint;
-  let balanceKaruraTokenInit: bigint;
-  let balanceKaruraTokenMiddle: bigint;
-  let balanceKaruraTokenFinal: bigint;
-  let balanceQuartzForeignTokenInit: bigint;
-  let balanceQuartzForeignTokenMiddle: bigint;
-  let balanceQuartzForeignTokenFinal: bigint;
 
-  // computed by a test transfer from prod Quartz to prod Karura.
-  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9
-  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)
-  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;
-  const karuraEps = 8n * 10n ** 16n;
-
-  let karuraBackwardTransferAmount: bigint;
-
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
       alice = await privateKey('//Alice');
@@ -72,15 +53,19 @@
         minimalBalance: 1000000000000000000n,
       };
 
-      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>
+        hexToString(v.toJSON()['symbol'])) as string[];
+
+      if(!assets.includes('QTZ')) {
+        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+      } else {
+        console.log('QTZ token already registered on Karura assetRegistry pallet');
+      }
       await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
-      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);
-      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
     });
 
     await usingPlaygrounds(async (helper) => {
-      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);
-      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
     });
   });
 
@@ -100,67 +85,17 @@
 // the the corresponding foreign assets are not registered
 describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => {
   let alice: IKeyringPair;
-  let alith: IKeyringPair;
 
-  const testAmount = 100_000_000_000n;
-  let quartzParachainJunction;
-  let quartzAccountJunction;
 
-  let quartzParachainMultilocation: any;
-  let quartzAccountMultilocation: any;
-  let quartzCombinedMultilocation: any;
-
-  let messageSent: any;
-
-  const maxWaitBlocks = 3;
-
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
       alice = await privateKey('//Alice');
 
-      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};
-      quartzAccountJunction = {
-        AccountId32: {
-          network: 'Any',
-          id: alice.addressRaw,
-        },
-      };
-
-      quartzParachainMultilocation = {
-        V2: {
-          parents: 1,
-          interior: {
-            X1: quartzParachainJunction,
-          },
-        },
-      };
 
-      quartzAccountMultilocation = {
-        V2: {
-          parents: 0,
-          interior: {
-            X1: quartzAccountJunction,
-          },
-        },
-      };
 
-      quartzCombinedMultilocation = {
-        V2: {
-          parents: 1,
-          interior: {
-            X2: [quartzParachainJunction, quartzAccountJunction],
-          },
-        },
-      };
-
       // Set the default version to wrap the first message to other chains.
       await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
     });
-
-    // eslint-disable-next-line require-await
-    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
-      alith = helper.account.alithAccount();
-    });
   });
 
   itSub('Quartz rejects KAR tokens from Karura', async () => {
@@ -195,22 +130,12 @@
     minimalBalance: 1n,
   };
 
-  let balanceQuartzTokenInit: bigint;
-  let balanceQuartzTokenMiddle: bigint;
-  let balanceQuartzTokenFinal: bigint;
-  let balanceForeignQtzTokenInit: bigint;
-  let balanceForeignQtzTokenMiddle: bigint;
-  let balanceForeignQtzTokenFinal: bigint;
-  let balanceMovrTokenInit: bigint;
-  let balanceMovrTokenMiddle: bigint;
-  let balanceMovrTokenFinal: bigint;
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
       alice = await privateKey('//Alice');
       [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);
 
-      balanceForeignQtzTokenInit = 0n;
 
       // Set the default version to wrap the first message to other chains.
       await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
@@ -239,20 +164,22 @@
       const isSufficient = true;
       const unitsPerSecond = 1n;
       const numAssetsWeightHint = 0;
+      if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {
+        console.log('Quartz asset already registered on Moonriver');
+      } else {
+        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
+          location: quartzAssetLocation,
+          metadata: quartzAssetMetadata,
+          existentialDeposit,
+          isSufficient,
+          unitsPerSecond,
+          numAssetsWeightHint,
+        });
 
-      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
-        location: quartzAssetLocation,
-        metadata: quartzAssetMetadata,
-        existentialDeposit,
-        isSufficient,
-        unitsPerSecond,
-        numAssetsWeightHint,
-      });
-
-      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
-
-      await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
 
+        await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+      }
       // >>> Acquire Quartz AssetId Info on Moonriver >>>
       console.log('Acquire Quartz AssetId Info on Moonriver.......');
 
@@ -267,13 +194,10 @@
       await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);
       console.log('Sponsoring random Account.......DONE');
       // <<< Sponsoring random Account <<<
-
-      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);
     });
 
     await usingPlaygrounds(async (helper) => {
       await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);
-      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);
     });
   });
 
@@ -296,7 +220,7 @@
 
 describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => {
   let alice: IKeyringPair;
-  let sender: IKeyringPair;
+  let randomAccount: IKeyringPair;
 
   const QTZ_ASSET_ID_ON_SHIDEN = 1;
   const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;
@@ -304,71 +228,69 @@
   // Quartz -> Shiden
   const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden
   const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?
-  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ
-  const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens
 
-  // Shiden -> Quartz
-  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ
-  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ
 
-  let balanceAfterQuartzToShidenXCM: bigint;
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
       alice = await privateKey('//Alice');
-      [sender] = await helper.arrange.createAccounts([100n], alice);
-      console.log('sender', sender.address);
+      randomAccount = helper.arrange.createEmptyAccount();
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
+      console.log('sender: ', randomAccount.address);
 
       // Set the default version to wrap the first message to other chains.
       await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
     });
 
     await usingShidenPlaygrounds(shidenUrl, async (helper) => {
-      console.log('1. Create foreign asset and metadata');
-      // TODO update metadata with values from production
-      await helper.assets.create(
-        alice,
-        QTZ_ASSET_ID_ON_SHIDEN,
-        alice.address,
-        QTZ_MINIMAL_BALANCE_ON_SHIDEN,
-      );
+      if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {
+        console.log('1. Create foreign asset and metadata');
+        // TODO update metadata with values from production
+        await helper.assets.create(
+          alice,
+          QTZ_ASSET_ID_ON_SHIDEN,
+          alice.address,
+          QTZ_MINIMAL_BALANCE_ON_SHIDEN,
+        );
 
-      await helper.assets.setMetadata(
-        alice,
-        QTZ_ASSET_ID_ON_SHIDEN,
-        'Cross chain QTZ',
-        'xcQTZ',
-        Number(QTZ_DECIMALS),
-      );
+        await helper.assets.setMetadata(
+          alice,
+          QTZ_ASSET_ID_ON_SHIDEN,
+          'Cross chain QTZ',
+          'xcQTZ',
+          Number(QTZ_DECIMALS),
+        );
 
-      console.log('2. Register asset location on Shiden');
-      const assetLocation = {
-        V2: {
-          parents: 1,
-          interior: {
-            X1: {
-              Parachain: QUARTZ_CHAIN,
+        console.log('2. Register asset location on Shiden');
+        const assetLocation = {
+          V2: {
+            parents: 1,
+            interior: {
+              X1: {
+                Parachain: QUARTZ_CHAIN,
+              },
             },
           },
-        },
-      };
-
-      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);
+        };
 
-      console.log('3. Set QTZ payment for XCM execution on Shiden');
-      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);
 
+        console.log('3. Set QTZ payment for XCM execution on Shiden');
+        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+      } else {
+        console.log('QTZ is already registered on Shiden');
+      }
       console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');
-      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, shidenInitialBalance);
     });
   });
 
   itSub('Should connect and send QTZ to Shiden', async () => {
-    await testHelper.sendUnqTo('shiden', sender);
+    await testHelper.sendUnqTo('shiden', randomAccount);
   });
 
   itSub('Should connect to Shiden and send QTZ back', async () => {
-    await testHelper.sendUnqBack('shiden', alice, sender);
+    await testHelper.sendUnqBack('shiden', alice, randomAccount);
   });
 
   itSub('Shiden can send only up to its balance', async () => {
modifiedtests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -16,320 +16,16 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import config from '../config';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util';
-import {Event} from '../util/playgrounds/unique.dev';
+import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util';
 import {nToBigInt} from '@polkadot/util';
 import {hexToString} from '@polkadot/util';
-import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
-
-
-const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
-const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
-const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
-const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
-const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
-
-let balanceUniqueTokenInit: bigint;
-let balanceUniqueTokenMiddle: bigint;
-let balanceUniqueTokenFinal: bigint;
-let unqFees: bigint;
+import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl,  moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types';
 
 const testHelper = new XcmTestHelper('unique');
-
-async function genericSendUnqTo(
-  networkName: keyof typeof NETWORKS,
-  randomAccount: IKeyringPair,
-  randomAccountOnTargetChain = randomAccount,
-) {
-  const networkUrl = mapToChainUrl(networkName);
-  const targetPlayground = getDevPlayground(networkName);
-  await usingPlaygrounds(async (helper) => {
-    balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
-    const destination = {
-      V2: {
-        parents: 1,
-        interior: {
-          X1: {
-            Parachain: mapToChainId(networkName),
-          },
-        },
-      },
-    };
-
-    const beneficiary = {
-      V2: {
-        parents: 0,
-        interior: {
-          X1: (
-            networkName == 'moonbeam' ?
-              {
-                AccountKey20: {
-                  network: 'Any',
-                  key: randomAccountOnTargetChain.address,
-                },
-              }
-              :
-              {
-                AccountId32: {
-                  network: 'Any',
-                  id: randomAccountOnTargetChain.addressRaw,
-                },
-              }
-          ),
-        },
-      },
-    };
-
-    const assets = {
-      V2: [
-        {
-          id: {
-            Concrete: {
-              parents: 0,
-              interior: 'Here',
-            },
-          },
-          fun: {
-            Fungible: TRANSFER_AMOUNT,
-          },
-        },
-      ],
-    };
-    const feeAssetItem = 0;
-
-    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
-    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
-
-    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
-    console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));
-    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
-
-    await targetPlayground(networkUrl, async (helper) => {
-      /*
-        Since only the parachain part of the Polkadex
-        infrastructure is launched (without their
-        solochain validators), processing incoming
-        assets will lead to an error.
-        This error indicates that the Polkadex chain
-        received a message from the Unique network,
-        since the hash is being checked to ensure
-        it matches what was sent.
-      */
-      if(networkName == 'polkadex') {
-        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);
-      } else {
-        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);
-      }
-    });
-
-  });
-}
-
-async function genericSendUnqBack(
-  networkName: keyof typeof NETWORKS,
-  sudoer: IKeyringPair,
-  randomAccountOnUnq: IKeyringPair,
-) {
-  const networkUrl = mapToChainUrl(networkName);
-
-  const targetPlayground = getDevPlayground(networkName);
-  await usingPlaygrounds(async (helper) => {
-
-    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
-      randomAccountOnUnq.addressRaw,
-      uniqueAssetId,
-      SENDBACK_AMOUNT,
-    );
-
-    let xcmProgramSent: any;
-
 
-    await targetPlayground(networkUrl, async (helper) => {
-      if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);
-        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      } else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);
-        // Needed to bypass the call filter.
-        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
-        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-
-    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
-
-    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
-
-    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);
-
-  });
-}
 
-async function genericSendOnlyOwnedBalance(
-  networkName: keyof typeof NETWORKS,
-  sudoer: IKeyringPair,
-) {
-  const networkUrl = mapToChainUrl(networkName);
-  const targetPlayground = getDevPlayground(networkName);
 
-  const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);
 
-  await usingPlaygrounds(async (helper) => {
-    const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));
-    await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);
-    const moreThanTargetChainHas = 2n * targetChainBalance;
-
-    const targetAccount = helper.arrange.createEmptyAccount();
-
-    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
-      targetAccount.addressRaw,
-      {
-        Concrete: {
-          parents: 0,
-          interior: 'Here',
-        },
-      },
-      moreThanTargetChainHas,
-    );
-
-    let maliciousXcmProgramSent: any;
-
-
-    await targetPlayground(networkUrl, async (helper) => {
-      if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);
-        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      } else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
-        // Needed to bypass the call filter.
-        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
-        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-
-    await expectFailedToTransact(helper, maliciousXcmProgramSent);
-
-    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
-    expect(targetAccountBalance).to.be.equal(0n);
-  });
-}
-
-async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {
-  const networkUrl = mapToChainUrl(netwokrName);
-  const targetPlayground = getDevPlayground(netwokrName);
-
-  await usingPlaygrounds(async (helper) => {
-    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
-    const targetAccount = helper.arrange.createEmptyAccount();
-
-    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
-      targetAccount.addressRaw,
-      uniqueAssetId,
-      testAmount,
-    );
-
-    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
-      targetAccount.addressRaw,
-      {
-        Concrete: {
-          parents: 0,
-          interior: 'Here',
-        },
-      },
-      testAmount,
-    );
-
-    let maliciousXcmProgramFullIdSent: any;
-    let maliciousXcmProgramHereIdSent: any;
-    const maxWaitBlocks = 3;
-
-    // Try to trick Unique using full UNQ identification
-    await targetPlayground(networkUrl, async (helper) => {
-      if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
-        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-      // Moonbeam case
-      else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
-        // Needed to bypass the call filter.
-        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);
-
-        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-
-
-    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);
-
-    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
-    expect(accountBalance).to.be.equal(0n);
-
-    // Try to trick Unique using shortened UNQ identification
-    await targetPlayground(networkUrl, async (helper) => {
-      if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
-        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-      else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
-        // Needed to bypass the call filter.
-        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);
-
-        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-
-    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);
-
-    accountBalance = await helper.balance.getSubstrate(targetAccount.address);
-    expect(accountBalance).to.be.equal(0n);
-  });
-}
-
-async function genericRejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {
-  const networkUrl = mapToChainUrl(networkName);
-  const targetPlayground = getDevPlayground(networkName);
-  let messageSent: any;
-
-  await usingPlaygrounds(async (helper) => {
-    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
-      helper.arrange.createEmptyAccount().addressRaw,
-      {
-        Concrete: {
-          parents: 1,
-          interior: {
-            X1: {
-              Parachain: mapToChainId(networkName),
-            },
-          },
-        },
-      },
-      TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,
-    );
-    await targetPlayground(networkUrl, async (helper) => {
-      if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
-        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      } else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
-        // Needed to bypass the call filter.
-        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
-        await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);
-
-        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-    await expectFailedToTransact(helper, messageSent);
-  });
-}
-
-
 describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {
   let alice: IKeyringPair;
   let randomAccount: IKeyringPair;
@@ -375,24 +71,23 @@
 
     await usingPlaygrounds(async (helper) => {
       await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
-      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
     });
   });
 
   itSub('Should connect and send UNQ to Acala', async () => {
-    await genericSendUnqTo('acala', randomAccount);
+    await testHelper.sendUnqTo('acala', randomAccount);
   });
 
   itSub('Should connect to Acala and send UNQ back', async () => {
-    await genericSendUnqBack('acala', alice, randomAccount);
+    await testHelper.sendUnqBack('acala', alice, randomAccount);
   });
 
   itSub('Acala can send only up to its balance', async () => {
-    await genericSendOnlyOwnedBalance('acala', alice);
+    await testHelper.sendOnlyOwnedBalance('acala', alice);
   });
 
   itSub('Should not accept reserve transfer of UNQ from Acala', async () => {
-    await genericReserveTransferUNQfrom('acala', alice);
+    await testHelper.reserveTransferUNQfrom('acala', alice);
   });
 });
 
@@ -430,7 +125,6 @@
 
     await usingPlaygrounds(async (helper) => {
       await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
-      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
     });
   });
 
@@ -467,19 +161,19 @@
   });
 
   itSub('Unique rejects ACA tokens from Acala', async () => {
-    await genericRejectNativeTokensFrom('acala', alice);
+    await testHelper.rejectNativeTokensFrom('acala', alice);
   });
 
   itSub('Unique rejects GLMR tokens from Moonbeam', async () => {
-    await genericRejectNativeTokensFrom('moonbeam', alice);
+    await testHelper.rejectNativeTokensFrom('moonbeam', alice);
   });
 
   itSub('Unique rejects ASTR tokens from Astar', async () => {
-    await genericRejectNativeTokensFrom('astar', alice);
+    await testHelper.rejectNativeTokensFrom('astar', alice);
   });
 
   itSub('Unique rejects PDX tokens from Polkadex', async () => {
-    await genericRejectNativeTokensFrom('polkadex', alice);
+    await testHelper.rejectNativeTokensFrom('polkadex', alice);
   });
 });
 
@@ -570,7 +264,6 @@
 
     await usingPlaygrounds(async (helper) => {
       await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);
-      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
     });
   });
 
@@ -657,19 +350,19 @@
   });
 
   itSub('Should connect and send UNQ to Astar', async () => {
-    await genericSendUnqTo('astar', randomAccount);
+    await testHelper.sendUnqTo('astar', randomAccount);
   });
 
   itSub('Should connect to Astar and send UNQ back', async () => {
-    await genericSendUnqBack('astar', alice, randomAccount);
+    await testHelper.sendUnqBack('astar', alice, randomAccount);
   });
 
   itSub('Astar can send only up to its balance', async () => {
-    await genericSendOnlyOwnedBalance('astar', alice);
+    await testHelper.sendOnlyOwnedBalance('astar', alice);
   });
 
   itSub('Should not accept reserve transfer of UNQ from Astar', async () => {
-    await genericReserveTransferUNQfrom('astar', alice);
+    await testHelper.reserveTransferUNQfrom('astar', alice);
   });
 });
 
modifiedtests/src/xcm/xcm.types.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcm.types.ts
+++ b/tests/src/xcm/xcm.types.ts
@@ -3,7 +3,6 @@
 import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util';
 import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
 import config from '../config';
-import {blake2AsHex} from '@polkadot/util-crypto';
 
 export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
 export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
@@ -136,10 +135,10 @@
 }
 
 export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
-const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
-const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
-const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
-const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
+export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
+export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
+export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
+export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
 
 export class XcmTestHelper {
   private _balanceUniqueTokenInit: bigint = 0n;
@@ -163,6 +162,7 @@
         return UNIQUE_CHAIN;
     }
   }
+
   private _isAddress20FormatFor(network: NetworkNames) {
     switch (network) {
       case 'moonbeam':
@@ -173,6 +173,19 @@
     }
   }
 
+  private _runtimeVersionedMultilocation() {
+    return {
+      V3: {
+        parents: 1,
+        interior: {
+          X1: {
+            Parachain: this._getNativeId(),
+          },
+        },
+      },
+    };
+  }
+
   uniqueChainMultilocationForRelay() {
     return {
       V3: {
@@ -250,8 +263,8 @@
       this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
 
       this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
-      console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(this._unqFees));
-      expect(this._unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
+      console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees));
+      expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true;
 
       await targetPlayground(networkUrl, async (helper) => {
       /*
@@ -302,10 +315,10 @@
 
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);
+          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), xcmProgram);
           xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         } else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), xcmProgram]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
@@ -354,10 +367,10 @@
 
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);
+          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgram);
           maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         } else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgram]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
@@ -413,12 +426,12 @@
       // Try to trick Unique using full UNQ identification
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
+          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);
           maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         }
         // Moonbeam case
         else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);
@@ -436,11 +449,11 @@
       // Try to trick Unique using shortened UNQ identification
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
+          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId);
           maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         }
         else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);
@@ -478,10 +491,10 @@
       );
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
+          await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);
           messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         } else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);
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 {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';19import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';20import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types';21222324const STATEMINE_PALLET_INSTANCE = 50;2526const TRANSFER_AMOUNT = 2000000000000000000000000n;2728const FUNDING_AMOUNT = 3_500_000_0000_000_000n;2930const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;3132const USDT_ASSET_ID = 100;33const USDT_ASSET_METADATA_DECIMALS = 18;34const USDT_ASSET_METADATA_NAME = 'USDT';35const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';36const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;37const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;3839const SAFE_XCM_VERSION = 2;4041describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {42  let alice: IKeyringPair;43  let bob: IKeyringPair;4445  let balanceStmnBefore: bigint;46  let balanceStmnAfter: bigint;4748  let balanceQuartzBefore: bigint;49  let balanceQuartzAfter: bigint;50  let balanceQuartzFinal: bigint;5152  let balanceBobBefore: bigint;53  let balanceBobAfter: bigint;54  let balanceBobFinal: bigint;5556  let balanceBobRelayTokenBefore: bigint;57  let balanceBobRelayTokenAfter: bigint;585960  before(async () => {61    await usingPlaygrounds(async (helper, privateKey) => {62      alice = await privateKey('//Alice');63      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor6465      // Set the default version to wrap the first message to other chains.66      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);67    });6869    await usingRelayPlaygrounds(relayUrl, async (helper) => {70      // Fund accounts on Statemine(t)71      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);72      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);73    });7475    await usingStateminePlaygrounds(statemineUrl, async (helper) => {76      const sovereignFundingAmount = 3_500_000_000n;7778      await helper.assets.create(79        alice,80        USDT_ASSET_ID,81        alice.address,82        USDT_ASSET_METADATA_MINIMAL_BALANCE,83      );84      await helper.assets.setMetadata(85        alice,86        USDT_ASSET_ID,87        USDT_ASSET_METADATA_NAME,88        USDT_ASSET_METADATA_DESCRIPTION,89        USDT_ASSET_METADATA_DECIMALS,90      );91      await helper.assets.mint(92        alice,93        USDT_ASSET_ID,94        alice.address,95        USDT_ASSET_AMOUNT,96      );9798      // funding parachain sovereing account on Statemine(t).99      // The sovereign account should be created before any action100      // (the assets pallet on Statemine(t) check if the sovereign account exists)101      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);102      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);103    });104105106    await usingPlaygrounds(async (helper) => {107      const location = {108        V2: {109          parents: 1,110          interior: {X3: [111            {112              Parachain: STATEMINE_CHAIN,113            },114            {115              PalletInstance: STATEMINE_PALLET_INSTANCE,116            },117            {118              GeneralIndex: USDT_ASSET_ID,119            },120          ]},121        },122      };123124      const metadata =125      {126        name: USDT_ASSET_ID,127        symbol: USDT_ASSET_METADATA_NAME,128        decimals: USDT_ASSET_METADATA_DECIMALS,129        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,130      };131      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);132      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);133    });134135136    // Providing the relay currency to the quartz sender account137    // (fee for USDT XCM are paid in relay tokens)138    await usingRelayPlaygrounds(relayUrl, async (helper) => {139      const destination = {140        V2: {141          parents: 0,142          interior: {X1: {143            Parachain: QUARTZ_CHAIN,144          },145          },146        }};147148      const beneficiary = {149        V2: {150          parents: 0,151          interior: {X1: {152            AccountId32: {153              network: 'Any',154              id: alice.addressRaw,155            },156          }},157        },158      };159160      const assets = {161        V2: [162          {163            id: {164              Concrete: {165                parents: 0,166                interior: 'Here',167              },168            },169            fun: {170              Fungible: TRANSFER_AMOUNT_RELAY,171            },172          },173        ],174      };175176      const feeAssetItem = 0;177178      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');179    });180181  });182183  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {184    await usingStateminePlaygrounds(statemineUrl, async (helper) => {185      const dest = {186        V2: {187          parents: 1,188          interior: {X1: {189            Parachain: QUARTZ_CHAIN,190          },191          },192        }};193194      const beneficiary = {195        V2: {196          parents: 0,197          interior: {X1: {198            AccountId32: {199              network: 'Any',200              id: alice.addressRaw,201            },202          }},203        },204      };205206      const assets = {207        V2: [208          {209            id: {210              Concrete: {211                parents: 0,212                interior: {213                  X2: [214                    {215                      PalletInstance: STATEMINE_PALLET_INSTANCE,216                    },217                    {218                      GeneralIndex: USDT_ASSET_ID,219                    },220                  ]},221              },222            },223            fun: {224              Fungible: TRANSFER_AMOUNT,225            },226          },227        ],228      };229230      const feeAssetItem = 0;231232      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);233      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');234235      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);236237      // common good parachain take commission in it native token238      console.log(239        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',240        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),241      );242      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;243244    });245246247    // ensure that asset has been delivered248    await helper.wait.newBlocks(3);249250    // expext collection id will be with id 1251    const free = await helper.ft.getBalance(1, {Substrate: alice.address});252253    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);254255    console.log(256      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',257      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),258    );259    console.log(260      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',261      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),262    );263    // commission has not paid in USDT token264    expect(free).to.be.equal(TRANSFER_AMOUNT);265    // ... and parachain native token266    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;267  });268269  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {270    const destination = {271      V2: {272        parents: 1,273        interior: {X2: [274          {275            Parachain: STATEMINE_CHAIN,276          },277          {278            AccountId32: {279              network: 'Any',280              id: alice.addressRaw,281            },282          },283        ]},284      },285    };286287    const relayFee = 400_000_000_000_000n;288    const currencies: [any, bigint][] = [289      [290        {291          ForeignAssetId: 0,292        },293        TRANSFER_AMOUNT,294      ],295      [296        {297          NativeAssetId: 'Parent',298        },299        relayFee,300      ],301    ];302303    const feeItem = 1;304305    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');306307    // the commission has been paid in parachain native token308    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);309    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));310    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;311312    await usingStateminePlaygrounds(statemineUrl, async (helper) => {313      await helper.wait.newBlocks(3);314315      // The USDT token never paid fees. Its amount not changed from begin value.316      // Also check that xcm transfer has been succeeded317      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;318    });319  });320321  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {322    balanceBobBefore = await helper.balance.getSubstrate(bob.address);323    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});324325    await usingRelayPlaygrounds(relayUrl, async (helper) => {326      const destination = {327        V2: {328          parents: 0,329          interior: {X1: {330            Parachain: QUARTZ_CHAIN,331          },332          },333        }};334335      const beneficiary = {336        V2: {337          parents: 0,338          interior: {X1: {339            AccountId32: {340              network: 'Any',341              id: bob.addressRaw,342            },343          }},344        },345      };346347      const assets = {348        V2: [349          {350            id: {351              Concrete: {352                parents: 0,353                interior: 'Here',354              },355            },356            fun: {357              Fungible: TRANSFER_AMOUNT_RELAY,358            },359          },360        ],361      };362363      const feeAssetItem = 0;364365      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');366    });367368    await helper.wait.newBlocks(3);369370    balanceBobAfter = await helper.balance.getSubstrate(bob.address);371    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});372373    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;374    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;375    console.log(376      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',377      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),378    );379    console.log(380      '[Relay (Westend) -> Quartz] transaction fees: %s WND',381      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),382    );383    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);384    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;385    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;386  });387388  itSub('Should connect and send Relay token back', async ({helper}) => {389    let relayTokenBalanceBefore: bigint;390    let relayTokenBalanceAfter: bigint;391    await usingRelayPlaygrounds(relayUrl, async (helper) => {392      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);393    });394395    const destination = {396      V2: {397        parents: 1,398        interior: {399          X1:{400            AccountId32: {401              network: 'Any',402              id: bob.addressRaw,403            },404          },405        },406      },407    };408409    const currencies: any = [410      [411        {412          NativeAssetId: 'Parent',413        },414        TRANSFER_AMOUNT_RELAY,415      ],416    ];417418    const feeItem = 0;419420    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');421422    balanceBobFinal = await helper.balance.getSubstrate(bob.address);423    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));424425    await usingRelayPlaygrounds(relayUrl, async (helper) => {426      await helper.wait.newBlocks(10);427      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);428429      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;430      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));431      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;432    });433  });434});435436describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {437  let alice: IKeyringPair;438  let randomAccount: IKeyringPair;439440  let balanceQuartzTokenInit: bigint;441  let balanceQuartzTokenMiddle: bigint;442  let balanceQuartzTokenFinal: bigint;443  let balanceKaruraTokenInit: bigint;444  let balanceKaruraTokenMiddle: bigint;445  let balanceKaruraTokenFinal: bigint;446  let balanceQuartzForeignTokenInit: bigint;447  let balanceQuartzForeignTokenMiddle: bigint;448  let balanceQuartzForeignTokenFinal: bigint;449450  // computed by a test transfer from prod Quartz to prod Karura.451  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9452  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)453  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;454  const karuraEps = 8n * 10n ** 16n;455456  let karuraBackwardTransferAmount: bigint;457458  before(async () => {459    await usingPlaygrounds(async (helper, privateKey) => {460      alice = await privateKey('//Alice');461      [randomAccount] = await helper.arrange.createAccounts([0n], alice);462463      // Set the default version to wrap the first message to other chains.464      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);465    });466467    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {468      const destination = {469        V2: {470          parents: 1,471          interior: {472            X1: {473              Parachain: QUARTZ_CHAIN,474            },475          },476        },477      };478479      const metadata = {480        name: 'Quartz',481        symbol: 'QTZ',482        decimals: 18,483        minimalBalance: 1000000000000000000n,484      };485486      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);487      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);488      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);489      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});490    });491492    await usingPlaygrounds(async (helper) => {493      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);494      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);495    });496  });497498  itSub('Should connect and send QTZ to Karura', async ({helper}) => {499    const destination = {500      V2: {501        parents: 1,502        interior: {503          X1: {504            Parachain: KARURA_CHAIN,505          },506        },507      },508    };509510    const beneficiary = {511      V2: {512        parents: 0,513        interior: {514          X1: {515            AccountId32: {516              network: 'Any',517              id: randomAccount.addressRaw,518            },519          },520        },521      },522    };523524    const assets = {525      V2: [526        {527          id: {528            Concrete: {529              parents: 0,530              interior: 'Here',531            },532          },533          fun: {534            Fungible: TRANSFER_AMOUNT,535          },536        },537      ],538    };539540    const feeAssetItem = 0;541542    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');543    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);544545    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;546    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;547    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));548549    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {550      await helper.wait.newBlocks(3);551552      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});553      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);554555      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;556      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;557      karuraBackwardTransferAmount = qtzIncomeTransfer;558559      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;560561      console.log(562        '[Quartz -> Karura] transaction fees on Karura: %s KAR',563        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),564      );565      console.log(566        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',567        helper.util.bigIntToDecimals(karUnqFees),568      );569      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));570      expect(karFees == 0n).to.be.true;571572      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;573574      expect(575        bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,576        'Karura took different income fee, check the Karura foreign asset config',577      ).to.be.true;578    });579  });580581  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {582    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {583      const destination = {584        V2: {585          parents: 1,586          interior: {587            X2: [588              {Parachain: QUARTZ_CHAIN},589              {590                AccountId32: {591                  network: 'Any',592                  id: randomAccount.addressRaw,593                },594              },595            ],596          },597        },598      };599600      const id = {601        ForeignAsset: 0,602      };603604      await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited');605      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);606      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);607608      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;609      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;610611      console.log(612        '[Karura -> Quartz] transaction fees on Karura: %s KAR',613        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),614      );615      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));616617      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;618      expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true;619    });620621    await helper.wait.newBlocks(3);622623    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);624    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;625    expect(actuallyDelivered > 0).to.be.true;626627    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));628629    const qtzFees = karuraBackwardTransferAmount - actuallyDelivered;630    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));631    expect(qtzFees == 0n).to.be.true;632  });633634  itSub('Karura can send only up to its balance', async ({helper}) => {635    // set Karura's sovereign account's balance636    const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);637    const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);638    await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);639640    const moreThanKaruraHas = karuraBalance * 2n;641642    let targetAccountBalance = 0n;643    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);644645    const quartzMultilocation = {646      V2: {647        parents: 1,648        interior: {649          X1: {Parachain: QUARTZ_CHAIN},650        },651      },652    };653654    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(655      targetAccount.addressRaw,656      {657        Concrete: {658          parents: 0,659          interior: 'Here',660        },661      },662      moreThanKaruraHas,663    );664665    let maliciousXcmProgramSent: any;666    const maxWaitBlocks = 5;667668    // Try to trick Quartz669    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {670      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);671672      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);673    });674675    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash676        && event.outcome.isFailedToTransactAsset);677678    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);679    expect(targetAccountBalance).to.be.equal(0n);680681    // But Karura still can send the correct amount682    const validTransferAmount = karuraBalance / 2n;683    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(684      targetAccount.addressRaw,685      {686        Concrete: {687          parents: 0,688          interior: 'Here',689        },690      },691      validTransferAmount,692    );693694    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {695      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);696    });697698    await helper.wait.newBlocks(maxWaitBlocks);699700    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);701    expect(targetAccountBalance).to.be.equal(validTransferAmount);702  });703704  itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {705    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);706    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);707708    const quartzMultilocation = {709      V2: {710        parents: 1,711        interior: {712          X1: {713            Parachain: QUARTZ_CHAIN,714          },715        },716      },717    };718719    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(720      targetAccount.addressRaw,721      {722        Concrete: {723          parents: 1,724          interior: {725            X1: {726              Parachain: QUARTZ_CHAIN,727            },728          },729        },730      },731      testAmount,732    );733734    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(735      targetAccount.addressRaw,736      {737        Concrete: {738          parents: 0,739          interior: 'Here',740        },741      },742      testAmount,743    );744745    let maliciousXcmProgramFullIdSent: any;746    let maliciousXcmProgramHereIdSent: any;747    const maxWaitBlocks = 3;748749    // Try to trick Quartz using full QTZ identification750    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {751      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);752753      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);754    });755756    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash757        && event.outcome.isUntrustedReserveLocation);758759    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);760    expect(accountBalance).to.be.equal(0n);761762    // Try to trick Quartz using shortened QTZ identification763    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {764      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);765766      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);767    });768769    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash770        && event.outcome.isUntrustedReserveLocation);771772    accountBalance = await helper.balance.getSubstrate(targetAccount.address);773    expect(accountBalance).to.be.equal(0n);774  });775});776777// These tests are relevant only when778// the the corresponding foreign assets are not registered779describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {780  let alice: IKeyringPair;781  let alith: IKeyringPair;782783  const testAmount = 100_000_000_000n;784  let quartzParachainJunction;785  let quartzAccountJunction;786787  let quartzParachainMultilocation: any;788  let quartzAccountMultilocation: any;789  let quartzCombinedMultilocation: any;790791  let messageSent: any;792793  const maxWaitBlocks = 3;794795  before(async () => {796    await usingPlaygrounds(async (helper, privateKey) => {797      alice = await privateKey('//Alice');798799      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};800      quartzAccountJunction = {801        AccountId32: {802          network: 'Any',803          id: alice.addressRaw,804        },805      };806807      quartzParachainMultilocation = {808        V2: {809          parents: 1,810          interior: {811            X1: quartzParachainJunction,812          },813        },814      };815816      quartzAccountMultilocation = {817        V2: {818          parents: 0,819          interior: {820            X1: quartzAccountJunction,821          },822        },823      };824825      quartzCombinedMultilocation = {826        V2: {827          parents: 1,828          interior: {829            X2: [quartzParachainJunction, quartzAccountJunction],830          },831        },832      };833834      // Set the default version to wrap the first message to other chains.835      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);836    });837838    // eslint-disable-next-line require-await839    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {840      alith = helper.account.alithAccount();841    });842  });843844  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {845    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash846        && event.outcome.isFailedToTransactAsset);847  };848849  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {850    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {851      const id = {852        Token: 'KAR',853      };854      const destination = quartzCombinedMultilocation;855      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');856857      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);858    });859860    await expectFailedToTransact(helper, messageSent);861  });862863  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {864    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {865      const id = 'SelfReserve';866      const destination = quartzCombinedMultilocation;867      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');868869      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);870    });871872    await expectFailedToTransact(helper, messageSent);873  });874875  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {876    await usingShidenPlaygrounds(shidenUrl, async (helper) => {877      const destinationParachain = quartzParachainMultilocation;878      const beneficiary = quartzAccountMultilocation;879      const assets = {880        V2: [{881          id: {882            Concrete: {883              parents: 0,884              interior: 'Here',885            },886          },887          fun: {888            Fungible: testAmount,889          },890        }],891      };892      const feeAssetItem = 0;893894      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [895        destinationParachain,896        beneficiary,897        assets,898        feeAssetItem,899      ]);900901      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);902    });903904    await expectFailedToTransact(helper, messageSent);905  });906});907908describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {909  // Quartz constants910  let alice: IKeyringPair;911  let quartzAssetLocation;912913  let randomAccountQuartz: IKeyringPair;914  let randomAccountMoonriver: IKeyringPair;915916  // Moonriver constants917  let assetId: string;918919  const quartzAssetMetadata = {920    name: 'xcQuartz',921    symbol: 'xcQTZ',922    decimals: 18,923    isFrozen: false,924    minimalBalance: 1n,925  };926927  let balanceQuartzTokenInit: bigint;928  let balanceQuartzTokenMiddle: bigint;929  let balanceQuartzTokenFinal: bigint;930  let balanceForeignQtzTokenInit: bigint;931  let balanceForeignQtzTokenMiddle: bigint;932  let balanceForeignQtzTokenFinal: bigint;933  let balanceMovrTokenInit: bigint;934  let balanceMovrTokenMiddle: bigint;935  let balanceMovrTokenFinal: bigint;936937  before(async () => {938    await usingPlaygrounds(async (helper, privateKey) => {939      alice = await privateKey('//Alice');940      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);941942      balanceForeignQtzTokenInit = 0n;943944      // Set the default version to wrap the first message to other chains.945      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);946    });947948    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {949      const alithAccount = helper.account.alithAccount();950      const baltatharAccount = helper.account.baltatharAccount();951      const dorothyAccount = helper.account.dorothyAccount();952953      randomAccountMoonriver = helper.account.create();954955      // >>> Sponsoring Dorothy >>>956      console.log('Sponsoring Dorothy.......');957      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);958      console.log('Sponsoring Dorothy.......DONE');959      // <<< Sponsoring Dorothy <<<960961      quartzAssetLocation = {962        XCM: {963          parents: 1,964          interior: {X1: {Parachain: QUARTZ_CHAIN}},965        },966      };967      const existentialDeposit = 1n;968      const isSufficient = true;969      const unitsPerSecond = 1n;970      const numAssetsWeightHint = 0;971972      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({973        location: quartzAssetLocation,974        metadata: quartzAssetMetadata,975        existentialDeposit,976        isSufficient,977        unitsPerSecond,978        numAssetsWeightHint,979      });980981      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);982983      await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);984985      // >>> Acquire Quartz AssetId Info on Moonriver >>>986      console.log('Acquire Quartz AssetId Info on Moonriver.......');987988      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();989990      console.log('QTZ asset ID is %s', assetId);991      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');992      // >>> Acquire Quartz AssetId Info on Moonriver >>>993994      // >>> Sponsoring random Account >>>995      console.log('Sponsoring random Account.......');996      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);997      console.log('Sponsoring random Account.......DONE');998      // <<< Sponsoring random Account <<<9991000      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1001    });10021003    await usingPlaygrounds(async (helper) => {1004      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1005      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1006    });1007  });10081009  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1010    const currencyId = {1011      NativeAssetId: 'Here',1012    };1013    const dest = {1014      V2: {1015        parents: 1,1016        interior: {1017          X2: [1018            {Parachain: MOONRIVER_CHAIN},1019            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1020          ],1021        },1022      },1023    };1024    const amount = TRANSFER_AMOUNT;10251026    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10271028    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1029    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10301031    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1032    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1033    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10341035    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1036      await helper.wait.newBlocks(3);10371038      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10391040      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1041      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1042      expect(movrFees == 0n).to.be.true;10431044      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1045      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1046      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1047      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1048    });1049  });10501051  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1052    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1053      const asset = {1054        V2: {1055          id: {1056            Concrete: {1057              parents: 1,1058              interior: {1059                X1: {Parachain: QUARTZ_CHAIN},1060              },1061            },1062          },1063          fun: {1064            Fungible: TRANSFER_AMOUNT,1065          },1066        },1067      };1068      const destination = {1069        V2: {1070          parents: 1,1071          interior: {1072            X2: [1073              {Parachain: QUARTZ_CHAIN},1074              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1075            ],1076          },1077        },1078      };10791080      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10811082      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10831084      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1085      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1086      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10871088      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);10891090      expect(qtzRandomAccountAsset).to.be.null;10911092      balanceForeignQtzTokenFinal = 0n;10931094      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1095      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1096      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1097    });10981099    await helper.wait.newBlocks(3);11001101    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1102    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1103    expect(actuallyDelivered > 0).to.be.true;11041105    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11061107    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1108    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1109    expect(qtzFees == 0n).to.be.true;1110  });11111112  itSub('Moonriver can send only up to its balance', async ({helper}) => {1113    // set Moonriver's sovereign account's balance1114    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1115    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1116    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11171118    const moreThanMoonriverHas = moonriverBalance * 2n;11191120    let targetAccountBalance = 0n;1121    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11221123    const quartzMultilocation = {1124      V2: {1125        parents: 1,1126        interior: {1127          X1: {Parachain: QUARTZ_CHAIN},1128        },1129      },1130    };11311132    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1133      targetAccount.addressRaw,1134      {1135        Concrete: {1136          parents: 0,1137          interior: 'Here',1138        },1139      },1140      moreThanMoonriverHas,1141    );11421143    let maliciousXcmProgramSent: any;1144    const maxWaitBlocks = 3;11451146    // Try to trick Quartz1147    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1148      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11491150      // Needed to bypass the call filter.1151      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1152      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11531154      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1155    });11561157    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1158        && event.outcome.isFailedToTransactAsset);11591160    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1161    expect(targetAccountBalance).to.be.equal(0n);11621163    // But Moonriver still can send the correct amount1164    const validTransferAmount = moonriverBalance / 2n;1165    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1166      targetAccount.addressRaw,1167      {1168        Concrete: {1169          parents: 0,1170          interior: 'Here',1171        },1172      },1173      validTransferAmount,1174    );11751176    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1177      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11781179      // Needed to bypass the call filter.1180      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1181      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1182    });11831184    await helper.wait.newBlocks(maxWaitBlocks);11851186    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1187    expect(targetAccountBalance).to.be.equal(validTransferAmount);1188  });11891190  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1191    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1192    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);11931194    const quartzMultilocation = {1195      V2: {1196        parents: 1,1197        interior: {1198          X1: {1199            Parachain: QUARTZ_CHAIN,1200          },1201        },1202      },1203    };12041205    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1206      targetAccount.addressRaw,1207      {1208        Concrete: {1209          parents: 0,1210          interior: {1211            X1: {1212              Parachain: QUARTZ_CHAIN,1213            },1214          },1215        },1216      },1217      testAmount,1218    );12191220    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1221      targetAccount.addressRaw,1222      {1223        Concrete: {1224          parents: 0,1225          interior: 'Here',1226        },1227      },1228      testAmount,1229    );12301231    let maliciousXcmProgramFullIdSent: any;1232    let maliciousXcmProgramHereIdSent: any;1233    const maxWaitBlocks = 3;12341235    // Try to trick Quartz using full QTZ identification1236    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1237      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12381239      // Needed to bypass the call filter.1240      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1241      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12421243      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1244    });12451246    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1247        && event.outcome.isUntrustedReserveLocation);12481249    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1250    expect(accountBalance).to.be.equal(0n);12511252    // Try to trick Quartz using shortened QTZ identification1253    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1254      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12551256      // Needed to bypass the call filter.1257      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1258      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12591260      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1261    });12621263    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1264        && event.outcome.isUntrustedReserveLocation);12651266    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1267    expect(accountBalance).to.be.equal(0n);1268  });1269});12701271describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1272  let alice: IKeyringPair;1273  let sender: IKeyringPair;12741275  const QTZ_ASSET_ID_ON_SHIDEN = 1;1276  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12771278  // Quartz -> Shiden1279  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1280  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1281  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1282  const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens12831284  // Shiden -> Quartz1285  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1286  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ12871288  let balanceAfterQuartzToShidenXCM: bigint;12891290  before(async () => {1291    await usingPlaygrounds(async (helper, privateKey) => {1292      alice = await privateKey('//Alice');1293      [sender] = await helper.arrange.createAccounts([100n], alice);1294      console.log('sender', sender.address);12951296      // Set the default version to wrap the first message to other chains.1297      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1298    });12991300    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1301      console.log('1. Create foreign asset and metadata');1302      // TODO update metadata with values from production1303      await helper.assets.create(1304        alice,1305        QTZ_ASSET_ID_ON_SHIDEN,1306        alice.address,1307        QTZ_MINIMAL_BALANCE_ON_SHIDEN,1308      );13091310      await helper.assets.setMetadata(1311        alice,1312        QTZ_ASSET_ID_ON_SHIDEN,1313        'Cross chain QTZ',1314        'xcQTZ',1315        Number(QTZ_DECIMALS),1316      );13171318      console.log('2. Register asset location on Shiden');1319      const assetLocation = {1320        V2: {1321          parents: 1,1322          interior: {1323            X1: {1324              Parachain: QUARTZ_CHAIN,1325            },1326          },1327        },1328      };13291330      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13311332      console.log('3. Set QTZ payment for XCM execution on Shiden');1333      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13341335      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1336      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1337    });1338  });13391340  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1341    const destination = {1342      V2: {1343        parents: 1,1344        interior: {1345          X1: {1346            Parachain: SHIDEN_CHAIN,1347          },1348        },1349      },1350    };13511352    const beneficiary = {1353      V2: {1354        parents: 0,1355        interior: {1356          X1: {1357            AccountId32: {1358              network: 'Any',1359              id: sender.addressRaw,1360            },1361          },1362        },1363      },1364    };13651366    const assets = {1367      V2: [1368        {1369          id: {1370            Concrete: {1371              parents: 0,1372              interior: 'Here',1373            },1374          },1375          fun: {1376            Fungible: qtzToShidenTransferred,1377          },1378        },1379      ],1380    };13811382    // Initial balance is 100 QTZ1383    const balanceBefore = await helper.balance.getSubstrate(sender.address);1384    console.log(`Initial balance is: ${balanceBefore}`);13851386    const feeAssetItem = 0;1387    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');13881389    // Balance after reserve transfer is less than 901390    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1391    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1392    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1393    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;13941395    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1396      await helper.wait.newBlocks(3);1397      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1398      const shidenBalance = await helper.balance.getSubstrate(sender.address);13991400      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1401      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14021403      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1404      // SHD balance does not changed:1405      expect(shidenBalance).to.eq(shidenInitialBalance);1406    });1407  });14081409  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1410    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1411      const destination = {1412        V2: {1413          parents: 1,1414          interior: {1415            X1: {1416              Parachain: QUARTZ_CHAIN,1417            },1418          },1419        },1420      };14211422      const beneficiary = {1423        V2: {1424          parents: 0,1425          interior: {1426            X1: {1427              AccountId32: {1428                network: 'Any',1429                id: sender.addressRaw,1430              },1431            },1432          },1433        },1434      };14351436      const assets = {1437        V2: [1438          {1439            id: {1440              Concrete: {1441                parents: 1,1442                interior: {1443                  X1: {1444                    Parachain: QUARTZ_CHAIN,1445                  },1446                },1447              },1448            },1449            fun: {1450              Fungible: qtzFromShidenTransfered,1451            },1452          },1453        ],1454      };14551456      // Initial balance is 1 SDN1457      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1458      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1459      expect(balanceSDNbefore).to.eq(shidenInitialBalance);14601461      const feeAssetItem = 0;1462      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1463      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14641465      // Balance after reserve transfer is less than 1 SDN1466      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1467      const balanceSDN = await helper.balance.getSubstrate(sender.address);1468      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14691470      // Assert: xcQTZ balance correctly decreased1471      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1472      // Assert: SDN balance is 0.996...1473      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1474    });14751476    await helper.wait.newBlocks(3);1477    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1478    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1479    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1480  });14811482  itSub('Shiden can send only up to its balance', async ({helper}) => {1483    // set Shiden's sovereign account's balance1484    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1485    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1486    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);14871488    const moreThanShidenHas = shidenBalance * 2n;14891490    let targetAccountBalance = 0n;1491    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14921493    const quartzMultilocation = {1494      V2: {1495        parents: 1,1496        interior: {1497          X1: {Parachain: QUARTZ_CHAIN},1498        },1499      },1500    };15011502    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1503      targetAccount.addressRaw,1504      {1505        Concrete: {1506          parents: 0,1507          interior: 'Here',1508        },1509      },1510      moreThanShidenHas,1511    );15121513    let maliciousXcmProgramSent: any;1514    const maxWaitBlocks = 3;15151516    // Try to trick Quartz1517    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1518      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15191520      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1521    });15221523    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1524        && event.outcome.isFailedToTransactAsset);15251526    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1527    expect(targetAccountBalance).to.be.equal(0n);15281529    // But Shiden still can send the correct amount1530    const validTransferAmount = shidenBalance / 2n;1531    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1532      targetAccount.addressRaw,1533      {1534        Concrete: {1535          parents: 0,1536          interior: 'Here',1537        },1538      },1539      validTransferAmount,1540    );15411542    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1543      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1544    });15451546    await helper.wait.newBlocks(maxWaitBlocks);15471548    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1549    expect(targetAccountBalance).to.be.equal(validTransferAmount);1550  });15511552  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1553    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1554    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15551556    const quartzMultilocation = {1557      V2: {1558        parents: 1,1559        interior: {1560          X1: {1561            Parachain: QUARTZ_CHAIN,1562          },1563        },1564      },1565    };15661567    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1568      targetAccount.addressRaw,1569      {1570        Concrete: {1571          parents: 1,1572          interior: {1573            X1: {1574              Parachain: QUARTZ_CHAIN,1575            },1576          },1577        },1578      },1579      testAmount,1580    );15811582    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1583      targetAccount.addressRaw,1584      {1585        Concrete: {1586          parents: 0,1587          interior: 'Here',1588        },1589      },1590      testAmount,1591    );15921593    let maliciousXcmProgramFullIdSent: any;1594    let maliciousXcmProgramHereIdSent: any;1595    const maxWaitBlocks = 3;15961597    // Try to trick Quartz using full QTZ identification1598    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1599      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16001601      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1602    });16031604    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1605        && event.outcome.isUntrustedReserveLocation);16061607    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1608    expect(accountBalance).to.be.equal(0n);16091610    // Try to trick Quartz using shortened QTZ identification1611    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1612      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16131614      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1615    });16161617    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1618        && event.outcome.isUntrustedReserveLocation);16191620    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1621    expect(accountBalance).to.be.equal(0n);1622  });1623});
after · tests/src/xcm/xcmQuartz.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';19import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';20import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types';21import {hexToString} from '@polkadot/util';22232425const STATEMINE_PALLET_INSTANCE = 50;2627const TRANSFER_AMOUNT = 2000000000000000000000000n;2829const FUNDING_AMOUNT = 3_500_000_0000_000_000n;3031const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;3233const USDT_ASSET_ID = 100;34const USDT_ASSET_METADATA_DECIMALS = 18;35const USDT_ASSET_METADATA_NAME = 'USDT';36const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';37const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;38const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;3940const SAFE_XCM_VERSION = 2;4142describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {43  let alice: IKeyringPair;44  let bob: IKeyringPair;4546  let balanceStmnBefore: bigint;47  let balanceStmnAfter: bigint;4849  let balanceQuartzBefore: bigint;50  let balanceQuartzAfter: bigint;51  let balanceQuartzFinal: bigint;5253  let balanceBobBefore: bigint;54  let balanceBobAfter: bigint;55  let balanceBobFinal: bigint;5657  let balanceBobRelayTokenBefore: bigint;58  let balanceBobRelayTokenAfter: bigint;596061  before(async () => {62    await usingPlaygrounds(async (helper, privateKey) => {63      alice = await privateKey('//Alice');64      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor6566      // Set the default version to wrap the first message to other chains.67      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);68    });6970    await usingRelayPlaygrounds(relayUrl, async (helper) => {71      // Fund accounts on Statemine(t)72      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);73      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);74    });7576    await usingStateminePlaygrounds(statemineUrl, async (helper) => {77      const sovereignFundingAmount = 3_500_000_000n;7879      await helper.assets.create(80        alice,81        USDT_ASSET_ID,82        alice.address,83        USDT_ASSET_METADATA_MINIMAL_BALANCE,84      );85      await helper.assets.setMetadata(86        alice,87        USDT_ASSET_ID,88        USDT_ASSET_METADATA_NAME,89        USDT_ASSET_METADATA_DESCRIPTION,90        USDT_ASSET_METADATA_DECIMALS,91      );92      await helper.assets.mint(93        alice,94        USDT_ASSET_ID,95        alice.address,96        USDT_ASSET_AMOUNT,97      );9899      // funding parachain sovereing account on Statemine(t).100      // The sovereign account should be created before any action101      // (the assets pallet on Statemine(t) check if the sovereign account exists)102      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);103      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);104    });105106107    await usingPlaygrounds(async (helper) => {108      const location = {109        V2: {110          parents: 1,111          interior: {X3: [112            {113              Parachain: STATEMINE_CHAIN,114            },115            {116              PalletInstance: STATEMINE_PALLET_INSTANCE,117            },118            {119              GeneralIndex: USDT_ASSET_ID,120            },121          ]},122        },123      };124125      const metadata =126      {127        name: USDT_ASSET_ID,128        symbol: USDT_ASSET_METADATA_NAME,129        decimals: USDT_ASSET_METADATA_DECIMALS,130        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,131      };132      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);133      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);134    });135136137    // Providing the relay currency to the quartz sender account138    // (fee for USDT XCM are paid in relay tokens)139    await usingRelayPlaygrounds(relayUrl, async (helper) => {140      const destination = {141        V2: {142          parents: 0,143          interior: {X1: {144            Parachain: QUARTZ_CHAIN,145          },146          },147        }};148149      const beneficiary = {150        V2: {151          parents: 0,152          interior: {X1: {153            AccountId32: {154              network: 'Any',155              id: alice.addressRaw,156            },157          }},158        },159      };160161      const assets = {162        V2: [163          {164            id: {165              Concrete: {166                parents: 0,167                interior: 'Here',168              },169            },170            fun: {171              Fungible: TRANSFER_AMOUNT_RELAY,172            },173          },174        ],175      };176177      const feeAssetItem = 0;178179      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');180    });181182  });183184  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {185    await usingStateminePlaygrounds(statemineUrl, async (helper) => {186      const dest = {187        V2: {188          parents: 1,189          interior: {X1: {190            Parachain: QUARTZ_CHAIN,191          },192          },193        }};194195      const beneficiary = {196        V2: {197          parents: 0,198          interior: {X1: {199            AccountId32: {200              network: 'Any',201              id: alice.addressRaw,202            },203          }},204        },205      };206207      const assets = {208        V2: [209          {210            id: {211              Concrete: {212                parents: 0,213                interior: {214                  X2: [215                    {216                      PalletInstance: STATEMINE_PALLET_INSTANCE,217                    },218                    {219                      GeneralIndex: USDT_ASSET_ID,220                    },221                  ]},222              },223            },224            fun: {225              Fungible: TRANSFER_AMOUNT,226            },227          },228        ],229      };230231      const feeAssetItem = 0;232233      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);234      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');235236      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);237238      // common good parachain take commission in it native token239      console.log(240        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',241        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),242      );243      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;244245    });246247248    // ensure that asset has been delivered249    await helper.wait.newBlocks(3);250251    // expext collection id will be with id 1252    const free = await helper.ft.getBalance(1, {Substrate: alice.address});253254    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);255256    console.log(257      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',258      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),259    );260    console.log(261      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',262      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),263    );264    // commission has not paid in USDT token265    expect(free).to.be.equal(TRANSFER_AMOUNT);266    // ... and parachain native token267    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;268  });269270  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {271    const destination = {272      V2: {273        parents: 1,274        interior: {X2: [275          {276            Parachain: STATEMINE_CHAIN,277          },278          {279            AccountId32: {280              network: 'Any',281              id: alice.addressRaw,282            },283          },284        ]},285      },286    };287288    const relayFee = 400_000_000_000_000n;289    const currencies: [any, bigint][] = [290      [291        {292          ForeignAssetId: 0,293        },294        TRANSFER_AMOUNT,295      ],296      [297        {298          NativeAssetId: 'Parent',299        },300        relayFee,301      ],302    ];303304    const feeItem = 1;305306    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');307308    // the commission has been paid in parachain native token309    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);310    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));311    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;312313    await usingStateminePlaygrounds(statemineUrl, async (helper) => {314      await helper.wait.newBlocks(3);315316      // The USDT token never paid fees. Its amount not changed from begin value.317      // Also check that xcm transfer has been succeeded318      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;319    });320  });321322  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {323    balanceBobBefore = await helper.balance.getSubstrate(bob.address);324    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});325326    await usingRelayPlaygrounds(relayUrl, async (helper) => {327      const destination = {328        V2: {329          parents: 0,330          interior: {X1: {331            Parachain: QUARTZ_CHAIN,332          },333          },334        }};335336      const beneficiary = {337        V2: {338          parents: 0,339          interior: {X1: {340            AccountId32: {341              network: 'Any',342              id: bob.addressRaw,343            },344          }},345        },346      };347348      const assets = {349        V2: [350          {351            id: {352              Concrete: {353                parents: 0,354                interior: 'Here',355              },356            },357            fun: {358              Fungible: TRANSFER_AMOUNT_RELAY,359            },360          },361        ],362      };363364      const feeAssetItem = 0;365366      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');367    });368369    await helper.wait.newBlocks(3);370371    balanceBobAfter = await helper.balance.getSubstrate(bob.address);372    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});373374    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;375    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;376    console.log(377      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',378      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),379    );380    console.log(381      '[Relay (Westend) -> Quartz] transaction fees: %s WND',382      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),383    );384    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);385    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;386    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;387  });388389  itSub('Should connect and send Relay token back', async ({helper}) => {390    let relayTokenBalanceBefore: bigint;391    let relayTokenBalanceAfter: bigint;392    await usingRelayPlaygrounds(relayUrl, async (helper) => {393      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);394    });395396    const destination = {397      V2: {398        parents: 1,399        interior: {400          X1:{401            AccountId32: {402              network: 'Any',403              id: bob.addressRaw,404            },405          },406        },407      },408    };409410    const currencies: any = [411      [412        {413          NativeAssetId: 'Parent',414        },415        TRANSFER_AMOUNT_RELAY,416      ],417    ];418419    const feeItem = 0;420421    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');422423    balanceBobFinal = await helper.balance.getSubstrate(bob.address);424    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));425426    await usingRelayPlaygrounds(relayUrl, async (helper) => {427      await helper.wait.newBlocks(10);428      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);429430      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;431      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));432      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;433    });434  });435});436437describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {438  let alice: IKeyringPair;439  let randomAccount: IKeyringPair;440441  let balanceQuartzTokenInit: bigint;442  let balanceQuartzTokenMiddle: bigint;443  let balanceQuartzTokenFinal: bigint;444  let balanceKaruraTokenInit: bigint;445  let balanceKaruraTokenMiddle: bigint;446  let balanceKaruraTokenFinal: bigint;447  let balanceQuartzForeignTokenInit: bigint;448  let balanceQuartzForeignTokenMiddle: bigint;449  let balanceQuartzForeignTokenFinal: bigint;450451  // computed by a test transfer from prod Quartz to prod Karura.452  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9453  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)454  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;455  const karuraEps = 8n * 10n ** 16n;456457  let karuraBackwardTransferAmount: bigint;458459  before(async () => {460    await usingPlaygrounds(async (helper, privateKey) => {461      alice = await privateKey('//Alice');462      [randomAccount] = await helper.arrange.createAccounts([0n], alice);463464      // Set the default version to wrap the first message to other chains.465      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);466    });467468    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {469      const destination = {470        V2: {471          parents: 1,472          interior: {473            X1: {474              Parachain: QUARTZ_CHAIN,475            },476          },477        },478      };479480      const metadata = {481        name: 'Quartz',482        symbol: 'QTZ',483        decimals: 18,484        minimalBalance: 1000000000000000000n,485      };486487      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>488        hexToString(v.toJSON()['symbol'])) as string[];489490      if(!assets.includes('QTZ')) {491        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);492      } else {493        console.log('QTZ token already registered on Karura assetRegistry pallet');494      }495      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);496      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);497      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});498    });499500    await usingPlaygrounds(async (helper) => {501      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);502      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);503    });504  });505506  itSub('Should connect and send QTZ to Karura', async ({helper}) => {507    const destination = {508      V2: {509        parents: 1,510        interior: {511          X1: {512            Parachain: KARURA_CHAIN,513          },514        },515      },516    };517518    const beneficiary = {519      V2: {520        parents: 0,521        interior: {522          X1: {523            AccountId32: {524              network: 'Any',525              id: randomAccount.addressRaw,526            },527          },528        },529      },530    };531532    const assets = {533      V2: [534        {535          id: {536            Concrete: {537              parents: 0,538              interior: 'Here',539            },540          },541          fun: {542            Fungible: TRANSFER_AMOUNT,543          },544        },545      ],546    };547548    const feeAssetItem = 0;549550    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');551    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);552553    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;554    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;555    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));556557    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {558      await helper.wait.newBlocks(3);559560      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});561      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);562563      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;564      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;565      karuraBackwardTransferAmount = qtzIncomeTransfer;566567      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;568569      console.log(570        '[Quartz -> Karura] transaction fees on Karura: %s KAR',571        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),572      );573      console.log(574        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',575        helper.util.bigIntToDecimals(karUnqFees),576      );577      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));578      expect(karFees == 0n).to.be.true;579580      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;581582      expect(583        bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,584        'Karura took different income fee, check the Karura foreign asset config',585      ).to.be.true;586    });587  });588589  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {590    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {591      const destination = {592        V2: {593          parents: 1,594          interior: {595            X2: [596              {Parachain: QUARTZ_CHAIN},597              {598                AccountId32: {599                  network: 'Any',600                  id: randomAccount.addressRaw,601                },602              },603            ],604          },605        },606      };607608      const id = {609        ForeignAsset: 0,610      };611612      await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited');613      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);614      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);615616      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;617      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;618619      console.log(620        '[Karura -> Quartz] transaction fees on Karura: %s KAR',621        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),622      );623      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));624625      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;626      expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true;627    });628629    await helper.wait.newBlocks(3);630631    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);632    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;633    expect(actuallyDelivered > 0).to.be.true;634635    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));636637    const qtzFees = karuraBackwardTransferAmount - actuallyDelivered;638    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));639    expect(qtzFees == 0n).to.be.true;640  });641642  itSub('Karura can send only up to its balance', async ({helper}) => {643    // set Karura's sovereign account's balance644    const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);645    const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);646    await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);647648    const moreThanKaruraHas = karuraBalance * 2n;649650    let targetAccountBalance = 0n;651    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);652653    const quartzMultilocation = {654      V2: {655        parents: 1,656        interior: {657          X1: {Parachain: QUARTZ_CHAIN},658        },659      },660    };661662    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(663      targetAccount.addressRaw,664      {665        Concrete: {666          parents: 0,667          interior: 'Here',668        },669      },670      moreThanKaruraHas,671    );672673    let maliciousXcmProgramSent: any;674    const maxWaitBlocks = 5;675676    // Try to trick Quartz677    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {678      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);679680      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);681    });682683    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash684        && event.outcome.isFailedToTransactAsset);685686    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);687    expect(targetAccountBalance).to.be.equal(0n);688689    // But Karura still can send the correct amount690    const validTransferAmount = karuraBalance / 2n;691    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(692      targetAccount.addressRaw,693      {694        Concrete: {695          parents: 0,696          interior: 'Here',697        },698      },699      validTransferAmount,700    );701702    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {703      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);704    });705706    await helper.wait.newBlocks(maxWaitBlocks);707708    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);709    expect(targetAccountBalance).to.be.equal(validTransferAmount);710  });711712  itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {713    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);714    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);715716    const quartzMultilocation = {717      V2: {718        parents: 1,719        interior: {720          X1: {721            Parachain: QUARTZ_CHAIN,722          },723        },724      },725    };726727    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(728      targetAccount.addressRaw,729      {730        Concrete: {731          parents: 1,732          interior: {733            X1: {734              Parachain: QUARTZ_CHAIN,735            },736          },737        },738      },739      testAmount,740    );741742    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(743      targetAccount.addressRaw,744      {745        Concrete: {746          parents: 0,747          interior: 'Here',748        },749      },750      testAmount,751    );752753    let maliciousXcmProgramFullIdSent: any;754    let maliciousXcmProgramHereIdSent: any;755    const maxWaitBlocks = 3;756757    // Try to trick Quartz using full QTZ identification758    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {759      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);760761      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);762    });763764    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash765        && event.outcome.isUntrustedReserveLocation);766767    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);768    expect(accountBalance).to.be.equal(0n);769770    // Try to trick Quartz using shortened QTZ identification771    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {772      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);773774      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);775    });776777    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash778        && event.outcome.isUntrustedReserveLocation);779780    accountBalance = await helper.balance.getSubstrate(targetAccount.address);781    expect(accountBalance).to.be.equal(0n);782  });783});784785// These tests are relevant only when786// the the corresponding foreign assets are not registered787describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {788  let alice: IKeyringPair;789  let alith: IKeyringPair;790791  const testAmount = 100_000_000_000n;792  let quartzParachainJunction;793  let quartzAccountJunction;794795  let quartzParachainMultilocation: any;796  let quartzAccountMultilocation: any;797  let quartzCombinedMultilocation: any;798799  let messageSent: any;800801  const maxWaitBlocks = 3;802803  before(async () => {804    await usingPlaygrounds(async (helper, privateKey) => {805      alice = await privateKey('//Alice');806807      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};808      quartzAccountJunction = {809        AccountId32: {810          network: 'Any',811          id: alice.addressRaw,812        },813      };814815      quartzParachainMultilocation = {816        V2: {817          parents: 1,818          interior: {819            X1: quartzParachainJunction,820          },821        },822      };823824      quartzAccountMultilocation = {825        V2: {826          parents: 0,827          interior: {828            X1: quartzAccountJunction,829          },830        },831      };832833      quartzCombinedMultilocation = {834        V2: {835          parents: 1,836          interior: {837            X2: [quartzParachainJunction, quartzAccountJunction],838          },839        },840      };841842      // Set the default version to wrap the first message to other chains.843      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);844    });845846    // eslint-disable-next-line require-await847    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {848      alith = helper.account.alithAccount();849    });850  });851852  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {853    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash854        && event.outcome.isFailedToTransactAsset);855  };856857  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {858    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {859      const id = {860        Token: 'KAR',861      };862      const destination = quartzCombinedMultilocation;863      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');864865      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);866    });867868    await expectFailedToTransact(helper, messageSent);869  });870871  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {872    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {873      const id = 'SelfReserve';874      const destination = quartzCombinedMultilocation;875      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');876877      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);878    });879880    await expectFailedToTransact(helper, messageSent);881  });882883  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {884    await usingShidenPlaygrounds(shidenUrl, async (helper) => {885      const destinationParachain = quartzParachainMultilocation;886      const beneficiary = quartzAccountMultilocation;887      const assets = {888        V2: [{889          id: {890            Concrete: {891              parents: 0,892              interior: 'Here',893            },894          },895          fun: {896            Fungible: testAmount,897          },898        }],899      };900      const feeAssetItem = 0;901902      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [903        destinationParachain,904        beneficiary,905        assets,906        feeAssetItem,907      ]);908909      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);910    });911912    await expectFailedToTransact(helper, messageSent);913  });914});915916describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {917  // Quartz constants918  let alice: IKeyringPair;919  let quartzAssetLocation;920921  let randomAccountQuartz: IKeyringPair;922  let randomAccountMoonriver: IKeyringPair;923924  // Moonriver constants925  let assetId: string;926927  const quartzAssetMetadata = {928    name: 'xcQuartz',929    symbol: 'xcQTZ',930    decimals: 18,931    isFrozen: false,932    minimalBalance: 1n,933  };934935  let balanceQuartzTokenInit: bigint;936  let balanceQuartzTokenMiddle: bigint;937  let balanceQuartzTokenFinal: bigint;938  let balanceForeignQtzTokenInit: bigint;939  let balanceForeignQtzTokenMiddle: bigint;940  let balanceForeignQtzTokenFinal: bigint;941  let balanceMovrTokenInit: bigint;942  let balanceMovrTokenMiddle: bigint;943  let balanceMovrTokenFinal: bigint;944945  before(async () => {946    await usingPlaygrounds(async (helper, privateKey) => {947      alice = await privateKey('//Alice');948      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);949950      balanceForeignQtzTokenInit = 0n;951952      // Set the default version to wrap the first message to other chains.953      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);954    });955956    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {957      const alithAccount = helper.account.alithAccount();958      const baltatharAccount = helper.account.baltatharAccount();959      const dorothyAccount = helper.account.dorothyAccount();960961      randomAccountMoonriver = helper.account.create();962963      // >>> Sponsoring Dorothy >>>964      console.log('Sponsoring Dorothy.......');965      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);966      console.log('Sponsoring Dorothy.......DONE');967      // <<< Sponsoring Dorothy <<<968969      quartzAssetLocation = {970        XCM: {971          parents: 1,972          interior: {X1: {Parachain: QUARTZ_CHAIN}},973        },974      };975      const existentialDeposit = 1n;976      const isSufficient = true;977      const unitsPerSecond = 1n;978      const numAssetsWeightHint = 0;979980      if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {981        console.log('Quartz asset already registered on Moonriver');982      } else {983        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({984          location: quartzAssetLocation,985          metadata: quartzAssetMetadata,986          existentialDeposit,987          isSufficient,988          unitsPerSecond,989          numAssetsWeightHint,990        });991992        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);993994        await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);995      }996      // >>> Acquire Quartz AssetId Info on Moonriver >>>997      console.log('Acquire Quartz AssetId Info on Moonriver.......');998999      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10001001      console.log('QTZ asset ID is %s', assetId);1002      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1003      // >>> Acquire Quartz AssetId Info on Moonriver >>>10041005      // >>> Sponsoring random Account >>>1006      console.log('Sponsoring random Account.......');1007      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1008      console.log('Sponsoring random Account.......DONE');1009      // <<< Sponsoring random Account <<<10101011      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1012    });10131014    await usingPlaygrounds(async (helper) => {1015      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1016      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1017    });1018  });10191020  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1021    const currencyId = {1022      NativeAssetId: 'Here',1023    };1024    const dest = {1025      V2: {1026        parents: 1,1027        interior: {1028          X2: [1029            {Parachain: MOONRIVER_CHAIN},1030            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1031          ],1032        },1033      },1034    };1035    const amount = TRANSFER_AMOUNT;10361037    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10381039    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1040    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10411042    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1043    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1044    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10451046    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1047      await helper.wait.newBlocks(3);10481049      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10501051      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1052      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1053      expect(movrFees == 0n).to.be.true;10541055      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1056      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1057      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1058      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1059    });1060  });10611062  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1063    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1064      const asset = {1065        V2: {1066          id: {1067            Concrete: {1068              parents: 1,1069              interior: {1070                X1: {Parachain: QUARTZ_CHAIN},1071              },1072            },1073          },1074          fun: {1075            Fungible: TRANSFER_AMOUNT,1076          },1077        },1078      };1079      const destination = {1080        V2: {1081          parents: 1,1082          interior: {1083            X2: [1084              {Parachain: QUARTZ_CHAIN},1085              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1086            ],1087          },1088        },1089      };10901091      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10921093      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10941095      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1096      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1097      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10981099      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11001101      expect(qtzRandomAccountAsset).to.be.null;11021103      balanceForeignQtzTokenFinal = 0n;11041105      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1106      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1107      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1108    });11091110    await helper.wait.newBlocks(3);11111112    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1113    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1114    expect(actuallyDelivered > 0).to.be.true;11151116    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11171118    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1119    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1120    expect(qtzFees == 0n).to.be.true;1121  });11221123  itSub('Moonriver can send only up to its balance', async ({helper}) => {1124    // set Moonriver's sovereign account's balance1125    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1126    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1127    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11281129    const moreThanMoonriverHas = moonriverBalance * 2n;11301131    let targetAccountBalance = 0n;1132    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11331134    const quartzMultilocation = {1135      V2: {1136        parents: 1,1137        interior: {1138          X1: {Parachain: QUARTZ_CHAIN},1139        },1140      },1141    };11421143    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1144      targetAccount.addressRaw,1145      {1146        Concrete: {1147          parents: 0,1148          interior: 'Here',1149        },1150      },1151      moreThanMoonriverHas,1152    );11531154    let maliciousXcmProgramSent: any;1155    const maxWaitBlocks = 3;11561157    // Try to trick Quartz1158    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1159      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11601161      // Needed to bypass the call filter.1162      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1163      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11641165      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1166    });11671168    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1169        && event.outcome.isFailedToTransactAsset);11701171    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1172    expect(targetAccountBalance).to.be.equal(0n);11731174    // But Moonriver still can send the correct amount1175    const validTransferAmount = moonriverBalance / 2n;1176    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1177      targetAccount.addressRaw,1178      {1179        Concrete: {1180          parents: 0,1181          interior: 'Here',1182        },1183      },1184      validTransferAmount,1185    );11861187    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1188      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11891190      // Needed to bypass the call filter.1191      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1192      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1193    });11941195    await helper.wait.newBlocks(maxWaitBlocks);11961197    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1198    expect(targetAccountBalance).to.be.equal(validTransferAmount);1199  });12001201  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1202    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1203    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12041205    const quartzMultilocation = {1206      V2: {1207        parents: 1,1208        interior: {1209          X1: {1210            Parachain: QUARTZ_CHAIN,1211          },1212        },1213      },1214    };12151216    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1217      targetAccount.addressRaw,1218      {1219        Concrete: {1220          parents: 0,1221          interior: {1222            X1: {1223              Parachain: QUARTZ_CHAIN,1224            },1225          },1226        },1227      },1228      testAmount,1229    );12301231    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1232      targetAccount.addressRaw,1233      {1234        Concrete: {1235          parents: 0,1236          interior: 'Here',1237        },1238      },1239      testAmount,1240    );12411242    let maliciousXcmProgramFullIdSent: any;1243    let maliciousXcmProgramHereIdSent: any;1244    const maxWaitBlocks = 3;12451246    // Try to trick Quartz using full QTZ identification1247    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1248      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12491250      // Needed to bypass the call filter.1251      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1252      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12531254      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1255    });12561257    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1258        && event.outcome.isUntrustedReserveLocation);12591260    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1261    expect(accountBalance).to.be.equal(0n);12621263    // Try to trick Quartz using shortened QTZ identification1264    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1265      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12661267      // Needed to bypass the call filter.1268      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1269      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12701271      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1272    });12731274    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1275        && event.outcome.isUntrustedReserveLocation);12761277    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1278    expect(accountBalance).to.be.equal(0n);1279  });1280});12811282describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1283  let alice: IKeyringPair;1284  let sender: IKeyringPair;12851286  const QTZ_ASSET_ID_ON_SHIDEN = 1;1287  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12881289  // Quartz -> Shiden1290  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1291  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1292  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1293  const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens12941295  // Shiden -> Quartz1296  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1297  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ12981299  let balanceAfterQuartzToShidenXCM: bigint;13001301  before(async () => {1302    await usingPlaygrounds(async (helper, privateKey) => {1303      alice = await privateKey('//Alice');1304      [sender] = await helper.arrange.createAccounts([100n], alice);1305      console.log('sender', sender.address);13061307      // Set the default version to wrap the first message to other chains.1308      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1309    });13101311    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1312      if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {1313        console.log('1. Create foreign asset and metadata');1314        // TODO update metadata with values from production1315        await helper.assets.create(1316          alice,1317          QTZ_ASSET_ID_ON_SHIDEN,1318          alice.address,1319          QTZ_MINIMAL_BALANCE_ON_SHIDEN,1320        );13211322        await helper.assets.setMetadata(1323          alice,1324          QTZ_ASSET_ID_ON_SHIDEN,1325          'Cross chain QTZ',1326          'xcQTZ',1327          Number(QTZ_DECIMALS),1328        );13291330        console.log('2. Register asset location on Shiden');1331        const assetLocation = {1332          V2: {1333            parents: 1,1334            interior: {1335              X1: {1336                Parachain: QUARTZ_CHAIN,1337              },1338            },1339          },1340        };13411342        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13431344        console.log('3. Set QTZ payment for XCM execution on Shiden');1345        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1346      } else {1347        console.log('QTZ is already registered on Shiden');1348      }1349      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1350      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1351    });1352  });13531354  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1355    const destination = {1356      V2: {1357        parents: 1,1358        interior: {1359          X1: {1360            Parachain: SHIDEN_CHAIN,1361          },1362        },1363      },1364    };13651366    const beneficiary = {1367      V2: {1368        parents: 0,1369        interior: {1370          X1: {1371            AccountId32: {1372              network: 'Any',1373              id: sender.addressRaw,1374            },1375          },1376        },1377      },1378    };13791380    const assets = {1381      V2: [1382        {1383          id: {1384            Concrete: {1385              parents: 0,1386              interior: 'Here',1387            },1388          },1389          fun: {1390            Fungible: qtzToShidenTransferred,1391          },1392        },1393      ],1394    };13951396    // Initial balance is 100 QTZ1397    const balanceBefore = await helper.balance.getSubstrate(sender.address);1398    console.log(`Initial balance is: ${balanceBefore}`);13991400    const feeAssetItem = 0;1401    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14021403    // Balance after reserve transfer is less than 901404    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1405    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1406    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1407    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14081409    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1410      await helper.wait.newBlocks(3);1411      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1412      const shidenBalance = await helper.balance.getSubstrate(sender.address);14131414      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1415      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14161417      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1418      // SHD balance does not changed:1419      expect(shidenBalance).to.eq(shidenInitialBalance);1420    });1421  });14221423  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1424    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1425      const destination = {1426        V2: {1427          parents: 1,1428          interior: {1429            X1: {1430              Parachain: QUARTZ_CHAIN,1431            },1432          },1433        },1434      };14351436      const beneficiary = {1437        V2: {1438          parents: 0,1439          interior: {1440            X1: {1441              AccountId32: {1442                network: 'Any',1443                id: sender.addressRaw,1444              },1445            },1446          },1447        },1448      };14491450      const assets = {1451        V2: [1452          {1453            id: {1454              Concrete: {1455                parents: 1,1456                interior: {1457                  X1: {1458                    Parachain: QUARTZ_CHAIN,1459                  },1460                },1461              },1462            },1463            fun: {1464              Fungible: qtzFromShidenTransfered,1465            },1466          },1467        ],1468      };14691470      // Initial balance is 1 SDN1471      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1472      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1473      expect(balanceSDNbefore).to.eq(shidenInitialBalance);14741475      const feeAssetItem = 0;1476      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1477      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14781479      // Balance after reserve transfer is less than 1 SDN1480      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1481      const balanceSDN = await helper.balance.getSubstrate(sender.address);1482      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14831484      // Assert: xcQTZ balance correctly decreased1485      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1486      // Assert: SDN balance is 0.996...1487      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1488    });14891490    await helper.wait.newBlocks(3);1491    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1492    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1493    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1494  });14951496  itSub('Shiden can send only up to its balance', async ({helper}) => {1497    // set Shiden's sovereign account's balance1498    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1499    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1500    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15011502    const moreThanShidenHas = shidenBalance * 2n;15031504    let targetAccountBalance = 0n;1505    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15061507    const quartzMultilocation = {1508      V2: {1509        parents: 1,1510        interior: {1511          X1: {Parachain: QUARTZ_CHAIN},1512        },1513      },1514    };15151516    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1517      targetAccount.addressRaw,1518      {1519        Concrete: {1520          parents: 0,1521          interior: 'Here',1522        },1523      },1524      moreThanShidenHas,1525    );15261527    let maliciousXcmProgramSent: any;1528    const maxWaitBlocks = 3;15291530    // Try to trick Quartz1531    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1532      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15331534      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1535    });15361537    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1538        && event.outcome.isFailedToTransactAsset);15391540    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1541    expect(targetAccountBalance).to.be.equal(0n);15421543    // But Shiden still can send the correct amount1544    const validTransferAmount = shidenBalance / 2n;1545    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1546      targetAccount.addressRaw,1547      {1548        Concrete: {1549          parents: 0,1550          interior: 'Here',1551        },1552      },1553      validTransferAmount,1554    );15551556    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1557      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1558    });15591560    await helper.wait.newBlocks(maxWaitBlocks);15611562    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1563    expect(targetAccountBalance).to.be.equal(validTransferAmount);1564  });15651566  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1567    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1568    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15691570    const quartzMultilocation = {1571      V2: {1572        parents: 1,1573        interior: {1574          X1: {1575            Parachain: QUARTZ_CHAIN,1576          },1577        },1578      },1579    };15801581    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1582      targetAccount.addressRaw,1583      {1584        Concrete: {1585          parents: 1,1586          interior: {1587            X1: {1588              Parachain: QUARTZ_CHAIN,1589            },1590          },1591        },1592      },1593      testAmount,1594    );15951596    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1597      targetAccount.addressRaw,1598      {1599        Concrete: {1600          parents: 0,1601          interior: 'Here',1602        },1603      },1604      testAmount,1605    );16061607    let maliciousXcmProgramFullIdSent: any;1608    let maliciousXcmProgramHereIdSent: any;1609    const maxWaitBlocks = 3;16101611    // Try to trick Quartz using full QTZ identification1612    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1613      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16141615      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1616    });16171618    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1619        && event.outcome.isUntrustedReserveLocation);16201621    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1622    expect(accountBalance).to.be.equal(0n);16231624    // Try to trick Quartz using shortened QTZ identification1625    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1626      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16271628      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1629    });16301631    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1632        && event.outcome.isUntrustedReserveLocation);16331634    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1635    expect(accountBalance).to.be.equal(0n);1636  });1637});