git.delta.rocks / unique-network / refs/commits / 430b0201ea83

difftreelog

Merge pull request #1046 from UniqueNetwork/ci/hydra-xcm-integration

Yaroslav Bolyukin2023-12-14parents: #27ba026 #ef7fe00.patch.diff
in: master
hydra xcm integration

16 files changed

modified.baedeker/rewrites.example.jsonnetdiffbeforeafterboth
--- a/.baedeker/rewrites.example.jsonnet
+++ b/.baedeker/rewrites.example.jsonnet
@@ -1,17 +1,18 @@
 local dotenv = {
-    [std.splitLimit(line, "=", 2)[0]]: std.splitLimit(line, "=", 2)[1]
-    for line in std.split(importstr "../.env", "\n")
-    if line != ""
-    if std.member(line, "=")
+  [std.splitLimit(line, '=', 2)[0]]: std.splitLimit(line, '=', 2)[1]
+  for line in std.split(importstr '../.env', '\n')
+  if line != ''
+  if std.member(line, '=')
 };
 
 function(prev, repoDir)
-(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({
-	'bin/unique':'%s/target/release/unique-collator' % repoDir,
-	'bin/polkadot':{dockerImage:'uniquenetwork/builder-polkadot:%s' % dotenv.POLKADOT_MAINNET_BRANCH},
-	'bin/acala':{dockerImage:'uniquenetwork/builder-acala:%s' % dotenv.ACALA_BUILD_BRANCH},
-	'bin/moonbeam':{dockerImage:'uniquenetwork/builder-moonbeam:%s' % dotenv.MOONBEAM_BUILD_BRANCH},
-	'bin/cumulus':{dockerImage:'uniquenetwork/builder-cumulus:%s' % dotenv.STATEMINE_BUILD_BRANCH},
-	'bin/astar':{dockerImage:'uniquenetwork/builder-astar:%s' % dotenv.ASTAR_BUILD_BRANCH},
-	'bin/polkadex':{dockerImage:'uniquenetwork/builder-polkadex:%s' % dotenv.POLKADEX_BUILD_BRANCH},
-})(prev)
+  (import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({
+    'bin/unique': '%s/target/release/unique-collator' % repoDir,
+    'bin/polkadot': { dockerImage: 'uniquenetwork/builder-polkadot:%s' % dotenv.POLKADOT_MAINNET_BRANCH },
+    'bin/acala': { dockerImage: 'uniquenetwork/builder-acala:%s' % dotenv.ACALA_BUILD_BRANCH },
+    'bin/moonbeam': { dockerImage: 'uniquenetwork/builder-moonbeam:%s' % dotenv.MOONBEAM_BUILD_BRANCH },
+    'bin/cumulus': { dockerImage: 'uniquenetwork/builder-cumulus:%s' % dotenv.STATEMINE_BUILD_BRANCH },
+    'bin/astar': { dockerImage: 'uniquenetwork/builder-astar:%s' % dotenv.ASTAR_BUILD_BRANCH },
+    'bin/polkadex': { dockerImage: 'uniquenetwork/builder-polkadex:%s' % dotenv.POLKADEX_BUILD_BRANCH },
+    'bin/hydradx': { dockerImage: 'uniquenetwork/builder-hydraDx:%s' % dotenv.HYDRADX_BUILD_BRANCH },
+  })(prev)
modified.baedeker/xcm-opal.jsonnetdiffbeforeafterboth
--- a/.baedeker/xcm-opal.jsonnet
+++ b/.baedeker/xcm-opal.jsonnet
@@ -19,6 +19,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'relay',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob', 'charlie', 'dave', 'eve']
 	},
@@ -35,6 +38,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
@@ -52,6 +58,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
modified.baedeker/xcm-quartz.jsonnetdiffbeforeafterboth
--- a/.baedeker/xcm-quartz.jsonnet
+++ b/.baedeker/xcm-quartz.jsonnet
@@ -24,6 +24,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'relay',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob', 'charlie', 'dave', 'eve', 'ferdie']
 	},
@@ -40,6 +43,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
@@ -60,6 +66,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
@@ -79,6 +88,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para-nimbus',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alith', 'baltathar']
 	},
