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

difftreelog

Merge pull request #1003 from UniqueNetwork/feature/llxcm-qtz

Yaroslav Bolyukin2023-10-02parents: #22b45b5 #3107202.patch.diff
in: master
added `XcmTestHelper` + allow XCM Transact for Gov/Identity/System calls

14 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:
modifiedruntime/common/config/xcm/mod.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/mod.rs
+++ b/runtime/common/config/xcm/mod.rs
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 use frame_support::{
-	traits::{Everything, Nothing, Get, ConstU32, ProcessMessageError},
+	traits::{Everything, Nothing, Get, ConstU32, ProcessMessageError, Contains},
 	parameter_types,
 };
 use frame_system::EnsureRoot;
@@ -162,6 +162,54 @@
 
 pub type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
 
+pub struct XcmCallFilter;
+impl XcmCallFilter {
+	fn allow_gov_and_sys_call(call: &RuntimeCall) -> bool {
+		match call {
+			RuntimeCall::System(..) => true,
+
+			#[cfg(feature = "governance")]
+			RuntimeCall::Identity(..)
+			| RuntimeCall::Preimage(..)
+			| RuntimeCall::Democracy(..)
+			| RuntimeCall::Council(..)
+			| RuntimeCall::TechnicalCommittee(..)
+			| RuntimeCall::CouncilMembership(..)
+			| RuntimeCall::TechnicalCommitteeMembership(..)
+			| RuntimeCall::FellowshipCollective(..)
+			| RuntimeCall::FellowshipReferenda(..) => true,
+			_ => false,
+		}
+	}
+
+	fn allow_utility_call(call: &RuntimeCall) -> bool {
+		match call {
+			RuntimeCall::Utility(pallet_utility::Call::batch { calls, .. }) => {
+				calls.iter().all(Self::allow_gov_and_sys_call)
+			}
+			RuntimeCall::Utility(pallet_utility::Call::batch_all { calls, .. }) => {
+				calls.iter().all(Self::allow_gov_and_sys_call)
+			}
+			RuntimeCall::Utility(pallet_utility::Call::as_derivative { call, .. }) => {
+				Self::allow_gov_and_sys_call(call)
+			}
+			RuntimeCall::Utility(pallet_utility::Call::dispatch_as { call, .. }) => {
+				Self::allow_gov_and_sys_call(call)
+			}
+			RuntimeCall::Utility(pallet_utility::Call::force_batch { calls, .. }) => {
+				calls.iter().all(Self::allow_gov_and_sys_call)
+			}
+			_ => false,
+		}
+	}
+}
+
+impl Contains<RuntimeCall> for XcmCallFilter {
+	fn contains(call: &RuntimeCall) -> bool {
+		Self::allow_gov_and_sys_call(call) || Self::allow_utility_call(call)
+	}
+}
+
 pub struct XcmExecutorConfig<T>(PhantomData<T>);
 impl<T> xcm_executor::Config for XcmExecutorConfig<T>
 where
@@ -191,9 +239,7 @@
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
-
-	// Deny all XCM Transacts.
-	type SafeCallFilter = Nothing;
+	type SafeCallFilter = XcmCallFilter;
 }
 
 #[cfg(feature = "runtime-benchmarks")]
modifiedruntime/opal/src/xcm_barrier.rsdiffbeforeafterboth
--- a/runtime/opal/src/xcm_barrier.rs
+++ b/runtime/opal/src/xcm_barrier.rs
@@ -14,7 +14,18 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-use frame_support::traits::Everything;
-use xcm_builder::{AllowTopLevelPaidExecutionFrom, TakeWeightCredit};
+use frame_support::{match_types, traits::Everything};
+use xcm::latest::{Junctions::*, MultiLocation};
+use xcm_builder::{AllowTopLevelPaidExecutionFrom, TakeWeightCredit, AllowExplicitUnpaidExecutionFrom};
 
-pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom<Everything>);
+match_types! {
+	pub type ParentOnly: impl Contains<MultiLocation> = {
+		MultiLocation { parents: 1, interior: Here }
+	};
+}
+
+pub type Barrier = (
+	TakeWeightCredit,
+	AllowExplicitUnpaidExecutionFrom<ParentOnly>,
+	AllowTopLevelPaidExecutionFrom<Everything>,
+);
modifiedruntime/quartz/src/xcm_barrier.rsdiffbeforeafterboth
--- a/runtime/quartz/src/xcm_barrier.rs
+++ b/runtime/quartz/src/xcm_barrier.rs
@@ -18,12 +18,16 @@
 use xcm::latest::{Junctions::*, MultiLocation};
 use xcm_builder::{
 	AllowKnownQueryResponses, AllowSubscriptionsFrom, TakeWeightCredit,
-	AllowTopLevelPaidExecutionFrom,
+	AllowTopLevelPaidExecutionFrom, AllowExplicitUnpaidExecutionFrom,
 };
 
 use crate::PolkadotXcm;
 
 match_types! {
+	pub type ParentOnly: impl Contains<MultiLocation> = {
+		MultiLocation { parents: 1, interior: Here }
+	};
+
 	pub type ParentOrSiblings: impl Contains<MultiLocation> = {
 		MultiLocation { parents: 1, interior: Here } |
 		MultiLocation { parents: 1, interior: X1(_) }
@@ -32,6 +36,7 @@
 
 pub type Barrier = (
 	TakeWeightCredit,
+	AllowExplicitUnpaidExecutionFrom<ParentOnly>,
 	AllowTopLevelPaidExecutionFrom<Everything>,
 	// Expected responses are OK.
 	AllowKnownQueryResponses<PolkadotXcm>,
modifiedruntime/unique/src/xcm_barrier.rsdiffbeforeafterboth
--- a/runtime/unique/src/xcm_barrier.rs
+++ b/runtime/unique/src/xcm_barrier.rs
@@ -18,12 +18,16 @@
 use xcm::latest::{Junctions::*, MultiLocation};
 use xcm_builder::{
 	AllowKnownQueryResponses, AllowSubscriptionsFrom, TakeWeightCredit,
-	AllowTopLevelPaidExecutionFrom,
+	AllowTopLevelPaidExecutionFrom, AllowExplicitUnpaidExecutionFrom,
 };
 
 use crate::PolkadotXcm;
 
 match_types! {
+	pub type ParentOnly: impl Contains<MultiLocation> = {
+		MultiLocation { parents: 1, interior: Here }
+	};
+
 	pub type ParentOrSiblings: impl Contains<MultiLocation> = {
 		MultiLocation { parents: 1, interior: Here } |
 		MultiLocation { parents: 1, interior: X1(_) }
@@ -32,6 +36,7 @@
 
 pub type Barrier = (
 	TakeWeightCredit,
+	AllowExplicitUnpaidExecutionFrom<ParentOnly>,
 	AllowTopLevelPaidExecutionFrom<Everything>,
 	// Expected responses are OK.
 	AllowKnownQueryResponses<PolkadotXcm>,
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -117,6 +117,8 @@
     "testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmUnique.test.ts",
     "testFullXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Unique.test.ts",
     "testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmQuartz.test.ts",
+    "testLowLevelXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/lowLevelXcmQuartz.test.ts",
+    "testFullXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Quartz.test.ts",
     "testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts",
     "testXcmTransferAcala": "yarn _test ./**/xcm/xcmTransferAcala.test.ts acalaId=2000 uniqueId=5000",
     "testXcmTransferStatemine": "yarn _test ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000",
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -9,7 +9,7 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import {EventRecord} from '@polkadot/types/interfaces';
 import {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types';
-import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';
+import {FrameSystemEventRecord, XcmV2TraitsError, XcmV3TraitsOutcome} from '@polkadot/types/lookup';
 import {SignerOptions, VoidFn} from '@polkadot/api/types';
 import {Pallets} from '..';
 import {spawnSync} from 'child_process';
@@ -260,6 +260,12 @@
       outcome: eventData<XcmV2TraitsError>(data, 1),
     }));
   };
+
+  static DmpQueue = class extends EventSection('dmpQueue') {
+    static ExecutedDownward = this.Method('ExecutedDownward', data => ({
+      outcome: eventData<XcmV3TraitsOutcome>(data, 1),
+    }));
+  };
 }
 
 // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -559,6 +565,12 @@
     super(logger, options);
     this.wait = new WaitGroup(this);
   }
+
+  getSudo() {
+    // eslint-disable-next-line @typescript-eslint/naming-convention
+    const SudoHelperType = SudoHelper(this.helperBase);
+    return this.clone(SudoHelperType) as DevRelayHelper;
+  }
 }
 
 export class DevWestmintHelper extends WestmintHelper {
@@ -968,6 +980,31 @@
       ],
     };
   }
+
+  makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) {
+    return {
+      V3: [
+        {
+          UnpaidExecution: {
+            weightLimit: 'Unlimited',
+            checkOrigin: null,
+          },
+        },
+        {
+          Transact: {
+            originKind: 'Superuser',
+            requireWeightAtMost: {
+              refTime: info.weightMultiplier * 200000000,
+              proofSize: info.weightMultiplier * 3000,
+            },
+            call: {
+              encoded: info.call,
+            },
+          },
+        },
+      ],
+    };
+  }
 }
 
 class MoonbeamAccountGroup {
@@ -1501,4 +1538,4 @@
       );
     }
   };
