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

difftreelog

Merge pull request #983 from UniqueNetwork/ci/baedeker-node-only

Yaroslav Bolyukin2023-09-05parents: #98753ec #1a650ec.patch.diff
in: master
Move node-only CI to baedeker

14 files changed

added.baedeker/node-only.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/.baedeker/node-only.jsonnet
@@ -0,0 +1,43 @@
+local
+m = import 'baedeker-library/mixin/spec.libsonnet',
+;
+
+local relay = {
+	name: 'relay',
+	bin: 'bin/polkadot',
+	validatorIdAssignment: 'staking',
+	spec: {Genesis:{
+		chain: 'rococo-local',
+		modify:: m.genericRelay($),
+	}},
+	nodes: {
+		[name]: {
+			bin: $.bin,
+			wantedKeys: 'relay',
+		},
+		for name in ['alice', 'bob', 'charlie', 'dave', 'eve']
+	},
+};
+
+local unique = {
+	name: 'unique',
+	bin: 'bin/unique',
+	paraId: 1001,
+	spec: {Genesis:{
+		modify:: m.genericPara($),
+	}},
+	nodes: {
+		[name]: {
+			bin: $.bin,
+			wantedKeys: 'para',
+		},
+		for name in ['alice', 'bob']
+	},
+};
+
+relay + {
+	parachains: {
+		[para.name]: para,
+		for para in [unique]
+	},
+}
modified.baedeker/xcm-opal.jsonnetdiffbeforeafterboth
--- a/.baedeker/xcm-opal.jsonnet
+++ b/.baedeker/xcm-opal.jsonnet
@@ -2,12 +2,14 @@
 m = import 'baedeker-library/mixin/spec.libsonnet',
 ;
 