@@ -96,6 +108,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
@@ -113,6 +128,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
modified.baedeker/xcm-unique.jsonnetdiffbeforeafterboth
--- a/.baedeker/xcm-unique.jsonnet
+++ b/.baedeker/xcm-unique.jsonnet
@@ -11,22 +11,25 @@
 	spec: {Genesis:{
 		chain: relay_spec,
 		modify:: m.genericRelay($, hrmp = std.join([], [
-			[[$.parachains[a].paraId, $.parachains[b].paraId, 8, 512], [$.parachains[b].paraId, $.parachains[a].paraId, 8, 512]],
-			for [a, b] in [
+			// [[$.parachains[a].paraId, $.parachains[b].paraId, 8, 512], [$.parachains[b].paraId, $.parachains[a].paraId, 8, 512]],
+			// for [a, b] in [
 				// ['unique', 'acala'],
 				// ['unique', 'moonbeam'],
 				// ['unique', 'statemint'],
 				// ['unique', 'astar'],
 				// ['unique', 'polkadex'],
-			]
+			// ]
 		])),
 	}},
 	nodes: {
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'relay',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
-		for name in ['alice', 'bob', 'charlie', 'dave', 'eve', 'ferdie', 'gregory']
+		for name in ['alice', 'bob', 'charlie', 'dave', 'eve', 'ferdie', 'gregory', 'holly']
 	},
 };
 
@@ -41,6 +44,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
@@ -61,6 +67,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
@@ -80,6 +89,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para-nimbus',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alith', 'baltathar']
 	},
@@ -97,6 +109,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para-ed',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
@@ -114,6 +129,9 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
@@ -131,14 +149,39 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
+			extraArgs: [
+				"-lxcm=trace"
+			],
+		},
+		for name in ['alice', 'bob']
+	},
+};
+
+local hydraDx = {
+	name: 'hydraDx',
+	bin: 'bin/hydradx',
+	paraId: 1007,
+	spec: {Genesis:{
+		chain: 'local',
+		modify:: m.genericPara($),
+	}},
+	nodes: {
+		[name]: {
+			bin: $.bin,
+			wantedKeys: 'para',
+			legacyRpc: true,
+			extraArgs: [
+				"-lxcm=trace"
+			],
 		},
 		for name in ['alice', 'bob']
 	},
 };
 
+
 relay + {
 	parachains: {
 		[para.name]: para,
-		for para in [unique, acala, moonbeam, statemint, astar, polkadex]
+		for para in [unique, acala, moonbeam, statemint, astar, polkadex, hydraDx]
 	},
 }