-}
\ No newline at end of file
+}
modifiedtests/src/util/playgrounds/unique.xcm.tsdiffbeforeafterboth
240}240}
241241
242export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {242export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {
243 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {243 async create(signer: TSigner, assetId: number | bigint, admin: string, minimalBalance: bigint) {
244 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);244 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
245 }245 }
246246
247 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {247 async setMetadata(signer: TSigner, assetId: number | bigint, name: string, symbol: string, decimals: number) {
248 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);248 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);
249 }249 }
250250
251 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {251 async mint(signer: TSigner, assetId: number | bigint, beneficiary: string, amount: bigint) {
252 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);252 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
253 }253 }
254254
255 async account(assetId: string | number, address: string) {255 async account(assetId: string | number | bigint, address: string) {
256 const accountAsset = (256 const accountAsset = (
257 await this.helper.callRpc('api.query.assets.account', [assetId, address])257 await this.helper.callRpc('api.query.assets.account', [assetId, address])
258 ).toJSON()! as any;258 ).toJSON()! as any;
addedtests/src/xcm/lowLevelXcmQuartz.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts
@@ -0,0 +1,364 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/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;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = await privateKey('//Alice');
+      [randomAccount] = await helper.arrange.createAccounts([0n], alice);
+
+      // Set the default version to wrap the first message to other chains.
+      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+    });
+
+    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
+      const destination = {
+        V2: {
+          parents: 1,
+          interior: {
+            X1: {
+              Parachain: QUARTZ_CHAIN,
+            },
+          },
+        },
+      };
+
+      const metadata = {
+        name: 'Quartz',
+        symbol: 'QTZ',
+        decimals: 18,
+        minimalBalance: 1000000000000000000n,
+      };
+
+      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);
+    });
+
+    await usingPlaygrounds(async (helper) => {
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
+    });
+  });
+
+  itSub('Should connect and send QTZ to Karura', async () => {
+    await testHelper.sendUnqTo('karura', randomAccount);
+  });
+
+  itSub('Should connect to Karura and send QTZ back', async () => {
+    await testHelper.sendUnqBack('karura', alice, randomAccount);
+  });
+
+  itSub('Karura can send only up to its balance', async () => {
+    await testHelper.sendOnlyOwnedBalance('karura', alice);
+  });
+});
+// These tests are relevant only when
+// the the corresponding foreign assets are not registered
+describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => {
+  let alice: IKeyringPair;
+
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = await privateKey('//Alice');
+
+
+
+      // Set the default version to wrap the first message to other chains.
+      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+    });
+  });
+
+  itSub('Quartz rejects KAR tokens from Karura', async () => {
+    await testHelper.rejectNativeTokensFrom('karura', alice);
+  });
+
+  itSub('Quartz rejects MOVR tokens from Moonriver', async () => {
+    await testHelper.rejectNativeTokensFrom('moonriver', alice);
+  });
+
+  itSub('Quartz rejects SDN tokens from Shiden', async () => {
+    await testHelper.rejectNativeTokensFrom('shiden', alice);
+  });
+});
+
+describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => {
+  // Quartz constants
+  let alice: IKeyringPair;
+  let quartzAssetLocation;
+
+  let randomAccountQuartz: IKeyringPair;
+  let randomAccountMoonriver: IKeyringPair;
+
+  // Moonriver constants
+  let assetId: string;
+
+  const quartzAssetMetadata = {
+    name: 'xcQuartz',
+    symbol: 'xcQTZ',
+    decimals: 18,
+    isFrozen: false,
+    minimalBalance: 1n,
+  };
+
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = await privateKey('//Alice');
+      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);
+
+
+      // Set the default version to wrap the first message to other chains.
+      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+    });
+
+    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
+      const alithAccount = helper.account.alithAccount();
+      const baltatharAccount = helper.account.baltatharAccount();
+      const dorothyAccount = helper.account.dorothyAccount();
+
+      randomAccountMoonriver = helper.account.create();
+
+      // >>> Sponsoring Dorothy >>>
+      console.log('Sponsoring Dorothy.......');
+      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);
+      console.log('Sponsoring Dorothy.......DONE');
+      // <<< Sponsoring Dorothy <<<
+
+      quartzAssetLocation = {
+        XCM: {
+          parents: 1,
+          interior: {X1: {Parachain: QUARTZ_CHAIN}},
+        },
+      };
+      const existentialDeposit = 1n;
+      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,
+        });
+
+        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.......');
+
+      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();
+
+      console.log('QTZ asset ID is %s', assetId);
+      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');
+      // >>> Acquire Quartz AssetId Info on Moonriver >>>
+
+      // >>> Sponsoring random Account >>>
+      console.log('Sponsoring random Account.......');
+      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);
+      console.log('Sponsoring random Account.......DONE');
+      // <<< Sponsoring random Account <<<
+    });
+
+    await usingPlaygrounds(async (helper) => {
+      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);
+    });
+  });
+
+  itSub('Should connect and send QTZ to Moonriver', async () => {
+    await testHelper.sendUnqTo('moonriver', randomAccountQuartz, randomAccountMoonriver);
+  });
+
+  itSub('Should connect to Moonriver and send QTZ back', async () => {
+    await testHelper.sendUnqBack('moonriver', alice, randomAccountQuartz);
+  });
+
+  itSub('Moonriver can send only up to its balance', async () => {
+    await testHelper.sendOnlyOwnedBalance('moonriver', alice);
+  });
+
+  itSub('Should not accept reserve transfer of QTZ from Moonriver', async () => {
+    await testHelper.rejectReserveTransferUNQfrom('moonriver', alice);
+  });
+});
+
+describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => {
+  let alice: IKeyringPair;
+  let randomAccount: IKeyringPair;
+
+  const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden
+  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden
+
+  // Quartz -> Shiden
+  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden
+  const unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = await privateKey('//Alice');
+      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) => {
+      if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {
+        console.log('1. Create foreign asset and metadata');
+        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,
+          'Quartz',
+          'QTZ',
+          Number(QTZ_DECIMALS),
+        );
+
+        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]);
+      } 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, randomAccount.address, shidenInitialBalance);
+    });
+  });
+
+  itSub('Should connect and send QTZ to Shiden', async () => {
+    await testHelper.sendUnqTo('shiden', randomAccount);
+  });
+
+  itSub('Should connect to Shiden and send QTZ back', async () => {
+    await testHelper.sendUnqBack('shiden', alice, randomAccount);
+  });
+
+  itSub('Shiden can send only up to its balance', async () => {
+    await testHelper.sendOnlyOwnedBalance('shiden', alice);
+  });
+
+  itSub('Should not accept reserve transfer of QTZ from Shiden', async () => {
+    await testHelper.rejectReserveTransferUNQfrom('shiden', alice);
+  });
+});
+
+describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {
+  let sudoer: IKeyringPair;
+
+  before(async function () {
+    await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {
+      sudoer = await privateKey('//Alice');
+    });
+  });
+
+  // At the moment there is no reliable way
+  // to establish the correspondence between the `ExecutedDownward` event
+  // and the relay's sent message due to `SetTopic` instruction
+  // containing an unpredictable topic silently added by the relay's messages on the router level.
+  // This changes the message hash on arrival to our chain.
+  //
+  // See:
+  // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83
+  // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36
+  //
+  // Because of this, we insert time gaps between tests so
+  // different `ExecutedDownward` events won't interfere with each other.
+  afterEach(async () => {
+    await usingPlaygrounds(async (helper) => {
+      await helper.wait.newBlocks(3);
+    });
+  });
+
+  itSub('The relay can set storage', async () => {
+    await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');
+  });
+
+  itSub('The relay can batch set storage', async () => {
+    await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');
+  });
+
+  itSub('The relay can batchAll set storage', async () => {
+    await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');
+  });
+
+  itSub('The relay can forceBatch set storage', async () => {
+    await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');
+  });
+
+  itSub('[negative] The relay cannot set balance', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');
+  });
+
+  itSub('[negative] The relay cannot set balance via batch', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');
+  });
+
+  itSub('[negative] The relay cannot set balance via batchAll', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');
+  });
+
+  itSub('[negative] The relay cannot set balance via forceBatch', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');
+  });
+
+  itSub('[negative] The relay cannot set balance via dispatchAs', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');
+  });
+});
modifiedtests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -16,317 +16,14 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import config from '../config';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} 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, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, 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;
-
-
-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);
+import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl,  moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types';
 