+function(relay_spec)
+
 local relay = {
 	name: 'relay',
 	bin: 'bin/polkadot',
 	validatorIdAssignment: 'staking',
 	spec: {Genesis:{
-		chain: 'westend-local',
+		chain: relay_spec,
 		modify:: m.genericRelay($, hrmp = [
 			[$.parachains.opal.paraId, $.parachains.westmint.paraId, 8, 512],
 			[$.parachains.westmint.paraId, $.parachains.opal.paraId, 8, 512],
modified.baedeker/xcm-quartz.jsonnetdiffbeforeafterboth
--- a/.baedeker/xcm-quartz.jsonnet
+++ b/.baedeker/xcm-quartz.jsonnet
@@ -2,12 +2,14 @@
 m = import 'baedeker-library/mixin/spec.libsonnet',
 ;
 
+function(relay_spec)
+
 local relay = {
 	name: 'relay',
 	bin: 'bin/polkadot',
 	validatorIdAssignment: 'staking',
 	spec: {Genesis:{
-		chain: 'westend-local',
+		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 [
@@ -113,7 +115,6 @@
 		[name]: {
 			bin: $.bin,
 			wantedKeys: 'para',
-			legacyRpc: true,
 		},
 		for name in ['alice', 'bob']
 	},
modified.baedeker/xcm-unique.jsonnetdiffbeforeafterboth
--- a/.baedeker/xcm-unique.jsonnet
+++ b/.baedeker/xcm-unique.jsonnet
@@ -2,12 +2,14 @@
 m = import 'baedeker-library/mixin/spec.libsonnet',
 ;
 
+function(relay_spec)
+
 local relay = {
 	name: 'relay',
 	bin: 'bin/polkadot',
 	validatorIdAssignment: 'staking',
 	spec: {Genesis:{
-		chain: 'westend-local',
+		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 [
deleted.docker/Dockerfile-parachain-node-only.j2diffbeforeafterboth
--- a/.docker/Dockerfile-parachain-node-only.j2
+++ /dev/null
@@ -1,67 +0,0 @@
-# ===== Rust builder =====
-FROM uniquenetwork/services:latest as rust-builder
-ENV CARGO_HOME="/cargo-home"
-ENV PATH="/cargo-home/bin:$PATH"
-ENV TZ=UTC
-
-RUN rustup toolchain uninstall $(rustup toolchain list) && \
-    rustup toolchain install {{ RUST_TOOLCHAIN }} && \
-    rustup default {{ RUST_TOOLCHAIN }} && \
-    rustup target list --installed && \
-    rustup show
-RUN rustup target add wasm32-unknown-unknown --toolchain {{ RUST_TOOLCHAIN }}
-
-RUN mkdir /unique_parachain
-WORKDIR /unique_parachain
-
-
-# ===== BUILD current version ======
-FROM rust-builder as builder-unique-current
-
-ARG PROFILE=release
-
-WORKDIR /unique_parachain
-
-RUN git clone -b {{ MAINNET_BRANCH }} https://github.com/UniqueNetwork/unique-chain.git . && \
-    cargo build --features={{ NETWORK }}-runtime --$PROFILE
-
-# ===== BUILD target version ======
-FROM rust-builder as builder-unique-target
-
-ARG PROFILE=release
-
-COPY . /unique_parachain
-WORKDIR /unique_parachain
-
-RUN cargo build --features={{ NETWORK }}-runtime --$PROFILE
-
-# ===== RUN ======
-
-FROM ubuntu:22.04
-
-RUN apt-get -y update && \
-    apt-get -y install curl git && \
-    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash && \
-    export NVM_DIR="$HOME/.nvm" && \
-    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
-    nvm install v16.16.0 && \
-    nvm use v16.16.0
-
-RUN git clone https://github.com/uniquenetwork/polkadot-launch -b {{ POLKADOT_LAUNCH_BRANCH }}
-
-COPY --from=builder-unique-current /unique_parachain/target/release/unique-collator /unique-chain/current/release/
-
-COPY --from=builder-unique-target /unique_parachain/target/release/unique-collator /unique-chain/target/release/
-COPY --from=builder-unique-target /unique_parachain/target/release/wbuild/{{ WASM_NAME }}-runtime/{{ WASM_NAME }}_runtime.compact.compressed.wasm /unique-chain/target/release/wbuild/{{ WASM_NAME }}-runtime/{{ WASM_NAME }}_runtime.compact.compressed.wasm
-COPY --from=builder-unique-target /unique_parachain/.docker/forkless-config/launch-config-node-only.json /polkadot-launch/launch-config.json
-
-COPY --from=uniquenetwork/builder-polkadot:{{ POLKADOT_BUILD_BRANCH }} /unique_parachain/polkadot/target/release/polkadot /polkadot/target/release/
-COPY --from=uniquenetwork/builder-polkadot:{{ POLKADOT_BUILD_BRANCH }} /unique_parachain/polkadot/target/release/wbuild/westend-runtime/westend_runtime.compact.compressed.wasm /polkadot/target/release/wbuild/westend-runtime/westend_runtime.compact.compressed.wasm
-COPY --from=uniquenetwork/builder-polkadot:{{ POLKADOT_BUILD_BRANCH }} /unique_parachain/polkadot/target/release/wbuild/rococo-runtime/rococo_runtime.compact.compressed.wasm /polkadot/target/release/wbuild/rococo-runtime/rococo_runtime.compact.compressed.wasm
-
-CMD export NVM_DIR="$HOME/.nvm" && \
-    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
-    cd /polkadot-launch && \
-    npm install --global yarn && \
-    yarn install && \
-    yarn start launch-config.json --test-upgrade-parachains -w -n
added.docker/Dockerfile-unique-releasediffbeforeafterboth
--- /dev/null
+++ b/.docker/Dockerfile-unique-release
@@ -0,0 +1,49 @@
+# ===== 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
+
+ARG RUST_TOOLCHAIN
+RUN echo "Using Rust '$RUST_TOOLCHAIN'" && \
+    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 POLKADOT =====
+FROM rust-builder as builder-polkadot-bin
+
+WORKDIR /unique_parachain
+
+ARG UNIQUE_VERSION
+RUN git clone -b "$UNIQUE_VERSION" --depth 1 https://github.com/uniquenetwork/unique-chain.git
+
+ARG RUNTIME_FEATURES
+RUN --mount=type=cache,target=/cargo-home/registry \
+    --mount=type=cache,target=/cargo-home/git \
+    --mount=type=cache,target=/unique_parachain/polkadot/target \
+    cd unique-chain && \
+    CARGO_INCREMENTAL=0 cargo build --release --features="$RUNTIME_FEATURES" --locked && \
+    mv ./target/release/unique-collator /unique_parachain/unique-chain/
+
+# ===== BIN ======
+
+FROM ubuntu:22.04 as builder-polkadot
+
+COPY --from=builder-polkadot-bin /unique_parachain/unique-chain/unique-collator /bin/unique-collator
+ENTRYPOINT ["/bin/unique-collator"]
deleted.docker/docker-compose.node-only.j2diffbeforeafterboth
--- a/.docker/docker-compose.node-only.j2
+++ /dev/null
@@ -1,16 +0,0 @@
-version: "3.5"
-
-services:
-  node-only:
-    image: uniquenetwork/ci-node-only-local:{{ NETWORK }}-{{ BUILD_TAG }}
-    container_name: node-only
-    expose:
-      - 9944
-      - 9945
-      - 9933
-      - 9844
-    ports:
-      - 127.0.0.1:9944:9944
-      - 127.0.0.1:9945:9945
-      - 127.0.0.1:9933:9933
-      - 127.0.0.1:9844:9844
added.github/actions/buildContainer/action.ymldiffbeforeafterboth
--- /dev/null
+++ b/.github/actions/buildContainer/action.yml
@@ -0,0 +1,59 @@
+name: Build or pull container
+description: ''
+inputs:
+  container:
+    description: Which name to fetch/push
+    required: true
+  tag:
+    description: Which tag to fetch/push
+    required: true
+  context:
+    description: Container context
+    required: true
+    default: "."
+  dockerfile:
+    description: Path to dockerfile (relative to context)
+    required: true
+  args:
+    description: Docker build extra args
+    default: ''
+  dockerhub_username:
+    description: Secret
+  dockerhub_token:
+    description: Secret
+outputs:
+  name:
+    description: Full container name
+    value: ${{ steps.ensure.outputs.name }}
+runs:
+  using: "composite"
+  steps:
+    - name: Ensure have ${{ inputs.container }}:${{ inputs.tag }}
+      id: ensure
+      run: |
+        echo "Wanted container: ${{ inputs.container }}:${{ inputs.tag }}"
+
+        TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${{ inputs.dockerhub_username }}'", "password": "'${{ inputs.dockerhub_token }}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token)
+
+        # Get TAGS from DOCKERHUB
+        TAGS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/${{ inputs.container }}/tags/?page_size=100 | jq -r '."results"[]["name"]' || echo "")
+
+        echo "Available tags:"
+        echo "$TAGS"
+
+        # Check correct version POLKADOT and build it if it doesn't exist in POLKADOT TAGS
+        if [[ ${TAGS[*]} =~ (^|[[:space:]])"${{ inputs.tag }}"($|[[:space:]]) ]]; then
+          echo "Repository has needed version, pulling";
+          docker pull ${{ inputs.container }}:${{ inputs.tag }}
+        else
+          echo "Repository had no needed version, so build it";
+          cd "${{ inputs.context }}" && docker build --file "${{ inputs.dockerfile }}" \
+            $BUILD_ARGS --tag ${{ inputs.container }}:${{ inputs.tag }} \
+            .
+          echo "Push built version to the repository";
+          docker push ${{ inputs.container }}:${{ inputs.tag }} || true
+        fi
+        echo "name=${{ inputs.container }}:${{ inputs.tag }}" >> $GITHUB_OUTPUT
+      env:
+        BUILD_ARGS: ${{ inputs.args }}
+      shell: bash
modified.github/actions/prepare/action.ymldiffbeforeafterboth
--- a/.github/actions/prepare/action.yml
+++ b/.github/actions/prepare/action.yml
@@ -14,7 +14,16 @@
       shell: bash  
       run: |  
         echo "LAST_COMMIT_SHA=${GITHUB_SHA}" >> ${GITHUB_ENV}
-    - name: Set build SHA
+    - name: Run find-and-replace to remove slashes from branch name
+      uses: mad9000/actions-find-and-replace-string@4
+      id: ref_slug
+      with:
+        source: ${{ github.head_ref }}
+        find: '/'
+        replace: '-'
+    - name: Set BUILD_SHA, REF_SLUG
       shell: bash
       run: |
         echo "BUILD_SHA=${LAST_COMMIT_SHA:0:8}" >> $GITHUB_ENV
+        echo "REF_SLUG=${{ steps.ref_slug.outputs.value }}" >> $GITHUB_ENV
+
modified.github/workflows/node-only-update.ymldiffbeforeafterboth
--- a/.github/workflows/node-only-update.yml
+++ b/.github/workflows/node-only-update.yml
@@ -37,11 +37,10 @@
         id: create_matrix
         with:
           matrix: |
-            network {opal}, wasm_name {opal}, mainnet_branch {${{ env.OPAL_MAINNET_BRANCH }}}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}
-            network {sapphire}, wasm_name {quartz}, mainnet_branch {${{ env.SAPPHIRE_MAINNET_BRANCH }}}, relay_branch {${{ env.UNIQUEEAST_MAINNET_BRANCH }}}
-            network {quartz}, wasm_name {quartz}, mainnet_branch {${{ env.QUARTZ_MAINNET_BRANCH }}}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}
-            network {unique}, wasm_name {unique}, mainnet_branch {${{ env.UNIQUE_MAINNET_BRANCH }}}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}
-
+            network {opal}, mainnet_branch {${{ env.OPAL_MAINNET_BRANCH }}}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, runtime_features {opal-runtime}
+            network {sapphire}, mainnet_branch {${{ env.SAPPHIRE_MAINNET_BRANCH }}}, relay_branch {${{ env.UNIQUEEAST_MAINNET_BRANCH }}}, runtime_features {sapphire-runtime}
+            network {quartz}, mainnet_branch {${{ env.QUARTZ_MAINNET_BRANCH }}}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, runtime_features {quartz-runtime}
+            network {unique}, mainnet_branch {${{ env.UNIQUE_MAINNET_BRANCH }}}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, runtime_features {unique-runtime}
 
   node-only-update-build:
 
@@ -79,293 +78,137 @@
 
       - name: Read .env file
         uses: xom9ikk/dotenv@v2
-
-      # Build main image for NODE-ONLY-UPDATE
-      - name: Generate ENV related extend Dockerfile file
-        uses: cuchi/jinja2-action@v1.2.0
-        with:
-          template: .docker/Dockerfile-parachain-node-only.j2
-          output_file: .docker/Dockerfile-parachain-node-only.${{ matrix.network }}.yml
-          variables: |
-            RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
-            NETWORK=${{ matrix.network }}
-            MAINNET_BRANCH=${{ matrix.mainnet_branch }}
-            POLKADOT_LAUNCH_BRANCH=${{ env.POLKADOT_LAUNCH_BRANCH }}
-            WASM_NAME=${{ matrix.wasm_name }}
-            POLKADOT_BUILD_BRANCH=${{ matrix.relay_branch }}
-
-      - name: Show build configuration
-        run: cat .docker/Dockerfile-parachain-node-only.${{ matrix.network }}.yml
-
-      - name: Generate launch-config.json
-        uses: cuchi/jinja2-action@v1.2.0
-        with:
-          template: .docker/forkless-config/launch-config-forkless-nodata.j2
-          output_file: .docker/forkless-config/launch-config-node-only.json
-          variables: |
-            WASM_NAME=${{ matrix.wasm_name }}
-            RELAY_CHAIN_TYPE=${{ env.RELAY_CHAIN_TYPE }}
-
-      - name: Show launch-config-forkless configuration
-        run: cat .docker/forkless-config/launch-config-node-only.json
-
-      - name: Run find-and-replace to remove slashes from branch name
-        uses: mad9000/actions-find-and-replace-string@4
-        id: branchname
-        with:
-          source: ${{ github.head_ref }}
-          find: '/'
-          replace: '-'
 
-      - name: Set build SHA
-        shell: bash
-        run: |
-          echo "BUILD_SHA=${LAST_COMMIT_SHA:0:8}" >> $GITHUB_ENV
-
-      - name: Build the stack
-        run: cd .docker/ && docker build --no-cache --file ./Dockerfile-parachain-node-only.${{ matrix.network }}.yml --tag uniquenetwork/ci-node-only-local:${{ matrix.network }}-${{ steps.branchname.outputs.value }}-$BUILD_SHA ../
-
       - name: Log in to Docker Hub
         uses: docker/login-action@v2.1.0
         with:
           username: ${{ secrets.CORE_DOCKERHUB_USERNAME }}
           password: ${{ secrets.CORE_DOCKERHUB_TOKEN }}
-
-      - name: Push docker image version
-        run: docker push uniquenetwork/ci-node-only-local:${{ matrix.network }}-${{ steps.branchname.outputs.value }}-$BUILD_SHA
-
-      - name: Remove builder cache
-        if: always()                   # run this step always
-        run: |
-          docker builder prune -f
-          docker system prune -f          
-
-
-  node-only-update-tests:
-    needs: [prepare-execution-matrix, node-only-update-build]
-    # The type of runner that the job will run on
-    runs-on: [self-hosted-ci, large]
-
-    timeout-minutes: 2880
-
-    name: ${{ matrix.network }}-tests
-
-    continue-on-error: true         #Do not stop testing of matrix runs failed.  As it decided during PR review - it required 50/50& Let's check it with false.
-
-    strategy:
-      matrix:
-        include: ${{fromJson(needs.prepare-execution-matrix.outputs.matrix)}}
 
-    steps:
-      - name: Skip if pull request is in Draft
-        if: github.event.pull_request.draft == true
-        run: exit 1
-
-      - name: Clean Workspace
-        uses: AutoModality/action-clean@v1.1.0
-
-      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
-      - uses: actions/checkout@v3.1.0
-        with:
-          ref: ${{ github.head_ref }}  #Checking out head commit
-
-      - name: Prepare
-        uses: ./.github/actions/prepare
-
-      - name: Set build SHA
-        shell: bash
-        run: |
-          echo "BUILD_SHA=${LAST_COMMIT_SHA:0:8}" >> $GITHUB_ENV
-
-      - name: Run find-and-replace to remove slashes from branch name
-        uses: mad9000/actions-find-and-replace-string@4
-        id: branchname
-        with:
-          source: ${{ github.head_ref }}
-          find: '/'
-          replace: '-'
-
-      - name: Read .env file
-        uses: xom9ikk/dotenv@v2
-
-      - name: Generate ENV related extend file for docker-compose
+      - name: Generate ENV related extend Dockerfile file for POLKADOT
         uses: cuchi/jinja2-action@v1.2.0
         with:
-          template: .docker/docker-compose.node-only.j2
-          output_file: .docker/docker-compose.node-only.${{ matrix.network }}.yml
+          template: .docker/Dockerfile-polkadot.j2
+          output_file: .docker/Dockerfile-polkadot.${{ matrix.relay_branch }}.yml
           variables: |
-            NETWORK=${{ matrix.network }}
-            BUILD_TAG=${{ steps.branchname.outputs.value }}-$BUILD_SHA
-
-      - name: Show build configuration
-        run: cat .docker/docker-compose.node-only.${{ matrix.network }}.yml
-
-      - name: Log in to Docker Hub
-        uses: docker/login-action@v2.1.0
+            RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+            POLKADOT_BUILD_BRANCH=${{ matrix.relay_branch }}
+      - name: Prepare polkadot
+        uses: ./.github/actions/buildContainer
+        id: polkadot
         with:
-          username: ${{ secrets.CORE_DOCKERHUB_USERNAME }}
-          password: ${{ secrets.CORE_DOCKERHUB_TOKEN }}
+          container: uniquenetwork/builder-polkadot
+          tag: ${{ matrix.relay_branch }}
+          context: .docker
+          dockerfile: Dockerfile-polkadot.${{ matrix.relay_branch }}.yml
+          dockerhub_username: ${{ secrets.CORE_DOCKERHUB_USERNAME }}
+          dockerhub_token: ${{ secrets.CORE_DOCKERHUB_TOKEN }}
 
-      - name: Build the stack
-        run: docker-compose -f ".docker/docker-compose.node-only.${{ matrix.network }}.yml" up -d --remove-orphans --force-recreate --timeout 300
+      - name: Prepare mainnet
+        uses: ./.github/actions/buildContainer
+        id: mainnet
+        with:
+          container: uniquenetwork/ci-node-only-${{ matrix.network }}
+          tag: ${{ matrix.mainnet_branch }}
+          context: .docker
+          dockerfile: Dockerfile-unique-release
+          args: |
+            --build-arg RUNTIME_FEATURES=${{ matrix.runtime_features }}
+            --build-arg RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+            --build-arg UNIQUE_VERSION=${{ matrix.mainnet_branch }}
+          dockerhub_username: ${{ secrets.CORE_DOCKERHUB_USERNAME }}
+          dockerhub_token: ${{ secrets.CORE_DOCKERHUB_TOKEN }}
 
-      #  🚀 POLKADOT LAUNCH COMPLETE 🚀
-      - name: Check if docker logs consist messages related to testing of Node Parachain Upgrade.
-        if: success()
-        run: |
-          counter=160
-          function check_container_status {
-                docker inspect -f {{.State.Running}} node-only
-          }
-          function do_docker_logs {
-                docker logs --details node-only  2>&1
-          }
-          function is_started {
-                if [ "$(check_container_status)" == "true" ]; then
-                        echo "Container: node-only RUNNING";
-                        echo "Check Docker logs"
-                        DOCKER_LOGS=$(do_docker_logs)
-                        if [[ ${DOCKER_LOGS} = *"POLKADOT LAUNCH COMPLETE"* ]];then
-                                echo "🚀 POLKADOT LAUNCH COMPLETE 🚀"
-                                return 0
-                        else
-                                echo "Message not found in logs output, repeating..."
-                                return 1
-                        fi
-                else
-                        echo "Container node-only NOT RUNNING"
-                        echo "Halting all future checks"
-                        exit 1
-                fi
-          echo "something goes wrong"
-          exit 1
-          }
-          while ! is_started; do
-                echo "Waiting for special message in log files "
-                sleep 30s
-                counter=$(( $counter - 1 ))
-                echo "Counter: $counter"
-                if [ "$counter" -gt "0" ]; then
-                         continue
-                else
-                         break
-                fi
-          done
-          echo "Halting script"
-          exit 0
-        shell: bash
+      - name: Prepare latest
+        uses: ./.github/actions/buildContainer
+        id: latest
+        with:
+          container: uniquenetwork/ci-node-only-${{ matrix.network }}
+          tag: ${{ env.REF_SLUG }}-${{ env.BUILD_SHA }}
+          context: .
+          dockerfile: .docker/Dockerfile-unique
+          args: |
+            --build-arg RUNTIME_FEATURES=${{ matrix.runtime_features }}
+            --build-arg RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+          dockerhub_username: ${{ secrets.CORE_DOCKERHUB_USERNAME }}
+          dockerhub_token: ${{ secrets.CORE_DOCKERHUB_TOKEN }}
 
       - name: Checkout at '${{ matrix.mainnet_branch }}' branch
         uses: actions/checkout@master
         with:
-          ref: ${{ matrix.mainnet_branch }}  #Checking out head commit
+          ref: ${{ github.head_ref }}
+          # ref: ${{ matrix.mainnet_branch }}  #Checking out head commit
           path: ${{ matrix.mainnet_branch }}
 
       - uses: actions/setup-node@v3.5.1
         with:
           node-version: 16
 
+      - name: Install baedeker
+        uses: UniqueNetwork/baedeker-action/setup@built
+
+      - name: Setup library
+        run: mkdir -p .baedeker/vendor/ && git clone https://github.com/UniqueNetwork/baedeker-library .baedeker/vendor/baedeker-library
+
+      - name: Start network
+        uses: UniqueNetwork/baedeker-action@built
+        id: bdk
+        with:
+          jpath: |
+            .baedeker/vendor
+          tla-str: |
+            relay_spec=westend-local
+          inputs: |
+            .baedeker/node-only.jsonnet
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/polkadot':{dockerImage:'${{ steps.polkadot.outputs.name }}'}})
+            ephemeral:snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/unique':{dockerImage:'${{ steps.mainnet.outputs.name }}'}}, extra_node_mixin = {legacyRpc: true})
+
       - name: Run Parallel tests before Node Parachain upgrade
         working-directory: ${{ matrix.mainnet_branch }}/tests
         if: success() || failure()
         run: |
-          yarn install
+          yarn
           yarn add mochawesome
           ./scripts/wait_for_first_block.sh
           echo "Ready to start tests"
           NOW=$(date +%s) && yarn testParallel --reporter mochawesome --reporter-options reportFilename=test-parallel-${NOW}
         env:
-          RPC_URL: http://127.0.0.1:9944/
+          RPC_URL: ${{ env.RELAY_UNIQUE_HTTP_URL }}
 
       - name: Run Sequential tests before Node Parachain upgrade
         if: success() || failure()
         working-directory: ${{ matrix.mainnet_branch }}/tests
         run: NOW=$(date +%s) && yarn testSequential --reporter mochawesome --reporter-options reportFilename=test-sequential-${NOW}
         env:
-          RPC_URL: http://127.0.0.1:9944/
+          RPC_URL: ${{ env.RELAY_UNIQUE_HTTP_URL }}
 
-      - name: Send SIGUSR1 to polkadot-launch process
+      - name: "Reconcile: only one old node"
         if: success() || failure()
-        run: |
-          #Get PID of polkadot-launch
-          ContainerID=$(docker ps -aqf "name=node-only")
-          PID=$(docker exec node-only pidof 'polkadot-launch')
-          sleep 30s
-          echo -e "\n"
-          echo -e "Restart polkadot-launch process: $PID\n"
-          docker exec node-only kill -SIGUSR1 ${PID}
-          echo "SIGUSR1 sent to Polkadot-launch PID: $PID"
-          sleep 60s
-          echo -e "Show logs of node-only container.\n"
-          docker logs ${ContainerID}
-
-      - name: Get chain logs in case of docker image crashed after Polkadot Launch restart
-        if: failure()                   # run this step only at failure
-        run: |
-          docker exec node-only tail -n 1000 /polkadot-launch/9944.log
-          docker exec node-only tail -n 1000 /polkadot-launch/9945.log
-          docker exec node-only tail -n 1000 /polkadot-launch/alice.log
+        uses: UniqueNetwork/baedeker-action/reconcile@built
+        with:
+          baedeker: ${{ steps.bdk.baedeker }}
+          # Chain should always be built with the mainnet spec, this we first set binary for all nodes expect one, then set mainnet binary for the last node, and then force chainspec to be still generated from mainnet
+          inputs: |
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/unique':{dockerImage:'${{ steps.latest.outputs.name }}'}}, leave = 1, for_chain = false)
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/unique':{dockerImage:'${{ steps.mainnet.outputs.name }}'}}, extra_node_mixin = {legacyRpc: true}, for_chain = false)
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/unique':{dockerImage:'${{ steps.mainnet.outputs.name }}'}})
 
-      - name: copy chain log files from container to the host
+      - name: Ensure network is alive
         if: success() || failure()    # run this step even if previous step failed
         run: |
-          mkdir -p /tmp/node-only-update
-          docker cp node-only:/polkadot-launch/9944.log /tmp/node-only-update/
-          docker cp node-only:/polkadot-launch/9945.log /tmp/node-only-update/
-          docker cp node-only:/polkadot-launch/alice.log /tmp/node-only-update/
+          ./tests/scripts/wait_for_first_block.sh
+        env:
+          RPC_URL: ${{ env.RELAY_UNIQUE_HTTP_URL }}
 
-      - name: Upload chain log files
+      - name: "Reconcile: all nodes are updated"
         if: success() || failure()
-        uses: actions/upload-artifact@v3
+        uses: UniqueNetwork/baedeker-action/reconcile@built
         with:
-          name: node-only-update-chain-logs
-          path: /tmp/node-only-update/
-          if-no-files-found: warn
-
-      - name: Check if docker logs consist messages related to testing of Node Parachain Upgrade.
-        if: success()
-        run: |
-          counter=160
-          function check_container_status {
-                docker inspect -f {{.State.Running}} node-only
-          }
-          function do_docker_logs {
-                docker logs --details node-only  2>&1
-          }
-          function is_started {
-                if [ "$(check_container_status)" == "true" ]; then
-                        echo "Container: node-only RUNNING";
-                        echo "Check Docker logs"
-                        DOCKER_LOGS=$(do_docker_logs)
-                        if [[ ${DOCKER_LOGS} = *"All parachain collators restarted with the new binaries."* ]];then
-                                echo "🌗 All parachain collators restarted with the new binaries."
-                                return 0
-                        else
-                                echo "Message not found in logs output, repeating..."
-                                return 1
-                        fi
-                else
-                        echo "Container node-only NOT RUNNING"
-                        echo "Halting all future checks"
-                        exit 1
-                fi
-          echo "something goes wrong"
-          exit 1
-          }
-          while ! is_started; do
-                echo "Waiting for special message in log files "
-                sleep 30s
-                counter=$(( $counter - 1 ))
-                echo "Counter: $counter"
-                if [ "$counter" -gt "0" ]; then
-                         continue
-                else
-                         break
-                fi
-          done
-          echo "Halting script"
-          exit 0
-        shell: bash
+          baedeker: ${{ steps.bdk.baedeker }}
+          # Chain should always be built with the mainnet spec, this we first set binary for all nodes, and then force chainspec to be still generated from mainnet
+          inputs: |
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/unique':{dockerImage:'${{ steps.latest.outputs.name }}'}}, for_chain = false)
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/unique':{dockerImage:'${{ steps.mainnet.outputs.name }}'}})
 
       - name: Run Parallel tests after Node Parachain upgrade
         working-directory: ${{ matrix.mainnet_branch }}/tests
@@ -377,27 +220,16 @@
           echo "Ready to start tests"
           NOW=$(date +%s) && yarn testParallel --reporter mochawesome --reporter-options reportFilename=test-parallel-${NOW}
         env:
-          RPC_URL: http://127.0.0.1:9944/
+          RPC_URL: ${{ env.RELAY_UNIQUE_HTTP_URL }}
 
       - name: Run Sequential tests after Node Parachain upgrade
         if: success() || failure()
         working-directory: ${{ matrix.mainnet_branch }}/tests
         run: NOW=$(date +%s) && yarn testSequential --reporter mochawesome --reporter-options reportFilename=test-sequential-${NOW}
         env:
-          RPC_URL: http://127.0.0.1:9944/
-
-      - name: Stop running containers
-        if: always()                   # run this step always
-        run: docker-compose -f ".docker/docker-compose.node-only.${{ matrix.network }}.yml" down
+          RPC_URL: ${{ env.RELAY_UNIQUE_HTTP_URL }}
 
       - name: Remove builder cache
         if: always()                   # run this step always
         run: |
-          docker builder prune -f -a
           docker system prune -f
-          docker image prune -f -a
-
-      - name: Remove repo at the end
-        if: always()                   # run this step always
-        run: |
-          ls -ls ./
modified.github/workflows/xcm.ymldiffbeforeafterboth
--- a/.github/workflows/xcm.yml
+++ b/.github/workflows/xcm.yml
@@ -284,25 +284,16 @@
             fi
         shell: bash
 
-
-      - name: Run find-and-replace to remove slashes from branch name
-        uses: mad9000/actions-find-and-replace-string@4
-        id: branchname
-        with:
-          source: ${{ github.head_ref }}
-          find: '/'
-          replace: '-'
-
       - name: Build unique-chain
         run: |
           docker build --file .docker/Dockerfile-unique \
             --build-arg RUNTIME_FEATURES=${{ matrix.runtime_features }} \
             --build-arg RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} \
-            --tag uniquenetwork/ci-xcm-local:${{ matrix.network }}-${{ steps.branchname.outputs.value }}-${{ env.BUILD_SHA }} \
+            --tag uniquenetwork/ci-xcm-local:${{ matrix.network }}-${{ env.REF_SLUG }}-${{ env.BUILD_SHA }} \
             .
 
       - name: Push docker image version
-        run: docker push uniquenetwork/ci-xcm-local:${{ matrix.network }}-${{ steps.branchname.outputs.value }}-${{ env.BUILD_SHA }}
+        run: docker push uniquenetwork/ci-xcm-local:${{ matrix.network }}-${{ env.REF_SLUG }}-${{ env.BUILD_SHA }}
 
       - uses: actions/setup-node@v3.5.1
         with:
@@ -320,10 +311,12 @@
         with:
           jpath: |
             .baedeker/vendor
+          tla-str: |
+            relay_spec=westend-local
           inputs: |
             .baedeker/xcm-${{ matrix.network }}.jsonnet
             snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/polkadot':{dockerImage:'uniquenetwork/builder-polkadot:${{ matrix.relay_branch }}'}})
-            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/unique':{dockerImage:'uniquenetwork/ci-xcm-local:${{ matrix.network }}-${{ steps.branchname.outputs.value }}-${{ env.BUILD_SHA }}'}})
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/unique':{dockerImage:'uniquenetwork/ci-xcm-local:${{ matrix.network }}-${{ env.REF_SLUG }}-${{ env.BUILD_SHA }}'}})
             snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/acala':{dockerImage:'uniquenetwork/builder-acala:${{ matrix.acala_version }}'}})
             snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/moonbeam':{dockerImage:'uniquenetwork/builder-moonbeam:${{ matrix.moonbeam_version }}'}})
             snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/cumulus':{dockerImage:'uniquenetwork/builder-cumulus:${{ matrix.cumulus_version }}'}})
modifiedtests/scripts/wait_for_first_block.shdiffbeforeafterboth
--- a/tests/scripts/wait_for_first_block.sh
+++ b/tests/scripts/wait_for_first_block.sh
@@ -4,24 +4,40 @@
 
 . $DIR/functions.sh
 
-function is_started {
+last_block_id=0
+block_id=0
+function get_block {
 	block_id_hex=$(do_rpc chain_getHeader | jq -r .result.number)
 	block_id=$((${block_id_hex}))
 	echo Id = $block_id
-	if (( $block_id > 1 )); then
+}
+
+function had_new_block {
+	last_block_id=$block_id
+	get_block
+	if (( last_block_id != 0 && block_id > last_block_id )); then
 		return 0
 	fi
 	return 1
 }
 
-while ! is_started; do
-	echo "Waiting for first block..."
+function reset_check {
+	last_block_id=0
+	block_id=0
+}
+
+while ! had_new_block; do
+	echo "Waiting for next block..."
 	sleep 12
 done
+reset_check
+
 echo "Chain is running, but lets wait for another block after a minute, to avoid startup flakiness."
-sleep 120
-while ! is_started; do
-	echo "Waiting for second block..."
+sleep 60
+
+while ! had_new_block; do
+	echo "Waiting for another block..."
 	sleep 12
 done
+
 echo "Chain is running!"
modifiedtests/src/util/globalSetup.tsdiffbeforeafterboth
--- a/tests/src/util/globalSetup.ts
+++ b/tests/src/util/globalSetup.ts
@@ -112,4 +112,8 @@
     });
 };
 
-globalSetup().catch(() => process.exit(1));
+globalSetup().catch(e => {
+  console.error('Setup error');
+  console.error(e);
+  process.exit(1)
+});
modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
after · tests/src/xcm/xcmQuartz.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';2122const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);23const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);24const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);25const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);26const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;34const shidenUrl = config.shidenUrl;3536const RELAY_DECIMALS = 12;37const STATEMINE_DECIMALS = 12;38const KARURA_DECIMALS = 12;39const SHIDEN_DECIMALS = 18n;40const QTZ_DECIMALS = 18n;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4546const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4748const USDT_ASSET_ID = 100;49const USDT_ASSET_METADATA_DECIMALS = 18;50const USDT_ASSET_METADATA_NAME = 'USDT';51const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';52const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;53const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5455const SAFE_XCM_VERSION = 2;5657describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {58  let alice: IKeyringPair;59  let bob: IKeyringPair;6061  let balanceStmnBefore: bigint;62  let balanceStmnAfter: bigint;6364  let balanceQuartzBefore: bigint;65  let balanceQuartzAfter: bigint;66  let balanceQuartzFinal: bigint;6768  let balanceBobBefore: bigint;69  let balanceBobAfter: bigint;70  let balanceBobFinal: bigint;7172  let balanceBobRelayTokenBefore: bigint;73  let balanceBobRelayTokenAfter: bigint;747576  before(async () => {77    await usingPlaygrounds(async (helper, privateKey) => {78      alice = await privateKey('//Alice');79      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor8081      // Set the default version to wrap the first message to other chains.82      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);83    });8485    await usingRelayPlaygrounds(relayUrl, async (helper) => {86      // Fund accounts on Statemine(t)87      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);88      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);89    });9091    await usingStateminePlaygrounds(statemineUrl, async (helper) => {92      const sovereignFundingAmount = 3_500_000_000n;9394      await helper.assets.create(95        alice,96        USDT_ASSET_ID,97        alice.address,98        USDT_ASSET_METADATA_MINIMAL_BALANCE,99      );100      await helper.assets.setMetadata(101        alice,102        USDT_ASSET_ID,103        USDT_ASSET_METADATA_NAME,104        USDT_ASSET_METADATA_DESCRIPTION,105        USDT_ASSET_METADATA_DECIMALS,106      );107      await helper.assets.mint(108        alice,109        USDT_ASSET_ID,110        alice.address,111        USDT_ASSET_AMOUNT,112      );113114      // funding parachain sovereing account on Statemine(t).115      // The sovereign account should be created before any action116      // (the assets pallet on Statemine(t) check if the sovereign account exists)117      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);118      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);119    });120121122    await usingPlaygrounds(async (helper) => {123      const location = {124        V2: {125          parents: 1,126          interior: {X3: [127            {128              Parachain: STATEMINE_CHAIN,129            },130            {131              PalletInstance: STATEMINE_PALLET_INSTANCE,132            },133            {134              GeneralIndex: USDT_ASSET_ID,135            },136          ]},137        },138      };139140      const metadata =141      {142        name: USDT_ASSET_ID,143        symbol: USDT_ASSET_METADATA_NAME,144        decimals: USDT_ASSET_METADATA_DECIMALS,145        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,146      };147      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);148      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);149    });150151152    // Providing the relay currency to the quartz sender account153    // (fee for USDT XCM are paid in relay tokens)154    await usingRelayPlaygrounds(relayUrl, async (helper) => {155      const destination = {156        V2: {157          parents: 0,158          interior: {X1: {159            Parachain: QUARTZ_CHAIN,160          },161          },162        }};163164      const beneficiary = {165        V2: {166          parents: 0,167          interior: {X1: {168            AccountId32: {169              network: 'Any',170              id: alice.addressRaw,171            },172          }},173        },174      };175176      const assets = {177        V2: [178          {179            id: {180              Concrete: {181                parents: 0,182                interior: 'Here',183              },184            },185            fun: {186              Fungible: TRANSFER_AMOUNT_RELAY,187            },188          },189        ],190      };191192      const feeAssetItem = 0;193194      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');195    });196197  });198199  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {200    await usingStateminePlaygrounds(statemineUrl, async (helper) => {201      const dest = {202        V2: {203          parents: 1,204          interior: {X1: {205            Parachain: QUARTZ_CHAIN,206          },207          },208        }};209210      const beneficiary = {211        V2: {212          parents: 0,213          interior: {X1: {214            AccountId32: {215              network: 'Any',216              id: alice.addressRaw,217            },218          }},219        },220      };221222      const assets = {223        V2: [224          {225            id: {226              Concrete: {227                parents: 0,228                interior: {229                  X2: [230                    {231                      PalletInstance: STATEMINE_PALLET_INSTANCE,232                    },233                    {234                      GeneralIndex: USDT_ASSET_ID,235                    },236                  ]},237              },238            },239            fun: {240              Fungible: TRANSFER_AMOUNT,241            },242          },243        ],244      };245246      const feeAssetItem = 0;247248      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);249      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');250251      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);252253      // common good parachain take commission in it native token254      console.log(255        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',256        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),257      );258      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;259260    });261262263    // ensure that asset has been delivered264    await helper.wait.newBlocks(3);265266    // expext collection id will be with id 1267    const free = await helper.ft.getBalance(1, {Substrate: alice.address});268269    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);270271    console.log(272      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',273      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),274    );275    console.log(276      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',277      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),278    );279    // commission has not paid in USDT token280    expect(free).to.be.equal(TRANSFER_AMOUNT);281    // ... and parachain native token282    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;283  });284285  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {286    const destination = {287      V2: {288        parents: 1,289        interior: {X2: [290          {291            Parachain: STATEMINE_CHAIN,292          },293          {294            AccountId32: {295              network: 'Any',296              id: alice.addressRaw,297            },298          },299        ]},300      },301    };302303    const relayFee = 400_000_000_000_000n;304    const currencies: [any, bigint][] = [305      [306        {307          ForeignAssetId: 0,308        },309        TRANSFER_AMOUNT,310      ],311      [312        {313          NativeAssetId: 'Parent',314        },315        relayFee,316      ],317    ];318319    const feeItem = 1;320321    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');322323    // the commission has been paid in parachain native token324    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);325    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));326    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;327328    await usingStateminePlaygrounds(statemineUrl, async (helper) => {329      await helper.wait.newBlocks(3);330331      // The USDT token never paid fees. Its amount not changed from begin value.332      // Also check that xcm transfer has been succeeded333      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;334    });335  });336337  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {338    balanceBobBefore = await helper.balance.getSubstrate(bob.address);339    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});340341    await usingRelayPlaygrounds(relayUrl, async (helper) => {342      const destination = {343        V2: {344          parents: 0,345          interior: {X1: {346            Parachain: QUARTZ_CHAIN,347          },348          },349        }};350351      const beneficiary = {352        V2: {353          parents: 0,354          interior: {X1: {355            AccountId32: {356              network: 'Any',357              id: bob.addressRaw,358            },359          }},360        },361      };362363      const assets = {364        V2: [365          {366            id: {367              Concrete: {368                parents: 0,369                interior: 'Here',370              },371            },372            fun: {373              Fungible: TRANSFER_AMOUNT_RELAY,374            },375          },376        ],377      };378379      const feeAssetItem = 0;380381      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');382    });383384    await helper.wait.newBlocks(3);385386    balanceBobAfter = await helper.balance.getSubstrate(bob.address);387    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});388389    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;390    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;391    console.log(392      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',393      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),394    );395    console.log(396      '[Relay (Westend) -> Quartz] transaction fees: %s WND',397      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),398    );399    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);400    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;401    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;402  });403404  itSub('Should connect and send Relay token back', async ({helper}) => {405    let relayTokenBalanceBefore: bigint;406    let relayTokenBalanceAfter: bigint;407    await usingRelayPlaygrounds(relayUrl, async (helper) => {408      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);409    });410411    const destination = {412      V2: {413        parents: 1,414        interior: {415          X1:{416            AccountId32: {417              network: 'Any',418              id: bob.addressRaw,419            },420          },421        },422      },423    };424425    const currencies: any = [426      [427        {428          NativeAssetId: 'Parent',429        },430        TRANSFER_AMOUNT_RELAY,431      ],432    ];433434    const feeItem = 0;435436    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438    balanceBobFinal = await helper.balance.getSubstrate(bob.address);439    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441    await usingRelayPlaygrounds(relayUrl, async (helper) => {442      await helper.wait.newBlocks(10);443      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448    });449  });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {453  let alice: IKeyringPair;454  let randomAccount: IKeyringPair;455456  let balanceQuartzTokenInit: bigint;457  let balanceQuartzTokenMiddle: bigint;458  let balanceQuartzTokenFinal: bigint;459  let balanceKaruraTokenInit: bigint;460  let balanceKaruraTokenMiddle: bigint;461  let balanceKaruraTokenFinal: bigint;462  let balanceQuartzForeignTokenInit: bigint;463  let balanceQuartzForeignTokenMiddle: bigint;464  let balanceQuartzForeignTokenFinal: bigint;465466  // computed by a test transfer from prod Quartz to prod Karura.467  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9468  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)469  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;470471  const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;472473  before(async () => {474    await usingPlaygrounds(async (helper, privateKey) => {475      alice = await privateKey('//Alice');476      [randomAccount] = await helper.arrange.createAccounts([0n], alice);477478      // Set the default version to wrap the first message to other chains.479      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);480    });481482    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {483      const destination = {484        V2: {485          parents: 1,486          interior: {487            X1: {488              Parachain: QUARTZ_CHAIN,489            },490          },491        },492      };493494      const metadata = {495        name: 'Quartz',496        symbol: 'QTZ',497        decimals: 18,498        minimalBalance: 1000000000000000000n,499      };500501      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);502      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);503      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);504      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});505    });506507    await usingPlaygrounds(async (helper) => {508      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);509      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);510    });511  });512513  itSub('Should connect and send QTZ to Karura', async ({helper}) => {514    const destination = {515      V2: {516        parents: 1,517        interior: {518          X1: {519            Parachain: KARURA_CHAIN,520          },521        },522      },523    };524525    const beneficiary = {526      V2: {527        parents: 0,528        interior: {529          X1: {530            AccountId32: {531              network: 'Any',532              id: randomAccount.addressRaw,533            },534          },535        },536      },537    };538539    const assets = {540      V2: [541        {542          id: {543            Concrete: {544              parents: 0,545              interior: 'Here',546            },547          },548          fun: {549            Fungible: TRANSFER_AMOUNT,550          },551        },552      ],553    };554555    const feeAssetItem = 0;556557    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');558    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);559560    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;561    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;562    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));563564    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {565      await helper.wait.newBlocks(3);566567      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});568      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);569570      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;571      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;572      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;573574      console.log(575        '[Quartz -> Karura] transaction fees on Karura: %s KAR',576        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),577      );578      console.log(579        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',580        helper.util.bigIntToDecimals(karUnqFees),581      );582      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));583      expect(karFees == 0n).to.be.true;584      expect(585        karUnqFees == expectedKaruraIncomeFee,586        'Karura took different income fee, check the Karura foreign asset config',587      ).to.be.true;588    });589  });590591  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {592    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {593      const destination = {594        V2: {595          parents: 1,596          interior: {597            X2: [598              {Parachain: QUARTZ_CHAIN},599              {600                AccountId32: {601                  network: 'Any',602                  id: randomAccount.addressRaw,603                },604              },605            ],606          },607        },608      };609610      const id = {611        ForeignAsset: 0,612      };613614      await helper.xTokens.transfer(randomAccount, id, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');615      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);616      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);617618      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;619      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;620621      console.log(622        '[Karura -> Quartz] transaction fees on Karura: %s KAR',623        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),624      );625      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));626627      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;628      expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;629    });630631    await helper.wait.newBlocks(3);632633    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);634    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;635    expect(actuallyDelivered > 0).to.be.true;636637    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));638639    const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;640    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));641    expect(qtzFees == 0n).to.be.true;642  });643644  itSub('Karura can send only up to its balance', async ({helper}) => {645    // set Karura's sovereign account's balance646    const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);647    const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);648    await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);649650    const moreThanKaruraHas = karuraBalance * 2n;651652    let targetAccountBalance = 0n;653    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);654655    const quartzMultilocation = {656      V2: {657        parents: 1,658        interior: {659          X1: {Parachain: QUARTZ_CHAIN},660        },661      },662    };663664    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(665      targetAccount.addressRaw,666      {667        Concrete: {668          parents: 0,669          interior: 'Here',670        },671      },672      moreThanKaruraHas,673    );674675    let maliciousXcmProgramSent: any;676    const maxWaitBlocks = 3;677678    // Try to trick Quartz679    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {680      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);681682      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);683    });684685    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash686        && event.outcome().isFailedToTransactAsset);687688    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);689    expect(targetAccountBalance).to.be.equal(0n);690691    // But Karura still can send the correct amount692    const validTransferAmount = karuraBalance / 2n;693    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(694      targetAccount.addressRaw,695      {696        Concrete: {697          parents: 0,698          interior: 'Here',699        },700      },701      validTransferAmount,702    );703704    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {705      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);706    });707708    await helper.wait.newBlocks(maxWaitBlocks);709710    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);711    expect(targetAccountBalance).to.be.equal(validTransferAmount);712  });713714  itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {715    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);716    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);717718    const quartzMultilocation = {719      V2: {720        parents: 1,721        interior: {722          X1: {723            Parachain: QUARTZ_CHAIN,724          },725        },726      },727    };728729    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(730      targetAccount.addressRaw,731      {732        Concrete: {733          parents: 1,734          interior: {735            X1: {736              Parachain: QUARTZ_CHAIN,737            },738          },739        },740      },741      testAmount,742    );743744    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(745      targetAccount.addressRaw,746      {747        Concrete: {748          parents: 0,749          interior: 'Here',750        },751      },752      testAmount,753    );754755    let maliciousXcmProgramFullIdSent: any;756    let maliciousXcmProgramHereIdSent: any;757    const maxWaitBlocks = 3;758759    // Try to trick Quartz using full QTZ identification760    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {761      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);762763      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);764    });765766    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash767        && event.outcome().isUntrustedReserveLocation);768769    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);770    expect(accountBalance).to.be.equal(0n);771772    // Try to trick Quartz using shortened QTZ identification773    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {774      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);775776      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);777    });778779    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash780        && event.outcome().isUntrustedReserveLocation);781782    accountBalance = await helper.balance.getSubstrate(targetAccount.address);783    expect(accountBalance).to.be.equal(0n);784  });785});786787// These tests are relevant only when788// the the corresponding foreign assets are not registered789describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {790  let alice: IKeyringPair;791  let alith: IKeyringPair;792793  const testAmount = 100_000_000_000n;794  let quartzParachainJunction;795  let quartzAccountJunction;796797  let quartzParachainMultilocation: any;798  let quartzAccountMultilocation: any;799  let quartzCombinedMultilocation: any;800801  let messageSent: any;802803  const maxWaitBlocks = 3;804805  before(async () => {806    await usingPlaygrounds(async (helper, privateKey) => {807      alice = await privateKey('//Alice');808809      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};810      quartzAccountJunction = {811        AccountId32: {812          network: 'Any',813          id: alice.addressRaw,814        },815      };816817      quartzParachainMultilocation = {818        V2: {819          parents: 1,820          interior: {821            X1: quartzParachainJunction,822          },823        },824      };825826      quartzAccountMultilocation = {827        V2: {828          parents: 0,829          interior: {830            X1: quartzAccountJunction,831          },832        },833      };834835      quartzCombinedMultilocation = {836        V2: {837          parents: 1,838          interior: {839            X2: [quartzParachainJunction, quartzAccountJunction],840          },841        },842      };843844      // Set the default version to wrap the first message to other chains.845      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);846    });847848    // eslint-disable-next-line require-await849    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {850      alith = helper.account.alithAccount();851    });852  });853854  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {855    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash856        && event.outcome().isFailedToTransactAsset);857  };858859  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {860    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {861      const id = {862        Token: 'KAR',863      };864      const destination = quartzCombinedMultilocation;865      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');866867      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);868    });869870    await expectFailedToTransact(helper, messageSent);871  });872873  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {874    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {875      const id = 'SelfReserve';876      const destination = quartzCombinedMultilocation;877      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');878879      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);880    });881882    await expectFailedToTransact(helper, messageSent);883  });884885  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {886    await usingShidenPlaygrounds(shidenUrl, async (helper) => {887      const destinationParachain = quartzParachainMultilocation;888      const beneficiary = quartzAccountMultilocation;889      const assets = {890        V2: [{891          id: {892            Concrete: {893              parents: 0,894              interior: 'Here',895            },896          },897          fun: {898            Fungible: testAmount,899          },900        }],901      };902      const feeAssetItem = 0;903904      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [905        destinationParachain,906        beneficiary,907        assets,908        feeAssetItem,909      ]);910911      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);912    });913914    await expectFailedToTransact(helper, messageSent);915  });916});917918describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {919  // Quartz constants920  let alice: IKeyringPair;921  let quartzAssetLocation;922923  let randomAccountQuartz: IKeyringPair;924  let randomAccountMoonriver: IKeyringPair;925926  // Moonriver constants927  let assetId: string;928929  const quartzAssetMetadata = {930    name: 'xcQuartz',931    symbol: 'xcQTZ',932    decimals: 18,933    isFrozen: false,934    minimalBalance: 1n,935  };936937  let balanceQuartzTokenInit: bigint;938  let balanceQuartzTokenMiddle: bigint;939  let balanceQuartzTokenFinal: bigint;940  let balanceForeignQtzTokenInit: bigint;941  let balanceForeignQtzTokenMiddle: bigint;942  let balanceForeignQtzTokenFinal: bigint;943  let balanceMovrTokenInit: bigint;944  let balanceMovrTokenMiddle: bigint;945  let balanceMovrTokenFinal: bigint;946947  before(async () => {948    await usingPlaygrounds(async (helper, privateKey) => {949      alice = await privateKey('//Alice');950      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);951952      balanceForeignQtzTokenInit = 0n;953954      // Set the default version to wrap the first message to other chains.955      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);956    });957958    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {959      const alithAccount = helper.account.alithAccount();960      const baltatharAccount = helper.account.baltatharAccount();961      const dorothyAccount = helper.account.dorothyAccount();962963      randomAccountMoonriver = helper.account.create();964965      // >>> Sponsoring Dorothy >>>966      console.log('Sponsoring Dorothy.......');967      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);968      console.log('Sponsoring Dorothy.......DONE');969      // <<< Sponsoring Dorothy <<<970971      quartzAssetLocation = {972        XCM: {973          parents: 1,974          interior: {X1: {Parachain: QUARTZ_CHAIN}},975        },976      };977      const existentialDeposit = 1n;978      const isSufficient = true;979      const unitsPerSecond = 1n;980      const numAssetsWeightHint = 0;981982      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({983        location: quartzAssetLocation,984        metadata: quartzAssetMetadata,985        existentialDeposit,986        isSufficient,987        unitsPerSecond,988        numAssetsWeightHint,989      });990991      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);992993      await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);994995      // >>> Acquire Quartz AssetId Info on Moonriver >>>996      console.log('Acquire Quartz AssetId Info on Moonriver.......');997998      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();9991000      console.log('QTZ asset ID is %s', assetId);1001      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1002      // >>> Acquire Quartz AssetId Info on Moonriver >>>10031004      // >>> Sponsoring random Account >>>1005      console.log('Sponsoring random Account.......');1006      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1007      console.log('Sponsoring random Account.......DONE');1008      // <<< Sponsoring random Account <<<10091010      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1011    });10121013    await usingPlaygrounds(async (helper) => {1014      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1015      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1016    });1017  });10181019  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1020    const currencyId = {1021      NativeAssetId: 'Here',1022    };1023    const dest = {1024      V2: {1025        parents: 1,1026        interior: {1027          X2: [1028            {Parachain: MOONRIVER_CHAIN},1029            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1030          ],1031        },1032      },1033    };1034    const amount = TRANSFER_AMOUNT;10351036    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10371038    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1039    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10401041    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1042    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1043    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10441045    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1046      await helper.wait.newBlocks(3);10471048      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10491050      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1051      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1052      expect(movrFees == 0n).to.be.true;10531054      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1055      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1056      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1057      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1058    });1059  });10601061  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1062    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1063      const asset = {1064        V2: {1065          id: {1066            Concrete: {1067              parents: 1,1068              interior: {1069                X1: {Parachain: QUARTZ_CHAIN},1070              },1071            },1072          },1073          fun: {1074            Fungible: TRANSFER_AMOUNT,1075          },1076        },1077      };1078      const destination = {1079        V2: {1080          parents: 1,1081          interior: {1082            X2: [1083              {Parachain: QUARTZ_CHAIN},1084              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1085            ],1086          },1087        },1088      };10891090      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10911092      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10931094      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1095      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1096      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10971098      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);10991100      expect(qtzRandomAccountAsset).to.be.null;11011102      balanceForeignQtzTokenFinal = 0n;11031104      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1105      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1106      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1107    });11081109    await helper.wait.newBlocks(3);11101111    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1112    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1113    expect(actuallyDelivered > 0).to.be.true;11141115    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11161117    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1118    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1119    expect(qtzFees == 0n).to.be.true;1120  });11211122  itSub('Moonriver can send only up to its balance', async ({helper}) => {1123    // set Moonriver's sovereign account's balance1124    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1125    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1126    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11271128    const moreThanMoonriverHas = moonriverBalance * 2n;11291130    let targetAccountBalance = 0n;1131    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11321133    const quartzMultilocation = {1134      V2: {1135        parents: 1,1136        interior: {1137          X1: {Parachain: QUARTZ_CHAIN},1138        },1139      },1140    };11411142    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1143      targetAccount.addressRaw,1144      {1145        Concrete: {1146          parents: 0,1147          interior: 'Here',1148        },1149      },1150      moreThanMoonriverHas,1151    );11521153    let maliciousXcmProgramSent: any;1154    const maxWaitBlocks = 3;11551156    // Try to trick Quartz1157    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1158      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11591160      // Needed to bypass the call filter.1161      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1162      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11631164      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1165    });11661167    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1168        && event.outcome().isFailedToTransactAsset);11691170    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1171    expect(targetAccountBalance).to.be.equal(0n);11721173    // But Moonriver still can send the correct amount1174    const validTransferAmount = moonriverBalance / 2n;1175    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1176      targetAccount.addressRaw,1177      {1178        Concrete: {1179          parents: 0,1180          interior: 'Here',1181        },1182      },1183      validTransferAmount,1184    );11851186    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1187      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11881189      // Needed to bypass the call filter.1190      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1191      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1192    });11931194    await helper.wait.newBlocks(maxWaitBlocks);11951196    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1197    expect(targetAccountBalance).to.be.equal(validTransferAmount);1198  });11991200  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1201    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1202    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12031204    const quartzMultilocation = {1205      V2: {1206        parents: 1,1207        interior: {1208          X1: {1209            Parachain: QUARTZ_CHAIN,1210          },1211        },1212      },1213    };12141215    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1216      targetAccount.addressRaw,1217      {1218        Concrete: {1219          parents: 0,1220          interior: {1221            X1: {1222              Parachain: QUARTZ_CHAIN,1223            },1224          },1225        },1226      },1227      testAmount,1228    );12291230    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1231      targetAccount.addressRaw,1232      {1233        Concrete: {1234          parents: 0,1235          interior: 'Here',1236        },1237      },1238      testAmount,1239    );12401241    let maliciousXcmProgramFullIdSent: any;1242    let maliciousXcmProgramHereIdSent: any;1243    const maxWaitBlocks = 3;12441245    // Try to trick Quartz using full QTZ identification1246    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1247      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12481249      // Needed to bypass the call filter.1250      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1251      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12521253      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1254    });12551256    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1257        && event.outcome().isUntrustedReserveLocation);12581259    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1260    expect(accountBalance).to.be.equal(0n);12611262    // Try to trick Quartz using shortened QTZ identification1263    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1264      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12651266      // Needed to bypass the call filter.1267      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1268      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12691270      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1271    });12721273    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1274        && event.outcome().isUntrustedReserveLocation);12751276    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1277    expect(accountBalance).to.be.equal(0n);1278  });1279});12801281describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1282  let alice: IKeyringPair;1283  let sender: IKeyringPair;12841285  const QTZ_ASSET_ID_ON_SHIDEN = 1;1286  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12871288  // Quartz -> Shiden1289  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1290  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1291  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1292  const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens12931294  // Shiden -> Quartz1295  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1296  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ12971298  let balanceAfterQuartzToShidenXCM: bigint;12991300  before(async () => {1301    await usingPlaygrounds(async (helper, privateKey) => {1302      alice = await privateKey('//Alice');1303      [sender] = await helper.arrange.createAccounts([100n], alice);1304      console.log('sender', sender.address);13051306      // Set the default version to wrap the first message to other chains.1307      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1308    });13091310    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1311      console.log('1. Create foreign asset and metadata');1312      // TODO update metadata with values from production1313      await helper.assets.create(1314        alice,1315        QTZ_ASSET_ID_ON_SHIDEN,1316        alice.address,1317        QTZ_MINIMAL_BALANCE_ON_SHIDEN,1318      );13191320      await helper.assets.setMetadata(1321        alice,1322        QTZ_ASSET_ID_ON_SHIDEN,1323        'Cross chain QTZ',1324        'xcQTZ',1325        Number(QTZ_DECIMALS),1326      );13271328      console.log('2. Register asset location on Shiden');1329      const assetLocation = {1330        V2: {1331          parents: 1,1332          interior: {1333            X1: {1334              Parachain: QUARTZ_CHAIN,1335            },1336          },1337        },1338      };13391340      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13411342      console.log('3. Set QTZ payment for XCM execution on Shiden');1343      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13441345      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1346      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1347    });1348  });13491350  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1351    const destination = {1352      V2: {1353        parents: 1,1354        interior: {1355          X1: {1356            Parachain: SHIDEN_CHAIN,1357          },1358        },1359      },1360    };13611362    const beneficiary = {1363      V2: {1364        parents: 0,1365        interior: {1366          X1: {1367            AccountId32: {1368              network: 'Any',1369              id: sender.addressRaw,1370            },1371          },1372        },1373      },1374    };13751376    const assets = {1377      V2: [1378        {1379          id: {1380            Concrete: {1381              parents: 0,1382              interior: 'Here',1383            },1384          },1385          fun: {1386            Fungible: qtzToShidenTransferred,1387          },1388        },1389      ],1390    };13911392    // Initial balance is 100 QTZ1393    const balanceBefore = await helper.balance.getSubstrate(sender.address);1394    console.log(`Initial balance is: ${balanceBefore}`);13951396    const feeAssetItem = 0;1397    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');13981399    // Balance after reserve transfer is less than 901400    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1401    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1402    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1403    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14041405    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1406      await helper.wait.newBlocks(3);1407      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1408      const shidenBalance = await helper.balance.getSubstrate(sender.address);14091410      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1411      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14121413      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1414      // SHD balance does not changed:1415      expect(shidenBalance).to.eq(shidenInitialBalance);1416    });1417  });14181419  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1420    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1421      const destination = {1422        V2: {1423          parents: 1,1424          interior: {1425            X1: {1426              Parachain: QUARTZ_CHAIN,1427            },1428          },1429        },1430      };14311432      const beneficiary = {1433        V2: {1434          parents: 0,1435          interior: {1436            X1: {1437              AccountId32: {1438                network: 'Any',1439                id: sender.addressRaw,1440              },1441            },1442          },1443        },1444      };14451446      const assets = {1447        V2: [1448          {1449            id: {1450              Concrete: {1451                parents: 1,1452                interior: {1453                  X1: {1454                    Parachain: QUARTZ_CHAIN,1455                  },1456                },1457              },1458            },1459            fun: {1460              Fungible: qtzFromShidenTransfered,1461            },1462          },1463        ],1464      };14651466      // Initial balance is 1 SDN1467      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1468      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1469      expect(balanceSDNbefore).to.eq(shidenInitialBalance);14701471      const feeAssetItem = 0;1472      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1473      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14741475      // Balance after reserve transfer is less than 1 SDN1476      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1477      const balanceSDN = await helper.balance.getSubstrate(sender.address);1478      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14791480      // Assert: xcQTZ balance correctly decreased1481      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1482      // Assert: SDN balance is 0.996...1483      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1484    });14851486    await helper.wait.newBlocks(3);1487    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1488    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1489    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1490  });14911492  itSub('Shiden can send only up to its balance', async ({helper}) => {1493    // set Shiden's sovereign account's balance1494    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1495    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1496    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);14971498    const moreThanShidenHas = shidenBalance * 2n;14991500    let targetAccountBalance = 0n;1501    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15021503    const quartzMultilocation = {1504      V2: {1505        parents: 1,1506        interior: {1507          X1: {Parachain: QUARTZ_CHAIN},1508        },1509      },1510    };15111512    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1513      targetAccount.addressRaw,1514      {1515        Concrete: {1516          parents: 0,1517          interior: 'Here',1518        },1519      },1520      moreThanShidenHas,1521    );15221523    let maliciousXcmProgramSent: any;1524    const maxWaitBlocks = 3;15251526    // Try to trick Quartz1527    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1528      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15291530      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1531    });15321533    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1534        && event.outcome().isFailedToTransactAsset);15351536    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1537    expect(targetAccountBalance).to.be.equal(0n);15381539    // But Shiden still can send the correct amount1540    const validTransferAmount = shidenBalance / 2n;1541    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1542      targetAccount.addressRaw,1543      {1544        Concrete: {1545          parents: 0,1546          interior: 'Here',1547        },1548      },1549      validTransferAmount,1550    );15511552    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1553      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1554    });15551556    await helper.wait.newBlocks(maxWaitBlocks);15571558    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1559    expect(targetAccountBalance).to.be.equal(validTransferAmount);1560  });15611562  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1563    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1564    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15651566    const quartzMultilocation = {1567      V2: {1568        parents: 1,1569        interior: {1570          X1: {1571            Parachain: QUARTZ_CHAIN,1572          },1573        },1574      },1575    };15761577    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1578      targetAccount.addressRaw,1579      {1580        Concrete: {1581          parents: 1,1582          interior: {1583            X1: {1584              Parachain: QUARTZ_CHAIN,1585            },1586          },1587        },1588      },1589      testAmount,1590    );15911592    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1593      targetAccount.addressRaw,1594      {1595        Concrete: {1596          parents: 0,1597          interior: 'Here',1598        },1599      },1600      testAmount,1601    );16021603    let maliciousXcmProgramFullIdSent: any;1604    let maliciousXcmProgramHereIdSent: any;1605    const maxWaitBlocks = 3;16061607    // Try to trick Quartz using full QTZ identification1608    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1609      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16101611      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1612    });16131614    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1615        && event.outcome().isUntrustedReserveLocation);16161617    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1618    expect(accountBalance).to.be.equal(0n);16191620    // Try to trick Quartz using shortened QTZ identification1621    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1622      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16231624      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1625    });16261627    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1628        && event.outcome().isUntrustedReserveLocation);16291630    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1631    expect(accountBalance).to.be.equal(0n);1632  });1633});