added.docker/Dockerfile-hydradx.j2diffbeforeafterboth
--- /dev/null
+++ b/.docker/Dockerfile-hydradx.j2
@@ -0,0 +1,46 @@
+# ===== Rust builder =====
+FROM ubuntu:22.04 as rust-builder
+LABEL maintainer="Unique.Network"
+
+ENV CARGO_HOME="/cargo-home"
+ENV PATH="/cargo-home/bin:$PATH"
+ENV TZ=UTC
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN apt-get update && \
+    apt-get install -y curl cmake pkg-config libssl-dev git clang llvm libudev-dev protobuf-compiler && \
+    apt-get clean && \
+    rm -r /var/lib/apt/lists/*
+
+RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none
+
+RUN rustup toolchain install {{ RUST_TOOLCHAIN }} && \
+    rustup target add wasm32-unknown-unknown --toolchain {{ RUST_TOOLCHAIN }} && \
+    rustup default {{ RUST_TOOLCHAIN }} && \
+    rustup target list --installed && \
+    rustup show
+
+RUN mkdir /unique_parachain
+WORKDIR /unique_parachain
+
+# ===== BUILD ACALA =====
+FROM rust-builder as builder-hydradx-bin
+
+WORKDIR /unique_parachain
+
+RUN git clone --depth 1 -b {{ HYDRADX_BUILD_BRANCH }} https://github.com/galacticcouncil/HydraDX-node.git
+
+RUN --mount=type=cache,target=/cargo-home/registry \
+    --mount=type=cache,target=/cargo-home/git \
+    --mount=type=cache,target=/unique_parachain/parachain/target \
+    cd HydraDX-node && \
+    CARGO_INCREMENTAL=0 cargo build --release --locked && \
+    mkdir /unique_parachain/Hydradx && \
+    mv ./target/release/hydradx /unique_parachain/Hydradx/hydradx
+
+# ===== BIN ======
+
+FROM ubuntu:22.04 as builder-hydradx
+
+COPY --from=builder-hydradx-bin /unique_parachain/Hydradx/hydradx /bin/hydradx
+ENTRYPOINT ["/bin/hydradx"]
modified.envdiffbeforeafterboth
--- a/.env
+++ b/.env
@@ -12,7 +12,7 @@
 UNIQUE_MAINNET_BRANCH=release-v10010063
 UNIQUE_REPLICA_FROM=wss://ws.unique.network:443
 POLKADEX_BUILD_BRANCH=v1.1.0
-
+HYDRADX_BUILD_BRANCH=v20.1.1
 
 KUSAMA_MAINNET_BRANCH=release-v1.0.0
 STATEMINE_BUILD_BRANCH=release-parachains-v9430
modified.github/workflows/xcm.ymldiffbeforeafterboth
--- a/.github/workflows/xcm.yml
+++ b/.github/workflows/xcm.yml
@@ -37,9 +37,9 @@
         id: create_matrix
         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 {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}
+            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 }}}, hydradx_version {${{ env.HYDRADX_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 }}}, hydradx_version {${{ env.HYDRADX_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 }}}, hydradx_version {${{ env.HYDRADX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime}
 
   xcm:
     needs: prepare-execution-marix
@@ -280,6 +280,40 @@
             fi
         shell: bash
 
+      # Check Hydradx version and build it if it doesn't exist in repository
+      - name: Generate ENV related extend Dockerfile file for Hydradx
+        uses: cuchi/jinja2-action@v1.2.0
+        with:
+          template: .docker/Dockerfile-hydradx.j2
+          output_file: .docker/Dockerfile-hydradx.${{ matrix.hydradx_version }}.yml
+          variables: |
+            RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+            HYDRADX_BUILD_BRANCH=${{ matrix.hydradx_version }}
+
+      - name: Check if the dockerhub repository contains the needed HYDRADX version
+        run: |
+          # aquire token
+            TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${{ secrets.CORE_DOCKERHUB_USERNAME }}'", "password": "'${{ secrets.CORE_DOCKERHUB_TOKEN }}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token)
+            export TOKEN=$TOKEN
+
+          # Get TAGS from DOCKERHUB repository
+            HYDRADX_TAGS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/uniquenetwork/builder-hydradx/tags/?page_size=100 | jq -r '."results"[]["name"]')
+          # Show TAGS
+            echo "HYDRADX TAGS:"
+            echo $HYDRADX_TAGS
+          # Check correct version HYDRADX and build it if it doesn't exist in HYDRADX TAGS
+            if [[ ${HYDRADX_TAGS[*]} =~ (^|[[:space:]])"${{ matrix.hydradx_version }}"($|[[:space:]]) ]]; then
+                echo "Repository has needed HYDRADX version";
+                docker pull uniquenetwork/builder-hydradx:${{ matrix.hydradx_version }}
+            else
+                echo "Repository has not needed HYDRADX version, so build it";
+                cd .docker/ && docker build --file ./Dockerfile-hydradx.${{ matrix.hydradx_version }}.yml --tag uniquenetwork/builder-hydradx:${{ matrix.hydradx_version }} .
+                echo "Push needed HYDRADX version to the repository";
+                docker push uniquenetwork/builder-hydradx:${{ matrix.hydradx_version }}
+            fi
+        shell: bash
+
+
       - name: Build unique-chain
         run: |
           docker build --file .docker/Dockerfile-unique \
@@ -318,6 +352,7 @@
             snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/cumulus':{dockerImage:'uniquenetwork/builder-cumulus:${{ matrix.cumulus_version }}'}})
             snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/astar':{dockerImage:'uniquenetwork/builder-astar:${{ matrix.astar_version }}'}})
             snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/polkadex':{dockerImage:'uniquenetwork/builder-polkadex:${{ matrix.polkadex_version }}'}})
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/hydradx':{dockerImage:'uniquenetwork/builder-hydradx:${{ matrix.hydradx_version }}'}})
 
       - name: Upload network config
         uses: actions/upload-artifact@v3
modified.vscode/settings.jsondiffbeforeafterboth
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -10,5 +10,5 @@
 		"SKIP_WASM_BUILD": "1"
 	},
 	"rust-analyzer.cargo.features": ["runtime-benchmarks", "try-runtime"],
-	"solidity.packageDefaultDependenciesDirectory": "tests/node_modules"
+	"solidity.packageDefaultDependenciesDirectory": "js-packages/node_modules"
 }
modifiedjs-packages/.vscode/settings.jsondiffbeforeafterboth
--- a/js-packages/.vscode/settings.json
+++ b/js-packages/.vscode/settings.json
@@ -3,7 +3,7 @@
 		"RUN_GOV_TESTS": "1",
 		"RUN_XCM_TESTS": "1"
 	},
-	"mochaExplorer.files": "src/**/*.test.ts",
+	"mochaExplorer.files": "tests/**/*.test.ts",
 	"mochaExplorer.require": "ts-node/register",
 	"eslint.format.enable": true,
 	"[javascript]": {
modifiedjs-packages/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.dev.ts
+++ b/js-packages/playgrounds/unique.dev.ts
@@ -17,7 +17,7 @@
 import type {FrameSystemEventRecord, StagingXcmV2TraitsError, StagingXcmV3TraitsOutcome} from '@polkadot/types/lookup';
 import type {SignerOptions, VoidFn} from '@polkadot/api/types';
 import {spawnSync} from 'child_process';
-import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup} from './unique.xcm.js';
+import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup, HydraDxHelper} from './unique.xcm.js';
 import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance.js';
 import type {ICollectiveGroup, IFellowshipGroup} from './unique.governance.js';
 