-    accountBalance = await helper.balance.getSubstrate(targetAccount.address);
-    expect(accountBalance).to.be.equal(0n);
-  });
-}
+const testHelper = new XcmTestHelper('unique');
 
-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', () => {
@@ -374,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.rejectReserveTransferUNQfrom('acala', alice);
   });
 });
 
@@ -429,25 +125,24 @@
 
     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 Polkadex', async () => {
-    await genericSendUnqTo('polkadex', randomAccount);
+    await testHelper.sendUnqTo('polkadex', randomAccount);
   });
 
 
   itSub('Should connect to Polkadex and send UNQ back', async () => {
-    await genericSendUnqBack('polkadex', alice, randomAccount);
+    await testHelper.sendUnqBack('polkadex', alice, randomAccount);
   });
 
   itSub('Polkadex can send only up to its balance', async () => {
-    await genericSendOnlyOwnedBalance('polkadex', alice);
+    await testHelper.sendOnlyOwnedBalance('polkadex', alice);
   });
 
   itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {
-    await genericReserveTransferUNQfrom('polkadex', alice);
+    await testHelper.rejectReserveTransferUNQfrom('polkadex', alice);
   });
 });
 
@@ -466,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);
   });
 });
 
@@ -569,24 +264,23 @@
 
     await usingPlaygrounds(async (helper) => {
       await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);
-      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
     });
   });
 
   itSub('Should connect and send UNQ to Moonbeam', async () => {
-    await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);
+    await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);
   });
 
   itSub('Should connect to Moonbeam and send UNQ back', async () => {
-    await genericSendUnqBack('moonbeam', alice, randomAccountUnique);
+    await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique);
   });
 
   itSub('Moonbeam can send only up to its balance', async () => {
-    await genericSendOnlyOwnedBalance('moonbeam', alice);
+    await testHelper.sendOnlyOwnedBalance('moonbeam', alice);
   });
 
   itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {
-    await genericReserveTransferUNQfrom('moonbeam', alice);
+    await testHelper.rejectReserveTransferUNQfrom('moonbeam', alice);
   });
 });
 