@@ -667,6 +667,20 @@
   }
 }
 
+export class DevHydraDxHelper extends HydraDxHelper {
+  wait: WaitGroup;
+  fastDemocracy: HydraFastDemocracyGroup;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.helperBase = options.helperBase ?? DevHydraDxHelper;
+
+    super(logger, options);
+
+    this.wait = new WaitGroup(this);
+    this.fastDemocracy = new HydraFastDemocracyGroup(this);
+  }
+}
+
 export class DevKaruraHelper extends DevAcalaHelper {}
 
 export class ArrangeGroup {
@@ -1150,6 +1164,91 @@
   }
 }
 
+class HydraFastDemocracyGroup {
+  helper: DevHydraDxHelper;
+
+  constructor(helper: DevHydraDxHelper) {
+    this.helper = helper;
+  }
+
+  async executeProposal(proposalDesciption: string, encodedProposal: string) {
+    const proposalHash = blake2AsHex(encodedProposal);
+    const aliceAccount = this.helper.util.fromSeed('//Alice');
+    const bobAccount = this.helper.util.fromSeed('//Bob');
+    const eveAccount = this.helper.util.fromSeed('//Eve');
+
+    const councilVotingThreshold = 1;
+    const technicalCommitteeThreshold = 3;
+    const fastTrackVotingPeriod = 3;
+    const fastTrackDelayPeriod = 0;
+
+    console.log(`[democracy] executing '${proposalDesciption}' proposal`);
+
+    // >>> Propose external motion through council >>>
+    console.log('\t* Propose external motion through council.......');
+    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});
+    const encodedMotion = externalMotion?.method.toHex() || '';
+    const motionHash = blake2AsHex(encodedMotion);
+    console.log('\t* Motion hash is %s', motionHash);
+
+    await this.helper.collective.council.propose(
+      aliceAccount,
+      councilVotingThreshold,
+      externalMotion,
+      externalMotion.encodedLength,
+    );
+
+    console.log('\t* Propose external motion through council.......DONE');
+    // <<< Propose external motion through council <<<
+
+    // >>> Fast track proposal through technical committee >>>
+    console.log('\t* Fast track proposal through technical committee.......');
+    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);
+    const encodedFastTrack = fastTrack?.method.toHex() || '';
+    const fastTrackHash = blake2AsHex(encodedFastTrack);
+    console.log('\t* FastTrack hash is %s', fastTrackHash);
+
+    await this.helper.collective.techCommittee.propose(aliceAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
+
+    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;
+    await this.helper.collective.techCommittee.vote(aliceAccount, fastTrackHash, techProposalIdx, true);
+    await this.helper.collective.techCommittee.vote(bobAccount, fastTrackHash, techProposalIdx, true);
+    await this.helper.collective.techCommittee.vote(eveAccount, fastTrackHash, techProposalIdx, true);
+
+    await this.helper.collective.techCommittee.close(
+      bobAccount,
+      fastTrackHash,
+      techProposalIdx,
+      {
+        refTime: 1_000_000_000,
+        proofSize: 1_000_000,
+      },
+      fastTrack.encodedLength,
+    );
+    console.log('\t* Fast track proposal through technical committee.......DONE');
+    // <<< Fast track proposal through technical committee <<<
+
+    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);
+    const referendumIndex = democracyStarted.referendumIndex;
+
+    // >>> Referendum voting >>>
+    console.log(`\t* Referendum #${referendumIndex} voting.......`);
+    await this.helper.democracy.referendumVote(eveAccount, referendumIndex, {
+      balance: 10_000_000_000_000_000_000n,
+      vote: {aye: true, conviction: 1},
+    });
+    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);
+    // <<< Referendum voting <<<
+
+    // Wait the proposal to pass
+    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);
+
+    await this.helper.wait.newBlocks(1);
+
+    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);
+  }
+}
+
 class WaitGroup {
   helper: ChainHelperBase;
 
modifiedjs-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -71,6 +71,31 @@
   }
 }
 
+class DemocracyGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+  notePreimagePallet: string;
+
+  constructor(helper: T, options: { [key: string]: any } = {}) {
+    super(helper);
+    this.notePreimagePallet = options.notePreimagePallet;
+  }
+
+  async notePreimage(signer: TSigner, encodedProposal: string) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true);
+  }
+
+  externalProposeMajority(proposal: any) {
+    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]);
+  }
+
+  fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {
+    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
+  }
+
+  async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);
+  }
+}
+
 class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {
   collective: string;
 
@@ -97,6 +122,31 @@
   }
 }
 
+class CollectiveGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+  collective: string;
+
+  constructor(helper: T, palletName: string) {
+    super(helper);
+
+    this.collective = palletName;
+  }
+
+  async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);
+  }
+
+  async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);
+  }
+
+  async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);
+  }
+
+  async proposalCount() {
+    return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));
+  }
+}
 class PolkadexXcmHelperGroup<T extends ChainHelperBase> extends HelperGroup<T> {
   async whitelistToken(signer: TSigner, assetId: any) {
     await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);
@@ -372,3 +422,28 @@
   }
 }
 
+export class HydraDxHelper extends XcmChainHelper {
+  balance: SubstrateBalanceGroup<HydraDxHelper>;
+  xcm: XcmGroup<HydraDxHelper>;
+  xTokens: XTokensGroup<HydraDxHelper>;
+  democracy: DemocracyGroup<HydraDxHelper>;
+  collective: {
+    council: CollectiveGroup<HydraDxHelper>,
+    techCommittee: CollectiveGroup<HydraDxHelper>,
+  };
+
+  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+    super(logger, options.helperBase ?? HydraDxHelper);
+    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';
+
+    this.balance = new SubstrateBalanceGroup(this);
+    this.xcm = new XcmGroup(this, 'polkadotXcm');
+    this.xTokens = new XTokensGroup(this);
+    this.democracy = new DemocracyGroup(this, options);
+    this.collective = {
+      council: new CollectiveGroup(this, 'council'),
+      techCommittee: new CollectiveGroup(this, 'technicalCommittee'),
+    };
+  }
+}
+
modifiedjs-packages/tests/config.tsdiffbeforeafterboth
--- a/js-packages/tests/config.ts
+++ b/js-packages/tests/config.ts
@@ -29,6 +29,7 @@
   statemineUrl: process.env.RELAY_STATEMINE_URL || 'ws://127.0.0.1:9948',
   statemintUrl: process.env.RELAY_STATEMINT_URL || 'ws://127.0.0.1:9948',
   polkadexUrl: process.env.RELAY_POLKADEX_URL || 'ws://127.0.0.1:9950',
+  hydraDxUrl: process.env.RELAY_HYDRADX_URL || 'ws://127.0.0.1:9951',
 };
 
 export default config;
modifiedjs-packages/tests/util/createHrmp.tsdiffbeforeafterboth
--- a/js-packages/tests/util/createHrmp.ts
+++ b/js-packages/tests/util/createHrmp.ts
@@ -27,6 +27,7 @@
       await bidirOpen(1001, 1004);
       await bidirOpen(1001, 1005);
       await bidirOpen(1001, 1006);
+      await bidirOpen(1001, 1007);
       break;
     default:
       throw new Error(`unknown hrmp config profile: ${profile}`);
modifiedjs-packages/tests/util/index.tsdiffbeforeafterboth
--- a/js-packages/tests/util/index.ts
+++ b/js-packages/tests/util/index.ts
@@ -11,7 +11,7 @@
 import config from '../config.js';
 import {ChainHelperBase} from '@unique/playgrounds/unique.js';
 import type {ILogger} from '@unique/playgrounds/types.js';
-import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper} from '@unique/playgrounds/unique.dev.js';
+import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper, DevHydraDxHelper} from '@unique/playgrounds/unique.dev.js';
 import {dirname} from 'path';
 import {fileURLToPath} from 'url';
 
@@ -91,6 +91,8 @@
 
 export const usingPolkadexPlaygrounds = (url: string, code: (helper: DevPolkadexHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevPolkadexHelper>(DevPolkadexHelper, url, code);
 
+export const usingHydraDxPlaygrounds = (url: string, code: (helper: DevHydraDxHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevHydraDxHelper>(DevHydraDxHelper, url, code);
+
 export const MINIMUM_DONOR_FUND = 4_000_000n;
 export const DONOR_FUNDING = 4_000_000n;
 
modifiedjs-packages/tests/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/lowLevelXcmUnique.test.ts
+++ b/js-packages/tests/xcm/lowLevelXcmUnique.test.ts
@@ -16,10 +16,10 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import config from '../config.js';
-import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util/index.js';
+import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingHydraDxPlaygrounds} from '../util/index.js';
 import {nToBigInt} from '@polkadot/util';
 import {hexToString} from '@polkadot/util';
-import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl,  moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types.js';
+import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl,  hydraDxUrl,  moonbeamUrl, polkadexUrl, uniqueAssetId} from './xcm.types.js';
 
 const testHelper = new XcmTestHelper('unique');
 
@@ -364,3 +364,40 @@
     await testHelper.rejectReserveTransferUNQfrom('astar', alice);
   });
 });