@@ -594,12 +288,12 @@
   let alice: IKeyringPair;
   let randomAccount: IKeyringPair;
 
-  const UNQ_ASSET_ID_ON_ASTAR = 1;
-  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;
+  const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar
+  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar
 
   // Unique -> Astar
   const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.
-  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?
+  const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
@@ -615,7 +309,6 @@
     await usingAstarPlaygrounds(astarUrl, async (helper) => {
       if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {
         console.log('1. Create foreign asset and metadata');
-        // TODO update metadata with values from production
         await helper.assets.create(
           alice,
           UNQ_ASSET_ID_ON_ASTAR,
@@ -626,8 +319,8 @@
         await helper.assets.setMetadata(
           alice,
           UNQ_ASSET_ID_ON_ASTAR,
-          'Cross chain UNQ',
-          'xcUNQ',
+          'Unique Network',
+          'UNQ',
           Number(UNQ_DECIMALS),
         );
 
@@ -656,18 +349,82 @@
   });
 
   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.rejectReserveTransferUNQfrom('astar', alice);
+  });
+});
+
+describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {
+  let sudoer: IKeyringPair;
+
+  before(async function () {
+    await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {
+      sudoer = await privateKey('//Alice');
+    });
+  });
+
+  // At the moment there is no reliable way
+  // to establish the correspondence between the `ExecutedDownward` event
+  // and the relay's sent message due to `SetTopic` instruction
+  // containing an unpredictable topic silently added by the relay's messages on the router level.
+  // This changes the message hash on arrival to our chain.
+  //
+  // See:
+  // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83
+  // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36
+  //
+  // Because of this, we insert time gaps between tests so
+  // different `ExecutedDownward` events won't interfere with each other.
+  afterEach(async () => {
+    await usingPlaygrounds(async (helper) => {
+      await helper.wait.newBlocks(3);
+    });
+  });
+
+  itSub('The relay can set storage', async () => {
+    await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');
+  });
+
+  itSub('The relay can batch set storage', async () => {
+    await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');
+  });
+
+  itSub('The relay can batchAll set storage', async () => {
+    await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');
+  });
+
+  itSub('The relay can forceBatch set storage', async () => {
+    await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');
+  });
+
+  itSub('[negative] The relay cannot set balance', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');
+  });
+
+  itSub('[negative] The relay cannot set balance via batch', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');
+  });
+
+  itSub('[negative] The relay cannot set balance via batchAll', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');
+  });
+
+  itSub('[negative] The relay cannot set balance via forceBatch', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');
+  });
+
+  itSub('[negative] The relay cannot set balance via dispatchAs', async () => {
+    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');
   });
 });
modifiedtests/src/xcm/xcm.types.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcm.types.ts
+++ b/tests/src/xcm/xcm.types.ts
@@ -1,4 +1,6 @@
-import {usingAcalaPlaygrounds, usingAstarPlaygrounds, usingMoonbeamPlaygrounds, usingPolkadexPlaygrounds} from '../util';
+import {IKeyringPair} from '@polkadot/types/types';
+import {hexToString} from '@polkadot/util';
+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';
 