+
+describeXCM('[XCMLL] Integration test: Exchanging tokens with HydraDx', () => {
+  let alice: IKeyringPair;
+  let randomAccount: IKeyringPair;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = await privateKey('//Alice');
+      console.log(config.acalaUrl);
+      randomAccount = helper.arrange.createEmptyAccount();
+
+      // Set the default version to wrap the first message to other chains.
+      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
+    });
+
+    await usingHydraDxPlaygrounds(hydraDxUrl, async (helper) => {
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
+    });
+  });
+
+  itSub('Should connect and send UNQ to HydraDx', async () => {
+    await testHelper.sendUnqTo('hydraDx', randomAccount);
+  });
+
+  itSub('Should connect to HydraDx and send UNQ back', async () => {
+    await testHelper.sendUnqBack('hydraDx', alice, randomAccount);
+  });
+
+  itSub('HydraDx can send only up to its balance', async () => {
+    await testHelper.sendOnlyOwnedBalance('hydraDx', alice);
+  });
+
+  itSub('Should not accept reserve transfer of UNQ from HydraDx', async () => {
+    await testHelper.rejectReserveTransferUNQfrom('hydraDx', alice);
+  });
+});
\ No newline at end of file
modifiedjs-packages/tests/xcm/xcm.types.tsdiffbeforeafterboth
after · js-packages/tests/xcm/xcm.types.ts
1import type {IKeyringPair} from '@polkadot/types/types';2import {hexToString} from '@polkadot/util';3import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingHydraDxPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util/index.js';4import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';5import config from '../config.js';67export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);8export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);9export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);10export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);11export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);12export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);13export const HYDRADX_CHAIN = +(process.env.RELAY_HYDRADX_ID || 2034);1415export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);16export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);17export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);18export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);19export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);2021export const relayUrl = config.relayUrl;22export const statemintUrl = config.statemintUrl;23export const statemineUrl = config.statemineUrl;2425export const acalaUrl = config.acalaUrl;26export const moonbeamUrl = config.moonbeamUrl;27export const astarUrl = config.astarUrl;28export const polkadexUrl = config.polkadexUrl;29export const hydraDxUrl = config.hydraDxUrl;3031export const karuraUrl = config.karuraUrl;32export const moonriverUrl = config.moonriverUrl;33export const shidenUrl = config.shidenUrl;3435export const SAFE_XCM_VERSION = 3;363738export const RELAY_DECIMALS = 12;39export const STATEMINE_DECIMALS = 12;40export const KARURA_DECIMALS = 12;41export const SHIDEN_DECIMALS = 18n;42export const QTZ_DECIMALS = 18n;4344export const ASTAR_DECIMALS = 18n;45export const UNQ_DECIMALS = 18n;4647export const maxWaitBlocks = 6;4849export const uniqueMultilocation = {50  parents: 1,51  interior: {52    X1: {53      Parachain: UNIQUE_CHAIN,54    },55  },56};57export const uniqueVersionedMultilocation = {58  V3: uniqueMultilocation,59};6061export const uniqueAssetId = {62  Concrete: uniqueMultilocation,63};6465export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {66  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash67        && event.outcome.isFailedToTransactAsset);68};69export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {70  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash71         && event.outcome.isUntrustedReserveLocation);72};7374export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => {75  // The correct messageHash for downward messages can't be reliably obtained76  await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission);77};7879export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => {80  // The correct messageHash for downward messages can't be reliably obtained81  await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete);82};8384export const NETWORKS = {85  acala: usingAcalaPlaygrounds,86  astar: usingAstarPlaygrounds,87  polkadex: usingPolkadexPlaygrounds,88  moonbeam: usingMoonbeamPlaygrounds,89  moonriver: usingMoonriverPlaygrounds,90  karura: usingKaruraPlaygrounds,91  shiden: usingShidenPlaygrounds,92  hydraDx: usingHydraDxPlaygrounds,93} as const;94type NetworkNames = keyof typeof NETWORKS;9596type NativeRuntime = 'opal' | 'quartz' | 'unique';9798export function mapToChainId(networkName: keyof typeof NETWORKS): number {99  switch (networkName) {100    case 'acala':101      return ACALA_CHAIN;102    case 'astar':103      return ASTAR_CHAIN;104    case 'moonbeam':105      return MOONBEAM_CHAIN;106    case 'polkadex':107      return POLKADEX_CHAIN;108    case 'moonriver':109      return MOONRIVER_CHAIN;110    case 'karura':111      return KARURA_CHAIN;112    case 'shiden':113      return SHIDEN_CHAIN;114    case 'hydraDx':115      return HYDRADX_CHAIN;116  }117}118119export function mapToChainUrl(networkName: NetworkNames): string {120  switch (networkName) {121    case 'acala':122      return acalaUrl;123    case 'astar':124      return astarUrl;125    case 'moonbeam':126      return moonbeamUrl;127    case 'polkadex':128      return polkadexUrl;129    case 'moonriver':130      return moonriverUrl;131    case 'karura':132      return karuraUrl;133    case 'shiden':134      return shidenUrl;135    case 'hydraDx':136      return hydraDxUrl;137  }138}139140export function getDevPlayground(name: NetworkNames) {141  return NETWORKS[name];142}143144export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;145export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;146export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;147export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;148export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;149150export class XcmTestHelper {151  private _balanceUniqueTokenInit: bigint = 0n;152  private _balanceUniqueTokenMiddle: bigint = 0n;153  private _balanceUniqueTokenFinal: bigint = 0n;154  private _unqFees: bigint = 0n;155  private _nativeRuntime: NativeRuntime;156157  constructor(runtime: NativeRuntime) {158    this._nativeRuntime = runtime;159  }160161  private _getNativeId() {162    switch (this._nativeRuntime) {163      case 'opal':164        // To-Do165        return 1001;166      case 'quartz':167        return QUARTZ_CHAIN;168      case 'unique':169        return UNIQUE_CHAIN;170    }171  }172173  private _isAddress20FormatFor(network: NetworkNames) {174    switch (network) {175      case 'moonbeam':176      case 'moonriver':177        return true;178      default:179        return false;180    }181  }182183  private _runtimeVersionedMultilocation() {184    return {185      V3: {186        parents: 1,187        interior: {188          X1: {189            Parachain: this._getNativeId(),190          },191        },192      },193    };194  }195196  private _uniqueChainMultilocationForRelay() {197    return {198      V3: {199        parents: 0,200        interior: {201          X1: {Parachain: this._getNativeId()},202        },203      },204    };205  }206207  async sendUnqTo(208    networkName: keyof typeof NETWORKS,209    randomAccount: IKeyringPair,210    randomAccountOnTargetChain = randomAccount,211  ) {212    const networkUrl = mapToChainUrl(networkName);213    const targetPlayground = getDevPlayground(networkName);214    await usingPlaygrounds(async (helper) => {215      this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);216      const destination = {217        V2: {218          parents: 1,219          interior: {220            X1: {221              Parachain: mapToChainId(networkName),222            },223          },224        },225      };226227      const beneficiary = {228        V2: {229          parents: 0,230          interior: {231            X1: (232              this._isAddress20FormatFor(networkName) ?233                {234                  AccountKey20: {235                    network: 'Any',236                    key: randomAccountOnTargetChain.address,237                  },238                }239                :240                {241                  AccountId32: {242                    network: 'Any',243                    id: randomAccountOnTargetChain.addressRaw,244                  },245                }246            ),247          },248        },249      };250251      const assets = {252        V2: [253          {254            id: {255              Concrete: {256                parents: 0,257                interior: 'Here',258              },259            },260            fun: {261              Fungible: TRANSFER_AMOUNT,262            },263          },264        ],265      };266      const feeAssetItem = 0;267268      await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');269      const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);270      this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);271272      this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;273      console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees));274      expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true;275276      await targetPlayground(networkUrl, async (helper) => {277      /*278        Since only the parachain part of the Polkadex279        infrastructure is launched (without their280        solochain validators), processing incoming281        assets will lead to an error.282        This error indicates that the Polkadex chain283        received a message from the Unique network,284        since the hash is being checked to ensure285        it matches what was sent.286      */287        if(networkName == 'polkadex' || networkName =='hydraDx') {288          await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);289        } else {290          await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);291        }292      });293294    });295  }296297  async sendUnqBack(298    networkName: keyof typeof NETWORKS,299    sudoer: IKeyringPair,300    randomAccountOnUnq: IKeyringPair,301  ) {302    const networkUrl = mapToChainUrl(networkName);303304    const targetPlayground = getDevPlayground(networkName);305    await usingPlaygrounds(async (helper) => {306307      const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(308        randomAccountOnUnq.addressRaw,309        {310          Concrete: {311            parents: 1,312            interior: {313              X1: {Parachain: this._getNativeId()},314            },315          },316        },317        SENDBACK_AMOUNT,318      );319320      let xcmProgramSent: any;321322323      await targetPlayground(networkUrl, async (helper) => {324        if('getSudo' in helper) {325          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), xcmProgram);326          xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);327        } else if('fastDemocracy' in helper) {328          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), xcmProgram]);329          // Needed to bypass the call filter.330          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);331          await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);332          xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);333        }334      });335336      await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);337338      this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);339340      expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);341342    });343  }344345  async sendOnlyOwnedBalance(346    networkName: keyof typeof NETWORKS,347    sudoer: IKeyringPair,348  ) {349    const networkUrl = mapToChainUrl(networkName);350    const targetPlayground = getDevPlayground(networkName);351352    const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);353354    await usingPlaygrounds(async (helper) => {355      const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));356      await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);357      const moreThanTargetChainHas = 2n * targetChainBalance;358359      const targetAccount = helper.arrange.createEmptyAccount();360361      const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(362        targetAccount.addressRaw,363        {364          Concrete: {365            parents: 0,366            interior: 'Here',367          },368        },369        moreThanTargetChainHas,370      );371372      let maliciousXcmProgramSent: any;373374375      await targetPlayground(networkUrl, async (helper) => {376        if('getSudo' in helper) {377          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgram);378          maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);379        } else if('fastDemocracy' in helper) {380          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgram]);381          // Needed to bypass the call filter.382          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);383          await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);384          maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);385        }386      });387388      await expectFailedToTransact(helper, maliciousXcmProgramSent);389390      const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);391      expect(targetAccountBalance).to.be.equal(0n);392    });393  }394395  async rejectReserveTransferUNQfrom(networkName: keyof typeof NETWORKS, sudoer: IKeyringPair) {396    const networkUrl = mapToChainUrl(networkName);397    const targetPlayground = getDevPlayground(networkName);398399    await usingPlaygrounds(async (helper) => {400      const testAmount = 10_000n * (10n ** UNQ_DECIMALS);401      const targetAccount = helper.arrange.createEmptyAccount();402403      const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(404        targetAccount.addressRaw,405        {406          Concrete: {407            parents: 1,408            interior: {409              X1: {410                Parachain: this._getNativeId(),411              },412            },413          },414        },415        testAmount,416      );417418      const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(419        targetAccount.addressRaw,420        {421          Concrete: {422            parents: 0,423            interior: 'Here',424          },425        },426        testAmount,427      );428429      let maliciousXcmProgramFullIdSent: any;430      let maliciousXcmProgramHereIdSent: any;431      const maxWaitBlocks = 3;432433      // Try to trick Unique using full UNQ identification434      await targetPlayground(networkUrl, async (helper) => {435        if('getSudo' in helper) {436          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);437          maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);438        }439        // Moonbeam case440        else if('fastDemocracy' in helper) {441          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);442          // Needed to bypass the call filter.443          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);444          await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using path asset identification`,batchCall);445446          maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);447        }448      });449450451      await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);452453      let accountBalance = await helper.balance.getSubstrate(targetAccount.address);454      expect(accountBalance).to.be.equal(0n);455456      // Try to trick Unique using shortened UNQ identification457      await targetPlayground(networkUrl, async (helper) => {458        if('getSudo' in helper) {459          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId);460          maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);461        }462        else if('fastDemocracy' in helper) {463          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]);464          // Needed to bypass the call filter.465          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);466          await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);467468          maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);469        }470      });471472      await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);473474      accountBalance = await helper.balance.getSubstrate(targetAccount.address);475      expect(accountBalance).to.be.equal(0n);476    });477  }478479  async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {480    const networkUrl = mapToChainUrl(networkName);481    const targetPlayground = getDevPlayground(networkName);482    let messageSent: any;483484    await usingPlaygrounds(async (helper) => {485      const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(486        helper.arrange.createEmptyAccount().addressRaw,487        {488          Concrete: {489            parents: 1,490            interior: {491              X1: {492                Parachain: mapToChainId(networkName),493              },494            },495          },496        },497        TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,498      );499      await targetPlayground(networkUrl, async (helper) => {500        if('getSudo' in helper) {501          await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);502          messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);503        } else if('fastDemocracy' in helper) {504          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);505          // Needed to bypass the call filter.506          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);507          await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);508509          messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);510        }511      });512      await expectFailedToTransact(helper, messageSent);513    });514  }515516  async registerRelayNativeTokenOnUnique(alice: IKeyringPair) {517    return await usingPlaygrounds(async (helper) => {518      const relayLocation = {519        parents: 1,520        interior: 'Here',521      };522      const relayAssetId = {Concrete: relayLocation};523524      const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayAssetId);525      if(relayCollectionId == null) {526        const name = 'Relay Tokens';527        const tokenPrefix = 'xDOT';528        const decimals = 10;529        await helper.getSudo().foreignAssets.register(alice, relayAssetId, name, tokenPrefix, {Fungible: decimals});530531        return await helper.foreignAssets.foreignCollectionId(relayAssetId);532      } else {533        console.log('Relay foreign collection is already registered');534        return relayCollectionId;535      }536    });537  }538}