@@ -9,19 +11,39 @@
 export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
 export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
 
+export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);
+export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);
+export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);
+export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);
+export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);
+
+export const relayUrl = config.relayUrl;
+export const statemintUrl = config.statemintUrl;
+export const statemineUrl = config.statemineUrl;
+
 export const acalaUrl = config.acalaUrl;
 export const moonbeamUrl = config.moonbeamUrl;
 export const astarUrl = config.astarUrl;
 export const polkadexUrl = config.polkadexUrl;
 
+export const karuraUrl = config.karuraUrl;
+export const moonriverUrl = config.moonriverUrl;
+export const shidenUrl = config.shidenUrl;
+
 export const SAFE_XCM_VERSION = 3;
 
-export const maxWaitBlocks = 6;
 
+export const RELAY_DECIMALS = 12;
+export const STATEMINE_DECIMALS = 12;
+export const KARURA_DECIMALS = 12;
+export const SHIDEN_DECIMALS = 18n;
+export const QTZ_DECIMALS = 18n;
 
 export const ASTAR_DECIMALS = 18n;
 export const UNQ_DECIMALS = 18n;
 
+export const maxWaitBlocks = 6;
+
 export const uniqueMultilocation = {
   parents: 1,
   interior: {
@@ -47,14 +69,30 @@
          && event.outcome.isUntrustedReserveLocation);
 };
 
+export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => {
+  // The correct messageHash for downward messages can't be reliably obtained
+  await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission);
+};
+
+export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => {
+  // The correct messageHash for downward messages can't be reliably obtained
+  await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete);
+};
+
 export const NETWORKS = {
   acala: usingAcalaPlaygrounds,
   astar: usingAstarPlaygrounds,
   polkadex: usingPolkadexPlaygrounds,
   moonbeam: usingMoonbeamPlaygrounds,
+  moonriver: usingMoonriverPlaygrounds,
+  karura: usingKaruraPlaygrounds,
+  shiden: usingShidenPlaygrounds,
 } as const;
+type NetworkNames = keyof typeof NETWORKS;
+
+type NativeRuntime = 'opal' | 'quartz' | 'unique';
 
-export function mapToChainId(networkName: keyof typeof NETWORKS) {
+export function mapToChainId(networkName: keyof typeof NETWORKS): number {
   switch (networkName) {
     case 'acala':
       return ACALA_CHAIN;
@@ -64,10 +102,16 @@
       return MOONBEAM_CHAIN;
     case 'polkadex':
       return POLKADEX_CHAIN;
+    case 'moonriver':
+      return MOONRIVER_CHAIN;
+    case 'karura':
+      return KARURA_CHAIN;
+    case 'shiden':
+      return SHIDEN_CHAIN;
   }
 }
 
-export function mapToChainUrl(networkName: keyof typeof NETWORKS): string {
+export function mapToChainUrl(networkName: NetworkNames): string {
   switch (networkName) {
     case 'acala':
       return acalaUrl;
@@ -77,9 +121,501 @@
       return moonbeamUrl;
     case 'polkadex':
       return polkadexUrl;
+    case 'moonriver':
+      return moonriverUrl;
+    case 'karura':
+      return karuraUrl;
+    case 'shiden':
+      return shidenUrl;
   }
 }
 
-export function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {
+export function getDevPlayground(name: NetworkNames) {
   return NETWORKS[name];
-}
\ No newline at end of file
+}
+
+export const TRANSFER_AMOUNT = 2000000_000_000_000_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;
+  private _balanceUniqueTokenMiddle: bigint = 0n;
+  private _balanceUniqueTokenFinal: bigint = 0n;
+  private _unqFees: bigint = 0n;
+  private _nativeRuntime: NativeRuntime;
+
+  constructor(runtime: NativeRuntime) {
+    this._nativeRuntime = runtime;
+  }
+
+  private _getNativeId() {
+    switch (this._nativeRuntime) {
+      case 'opal':
+        // To-Do
+        return 1001;
+      case 'quartz':
+        return QUARTZ_CHAIN;
+      case 'unique':
+        return UNIQUE_CHAIN;
+    }
+  }
+
+  private _isAddress20FormatFor(network: NetworkNames) {
+    switch (network) {
+      case 'moonbeam':
+      case 'moonriver':
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  private _runtimeVersionedMultilocation() {
+    return {
+      V3: {
+        parents: 1,
+        interior: {
+          X1: {
+            Parachain: this._getNativeId(),
+          },
+        },
+      },
+    };
+  }
+
+  private _uniqueChainMultilocationForRelay() {
+    return {
+      V3: {
+        parents: 0,
+        interior: {
+          X1: {Parachain: this._getNativeId()},
+        },
+      },
+    };
+  }
+
+  async sendUnqTo(
+    networkName: keyof typeof NETWORKS,
+    randomAccount: IKeyringPair,
+    randomAccountOnTargetChain = randomAccount,
+  ) {
+    const networkUrl = mapToChainUrl(networkName);
+    const targetPlayground = getDevPlayground(networkName);
+    await usingPlaygrounds(async (helper) => {
+      this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+      const destination = {
+        V2: {
+          parents: 1,
+          interior: {
+            X1: {
+              Parachain: mapToChainId(networkName),
+            },
+          },
+        },
+      };
+
+      const beneficiary = {
+        V2: {
+          parents: 0,
+          interior: {
+            X1: (
+              this._isAddress20FormatFor(networkName) ?
+                {
+                  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);
+      this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
+
+      this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
+      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) => {
+      /*
+        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 sendUnqBack(
+    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,
+        {
+          Concrete: {
+            parents: 1,
+            interior: {
+              X1: {Parachain: this._getNativeId()},
+            },
+          },
+        },
+        SENDBACK_AMOUNT,
+      );
+
+      let xcmProgramSent: any;
+
+
+      await targetPlayground(networkUrl, async (helper) => {
+        if('getSudo' in helper) {
+          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', [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);
+          xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+        }
+      });
+
+      await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
+
+      this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
+
+      expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);
+
+    });
+  }
+
+  async sendOnlyOwnedBalance(
+    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, 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', [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);
+          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 rejectReserveTransferUNQfrom(networkName: keyof typeof NETWORKS, sudoer: IKeyringPair) {
+    const networkUrl = mapToChainUrl(networkName);
+    const targetPlayground = getDevPlayground(networkName);
+
+    await usingPlaygrounds(async (helper) => {
+      const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
+      const targetAccount = helper.arrange.createEmptyAccount();
+
+      const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+        targetAccount.addressRaw,
+        {
+          Concrete: {
+            parents: 1,
+            interior: {
+              X1: {
+                Parachain: this._getNativeId(),
+              },
+            },
+          },
+        },
+        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, 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', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);
+          // Needed to bypass the call filter.
+          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
+          await helper.fastDemocracy.executeProposal(`${networkName} 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, 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', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]);
+          // Needed to bypass the call filter.
+          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
+          await helper.fastDemocracy.executeProposal(`${networkName} 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 rejectNativeTokensFrom(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, 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', [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);
+
+          messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+        }
+      });
+      await expectFailedToTransact(helper, messageSent);
+    });
+  }
+
+  private async _relayXcmTransactSetStorage(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {
+    // eslint-disable-next-line require-await
+    return await usingPlaygrounds(async (helper) => {
+      const relayForceKV = () => {
+        const random = Math.random();
+        const key = `relay-forced-key (instance: ${random})`;
+        const val = `relay-forced-value (instance: ${random})`;
+        const call = helper.constructApiCall('api.tx.system.setStorage', [[[key, val]]]).method.toHex();
+
+        return {
+          call,
+          key,
+          val,
+        };
+      };
+
+      if(variant == 'plain') {
+        const kv = relayForceKV();
+        return {
+          program: helper.arrange.makeUnpaidSudoTransactProgram({
+            weightMultiplier: 1,
+            call: kv.call,
+          }),
+          kvs: [kv],
+        };
+      } else {
+        const kv0 = relayForceKV();
+        const kv1 = relayForceKV();
+
+        const batchCall = helper.constructApiCall(`api.tx.utility.${variant}`, [[kv0.call, kv1.call]]).method.toHex();
+        return {
+          program: helper.arrange.makeUnpaidSudoTransactProgram({
+            weightMultiplier: 2,
+            call: batchCall,
+          }),
+          kvs: [kv0, kv1],
+        };
+      }
+    });
+  }
+
+  async relayIsPermittedToSetStorage(relaySudoer: IKeyringPair, variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {
+    const {program, kvs} = await this._relayXcmTransactSetStorage(variant);
+
+    await usingRelayPlaygrounds(relayUrl, async (helper) => {
+      await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [
+        this._uniqueChainMultilocationForRelay(),
+        program,
+      ]);
+    });
+
+    await usingPlaygrounds(async (helper) => {
+      await expectDownwardXcmComplete(helper);
+
+      for(const kv of kvs) {
+        const forcedValue = await helper.callRpc('api.rpc.state.getStorage', [kv.key]);
+        expect(hexToString(forcedValue.toHex())).to.be.equal(kv.val);
+      }
+    });
+  }
+
+  private async _relayXcmTransactSetBalance(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs') {
+    // eslint-disable-next-line require-await
+    return await usingPlaygrounds(async (helper) => {
+      const emptyAccount = helper.arrange.createEmptyAccount().address;
+
+      const forceSetBalanceCall = helper.constructApiCall('api.tx.balances.forceSetBalance', [emptyAccount, 10_000n]).method.toHex();
+
+      let call;
+
+      if(variant == 'plain') {
+        call = forceSetBalanceCall;
+
+      } else if(variant == 'dispatchAs') {
+        call = helper.constructApiCall('api.tx.utility.dispatchAs', [
+          {
+            system: 'Root',
+          },
+          forceSetBalanceCall,
+        ]).method.toHex();
+      } else {
+        call = helper.constructApiCall(`api.tx.utility.${variant}`, [[forceSetBalanceCall]]).method.toHex();
+      }
+
+      return {
+        program: helper.arrange.makeUnpaidSudoTransactProgram({
+          weightMultiplier: 1,
+          call,
+        }),
+        emptyAccount,
+      };
+    });
+  }
+
+  async relayIsNotPermittedToSetBalance(
+    relaySudoer: IKeyringPair,
+    variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs',
+  ) {
+    const {program, emptyAccount} = await this._relayXcmTransactSetBalance(variant);
+
+    await usingRelayPlaygrounds(relayUrl, async (helper) => {
+      await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [
+        this._uniqueChainMultilocationForRelay(),
+        program,
+      ]);
+    });
+
+    await usingPlaygrounds(async (helper) => {
+      await expectDownwardXcmNoPermission(helper);
+      expect(await helper.balance.getSubstrate(emptyAccount)).to.be.equal(0n);
+    });
+  }
+}
modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.test.ts
@@ -15,30 +15,15 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {IKeyringPair} from '@polkadot/types/types';
-import config from '../config';
 import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} 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} from './xcm.types';
+import {hexToString} from '@polkadot/util';
 
-const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);
-const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);
-const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);
-const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);
-const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);
 
-const STATEMINE_PALLET_INSTANCE = 50;
 
-const relayUrl = config.relayUrl;
-const statemineUrl = config.statemineUrl;
-const karuraUrl = config.karuraUrl;
-const moonriverUrl = config.moonriverUrl;
-const shidenUrl = config.shidenUrl;
+const STATEMINE_PALLET_INSTANCE = 50;
 
-const RELAY_DECIMALS = 12;
-const STATEMINE_DECIMALS = 12;
-const KARURA_DECIMALS = 12;
-const SHIDEN_DECIMALS = 18n;
-const QTZ_DECIMALS = 18n;
-
 const TRANSFER_AMOUNT = 2000000000000000000000000n;
 
 const FUNDING_AMOUNT = 3_500_000_0000_000_000n;
@@ -499,7 +484,14 @@
         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});
@@ -985,19 +977,22 @@
       const unitsPerSecond = 1n;
       const numAssetsWeightHint = 0;
 
-      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
-        location: quartzAssetLocation,
-        metadata: quartzAssetMetadata,
-        existentialDeposit,
-        isSufficient,
-        unitsPerSecond,
-        numAssetsWeightHint,
-      });
+      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,
+        });
 
-      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.......');
 
@@ -1288,12 +1283,12 @@
   let alice: IKeyringPair;
   let sender: IKeyringPair;
 
-  const QTZ_ASSET_ID_ON_SHIDEN = 1;
-  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;
+  const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden
+  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden
 
   // 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 unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden
   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
 
@@ -1314,40 +1309,42 @@
     });
 
     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');
+        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,
+          'Quartz',
+          'QTZ',
+          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]);
+        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]);
-
+        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);
     });
modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -1511,12 +1511,12 @@
   let alice: IKeyringPair;
   let randomAccount: IKeyringPair;
 
-  const UNQ_ASSET_ID_ON_ASTAR = 1;
-  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;
+  const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar
+  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar
 
   // Unique -> Astar
   const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.
-  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?
+  const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar
   const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ
   const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens
 
@@ -1539,7 +1539,6 @@
     await usingAstarPlaygrounds(astarUrl, async (helper) => {
       if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {
         console.log('1. Create foreign asset and metadata');
-        // TODO update metadata with values from production
         await helper.assets.create(
           alice,
           UNQ_ASSET_ID_ON_ASTAR,
@@ -1550,8 +1549,8 @@
         await helper.assets.setMetadata(
           alice,
           UNQ_ASSET_ID_ON_ASTAR,
-          'Cross chain UNQ',
-          'xcUNQ',
+          'Unique Network',
+          'UNQ',
           Number(UNQ_DECIMALS),
         );