git.delta.rocks / unique-network / refs/commits / 1a650ecf2bf7

difftreelog

ci move nodeonly to baedeker

Yaroslav Bolyukin2023-09-03parent: #56c4087.patch.diff
in: master

10 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]
+	},
+}
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/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
@@ -312,7 +312,7 @@
           jpath: |
             .baedeker/vendor
           tla-str: |
-            relay_spec=rococo
+            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 }}'}})
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
before · tests/src/xcm/xcmQuartz.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';2122const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);23const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);24const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);25const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);26const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;34const shidenUrl = config.shidenUrl;3536const RELAY_DECIMALS = 12;37const STATEMINE_DECIMALS = 12;38const KARURA_DECIMALS = 12;39const SHIDEN_DECIMALS = 18n;40const QTZ_DECIMALS = 18n;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4546const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4748const USDT_ASSET_ID = 100;49const USDT_ASSET_METADATA_DECIMALS = 18;50const USDT_ASSET_METADATA_NAME = 'USDT';51const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';52const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;53const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5455const SAFE_XCM_VERSION = 2;5657describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {58  let alice: IKeyringPair;59  let bob: IKeyringPair;6061  let balanceStmnBefore: bigint;62  let balanceStmnAfter: bigint;6364  let balanceQuartzBefore: bigint;65  let balanceQuartzAfter: bigint;66  let balanceQuartzFinal: bigint;6768  let balanceBobBefore: bigint;69  let balanceBobAfter: bigint;70  let balanceBobFinal: bigint;7172  let balanceBobRelayTokenBefore: bigint;73  let balanceBobRelayTokenAfter: bigint;747576  before(async () => {77    await usingPlaygrounds(async (helper, privateKey) => {78      alice = await privateKey('//Alice');79      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor8081      // Set the default version to wrap the first message to other chains.82      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);83    });8485    await usingRelayPlaygrounds(relayUrl, async (helper) => {86      // Fund accounts on Statemine(t)87      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);88      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);89    });9091    await usingStateminePlaygrounds(statemineUrl, async (helper) => {92      const sovereignFundingAmount = 3_500_000_000n;9394      await helper.assets.create(95        alice,96        USDT_ASSET_ID,97        alice.address,98        USDT_ASSET_METADATA_MINIMAL_BALANCE,99      );100      await helper.assets.setMetadata(101        alice,102        USDT_ASSET_ID,103        USDT_ASSET_METADATA_NAME,104        USDT_ASSET_METADATA_DESCRIPTION,105        USDT_ASSET_METADATA_DECIMALS,106      );107      await helper.assets.mint(108        alice,109        USDT_ASSET_ID,110        alice.address,111        USDT_ASSET_AMOUNT,112      );113114      // funding parachain sovereing account on Statemine(t).115      // The sovereign account should be created before any action116      // (the assets pallet on Statemine(t) check if the sovereign account exists)117      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);118      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);119    });120121122    await usingPlaygrounds(async (helper) => {123      const location = {124        V2: {125          parents: 1,126          interior: {X3: [127            {128              Parachain: STATEMINE_CHAIN,129            },130            {131              PalletInstance: STATEMINE_PALLET_INSTANCE,132            },133            {134              GeneralIndex: USDT_ASSET_ID,135            },136          ]},137        },138      };139140      const metadata =141      {142        name: USDT_ASSET_ID,143        symbol: USDT_ASSET_METADATA_NAME,144        decimals: USDT_ASSET_METADATA_DECIMALS,145        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,146      };147      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);148      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);149    });150151152    // Providing the relay currency to the quartz sender account153    // (fee for USDT XCM are paid in relay tokens)154    await usingRelayPlaygrounds(relayUrl, async (helper) => {155      const destination = {156        V2: {157          parents: 0,158          interior: {X1: {159            Parachain: QUARTZ_CHAIN,160          },161          },162        }};163164      const beneficiary = {165        V2: {166          parents: 0,167          interior: {X1: {168            AccountId32: {169              network: 'Any',170              id: alice.addressRaw,171            },172          }},173        },174      };175176      const assets = {177        V2: [178          {179            id: {180              Concrete: {181                parents: 0,182                interior: 'Here',183              },184            },185            fun: {186              Fungible: TRANSFER_AMOUNT_RELAY,187            },188          },189        ],190      };191192      const feeAssetItem = 0;193194      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');195    });196197  });198199  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {200    await usingStateminePlaygrounds(statemineUrl, async (helper) => {201      const dest = {202        V2: {203          parents: 1,204          interior: {X1: {205            Parachain: QUARTZ_CHAIN,206          },207          },208        }};209210      const beneficiary = {211        V2: {212          parents: 0,213          interior: {X1: {214            AccountId32: {215              network: 'Any',216              id: alice.addressRaw,217            },218          }},219        },220      };221222      const assets = {223        V2: [224          {225            id: {226              Concrete: {227                parents: 0,228                interior: {229                  X2: [230                    {231                      PalletInstance: STATEMINE_PALLET_INSTANCE,232                    },233                    {234                      GeneralIndex: USDT_ASSET_ID,235                    },236                  ]},237              },238            },239            fun: {240              Fungible: TRANSFER_AMOUNT,241            },242          },243        ],244      };245246      const feeAssetItem = 0;247248      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);249      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');250251      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);252253      // common good parachain take commission in it native token254      console.log(255        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',256        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),257      );258      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;259260    });261262263    // ensure that asset has been delivered264    await helper.wait.newBlocks(3);265266    // expext collection id will be with id 1267    const free = await helper.ft.getBalance(1, {Substrate: alice.address});268269    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);270271    console.log(272      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',273      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),274    );275    console.log(276      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',277      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),278    );279    // commission has not paid in USDT token280    expect(free).to.be.equal(TRANSFER_AMOUNT);281    // ... and parachain native token282    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;283  });284285  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {286    const destination = {287      V2: {288        parents: 1,289        interior: {X2: [290          {291            Parachain: STATEMINE_CHAIN,292          },293          {294            AccountId32: {295              network: 'Any',296              id: alice.addressRaw,297            },298          },299        ]},300      },301    };302303    const relayFee = 400_000_000_000_000n;304    const currencies: [any, bigint][] = [305      [306        {307          ForeignAssetId: 0,308        },309        TRANSFER_AMOUNT,310      ],311      [312        {313          NativeAssetId: 'Parent',314        },315        relayFee,316      ],317    ];318319    const feeItem = 1;320321    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');322323    // the commission has been paid in parachain native token324    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);325    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));326    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;327328    await usingStateminePlaygrounds(statemineUrl, async (helper) => {329      await helper.wait.newBlocks(3);330331      // The USDT token never paid fees. Its amount not changed from begin value.332      // Also check that xcm transfer has been succeeded333      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;334    });335  });336337  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {338    balanceBobBefore = await helper.balance.getSubstrate(bob.address);339    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});340341    await usingRelayPlaygrounds(relayUrl, async (helper) => {342      const destination = {343        V2: {344          parents: 0,345          interior: {X1: {346            Parachain: QUARTZ_CHAIN,347          },348          },349        }};350351      const beneficiary = {352        V2: {353          parents: 0,354          interior: {X1: {355            AccountId32: {356              network: 'Any',357              id: bob.addressRaw,358            },359          }},360        },361      };362363      const assets = {364        V2: [365          {366            id: {367              Concrete: {368                parents: 0,369                interior: 'Here',370              },371            },372            fun: {373              Fungible: TRANSFER_AMOUNT_RELAY,374            },375          },376        ],377      };378379      const feeAssetItem = 0;380381      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');382    });383384    await helper.wait.newBlocks(3);385386    balanceBobAfter = await helper.balance.getSubstrate(bob.address);387    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});388389    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;390    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;391    console.log(392      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',393      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),394    );395    console.log(396      '[Relay (Westend) -> Quartz] transaction fees: %s WND',397      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),398    );399    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);400    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;401    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;402  });403404  itSub('Should connect and send Relay token back', async ({helper}) => {405    let relayTokenBalanceBefore: bigint;406    let relayTokenBalanceAfter: bigint;407    await usingRelayPlaygrounds(relayUrl, async (helper) => {408      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);409    });410411    const destination = {412      V2: {413        parents: 1,414        interior: {415          X1:{416            AccountId32: {417              network: 'Any',418              id: bob.addressRaw,419            },420          },421        },422      },423    };424425    const currencies: any = [426      [427        {428          NativeAssetId: 'Parent',429        },430        TRANSFER_AMOUNT_RELAY,431      ],432    ];433434    const feeItem = 0;435436    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438    balanceBobFinal = await helper.balance.getSubstrate(bob.address);439    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441    await usingRelayPlaygrounds(relayUrl, async (helper) => {442      await helper.wait.newBlocks(10);443      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448    });449  });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {453  let alice: IKeyringPair;454  let randomAccount: IKeyringPair;455456  let balanceQuartzTokenInit: bigint;457  let balanceQuartzTokenMiddle: bigint;458  let balanceQuartzTokenFinal: bigint;459  let balanceKaruraTokenInit: bigint;460  let balanceKaruraTokenMiddle: bigint;461  let balanceKaruraTokenFinal: bigint;462  let balanceQuartzForeignTokenInit: bigint;463  let balanceQuartzForeignTokenMiddle: bigint;464  let balanceQuartzForeignTokenFinal: bigint;465466  // computed by a test transfer from prod Quartz to prod Karura.467  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9468  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)469  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;470471  const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;472473  before(async () => {474    await usingPlaygrounds(async (helper, privateKey) => {475      alice = await privateKey('//Alice');476      [randomAccount] = await helper.arrange.createAccounts([0n], alice);477478      // Set the default version to wrap the first message to other chains.479      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);480    });481482    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {483      const destination = {484        V1: {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        V1: {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      V1: {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      V1: {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;800  let quartzCombinedMultilocationKarura: any; // TODO remove it when Karura goes V2801802  let messageSent: any;803804  const maxWaitBlocks = 3;805806  before(async () => {807    await usingPlaygrounds(async (helper, privateKey) => {808      alice = await privateKey('//Alice');809810      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};811      quartzAccountJunction = {812        AccountId32: {813          network: 'Any',814          id: alice.addressRaw,815        },816      };817818      quartzParachainMultilocation = {819        V2: {820          parents: 1,821          interior: {822            X1: quartzParachainJunction,823          },824        },825      };826827      quartzAccountMultilocation = {828        V2: {829          parents: 0,830          interior: {831            X1: quartzAccountJunction,832          },833        },834      };835836      quartzCombinedMultilocation = {837        V2: {838          parents: 1,839          interior: {840            X2: [quartzParachainJunction, quartzAccountJunction],841          },842        },843      };844845      quartzCombinedMultilocationKarura = {846        V1: {847          parents: 1,848          interior: {849            X2: [quartzParachainJunction, quartzAccountJunction],850          },851        },852      };853854      // Set the default version to wrap the first message to other chains.855      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);856    });857858    // eslint-disable-next-line require-await859    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {860      alith = helper.account.alithAccount();861    });862  });863864  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {865    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash866        && event.outcome().isFailedToTransactAsset);867  };868869  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {870    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {871      const id = {872        Token: 'KAR',873      };874      const destination = quartzCombinedMultilocationKarura;875      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');876877      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);878    });879880    await expectFailedToTransact(helper, messageSent);881  });882883  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {884    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {885      const id = 'SelfReserve';886      const destination = quartzCombinedMultilocation;887      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');888889      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);890    });891892    await expectFailedToTransact(helper, messageSent);893  });894895  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {896    await usingShidenPlaygrounds(shidenUrl, async (helper) => {897      const destinationParachain = quartzParachainMultilocation;898      const beneficiary = quartzAccountMultilocation;899      const assets = {900        V2: [{901          id: {902            Concrete: {903              parents: 0,904              interior: 'Here',905            },906          },907          fun: {908            Fungible: testAmount,909          },910        }],911      };912      const feeAssetItem = 0;913914      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [915        destinationParachain,916        beneficiary,917        assets,918        feeAssetItem,919      ]);920921      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);922    });923924    await expectFailedToTransact(helper, messageSent);925  });926});927928describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {929  // Quartz constants930  let alice: IKeyringPair;931  let quartzAssetLocation;932933  let randomAccountQuartz: IKeyringPair;934  let randomAccountMoonriver: IKeyringPair;935936  // Moonriver constants937  let assetId: string;938939  const quartzAssetMetadata = {940    name: 'xcQuartz',941    symbol: 'xcQTZ',942    decimals: 18,943    isFrozen: false,944    minimalBalance: 1n,945  };946947  let balanceQuartzTokenInit: bigint;948  let balanceQuartzTokenMiddle: bigint;949  let balanceQuartzTokenFinal: bigint;950  let balanceForeignQtzTokenInit: bigint;951  let balanceForeignQtzTokenMiddle: bigint;952  let balanceForeignQtzTokenFinal: bigint;953  let balanceMovrTokenInit: bigint;954  let balanceMovrTokenMiddle: bigint;955  let balanceMovrTokenFinal: bigint;956957  before(async () => {958    await usingPlaygrounds(async (helper, privateKey) => {959      alice = await privateKey('//Alice');960      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);961962      balanceForeignQtzTokenInit = 0n;963964      // Set the default version to wrap the first message to other chains.965      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);966    });967968    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {969      const alithAccount = helper.account.alithAccount();970      const baltatharAccount = helper.account.baltatharAccount();971      const dorothyAccount = helper.account.dorothyAccount();972973      randomAccountMoonriver = helper.account.create();974975      // >>> Sponsoring Dorothy >>>976      console.log('Sponsoring Dorothy.......');977      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);978      console.log('Sponsoring Dorothy.......DONE');979      // <<< Sponsoring Dorothy <<<980981      quartzAssetLocation = {982        XCM: {983          parents: 1,984          interior: {X1: {Parachain: QUARTZ_CHAIN}},985        },986      };987      const existentialDeposit = 1n;988      const isSufficient = true;989      const unitsPerSecond = 1n;990      const numAssetsWeightHint = 0;991992      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({993        location: quartzAssetLocation,994        metadata: quartzAssetMetadata,995        existentialDeposit,996        isSufficient,997        unitsPerSecond,998        numAssetsWeightHint,999      });10001001      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);10021003      await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);10041005      // >>> Acquire Quartz AssetId Info on Moonriver >>>1006      console.log('Acquire Quartz AssetId Info on Moonriver.......');10071008      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10091010      console.log('QTZ asset ID is %s', assetId);1011      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1012      // >>> Acquire Quartz AssetId Info on Moonriver >>>10131014      // >>> Sponsoring random Account >>>1015      console.log('Sponsoring random Account.......');1016      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1017      console.log('Sponsoring random Account.......DONE');1018      // <<< Sponsoring random Account <<<10191020      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1021    });10221023    await usingPlaygrounds(async (helper) => {1024      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1025      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1026    });1027  });10281029  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1030    const currencyId = {1031      NativeAssetId: 'Here',1032    };1033    const dest = {1034      V2: {1035        parents: 1,1036        interior: {1037          X2: [1038            {Parachain: MOONRIVER_CHAIN},1039            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1040          ],1041        },1042      },1043    };1044    const amount = TRANSFER_AMOUNT;10451046    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10471048    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1049    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10501051    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1052    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1053    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10541055    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1056      await helper.wait.newBlocks(3);10571058      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10591060      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1061      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1062      expect(movrFees == 0n).to.be.true;10631064      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1065      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1066      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1067      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1068    });1069  });10701071  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1072    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1073      const asset = {1074        V2: {1075          id: {1076            Concrete: {1077              parents: 1,1078              interior: {1079                X1: {Parachain: QUARTZ_CHAIN},1080              },1081            },1082          },1083          fun: {1084            Fungible: TRANSFER_AMOUNT,1085          },1086        },1087      };1088      const destination = {1089        V2: {1090          parents: 1,1091          interior: {1092            X2: [1093              {Parachain: QUARTZ_CHAIN},1094              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1095            ],1096          },1097        },1098      };10991100      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');11011102      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);11031104      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1105      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1106      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;11071108      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11091110      expect(qtzRandomAccountAsset).to.be.null;11111112      balanceForeignQtzTokenFinal = 0n;11131114      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1115      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1116      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1117    });11181119    await helper.wait.newBlocks(3);11201121    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1122    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1123    expect(actuallyDelivered > 0).to.be.true;11241125    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11261127    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1128    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1129    expect(qtzFees == 0n).to.be.true;1130  });11311132  itSub('Moonriver can send only up to its balance', async ({helper}) => {1133    // set Moonriver's sovereign account's balance1134    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1135    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1136    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11371138    const moreThanMoonriverHas = moonriverBalance * 2n;11391140    let targetAccountBalance = 0n;1141    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11421143    const quartzMultilocation = {1144      V2: {1145        parents: 1,1146        interior: {1147          X1: {Parachain: QUARTZ_CHAIN},1148        },1149      },1150    };11511152    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1153      targetAccount.addressRaw,1154      {1155        Concrete: {1156          parents: 0,1157          interior: 'Here',1158        },1159      },1160      moreThanMoonriverHas,1161    );11621163    let maliciousXcmProgramSent: any;1164    const maxWaitBlocks = 3;11651166    // Try to trick Quartz1167    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1168      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11691170      // Needed to bypass the call filter.1171      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1172      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11731174      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1175    });11761177    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1178        && event.outcome().isFailedToTransactAsset);11791180    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1181    expect(targetAccountBalance).to.be.equal(0n);11821183    // But Moonriver still can send the correct amount1184    const validTransferAmount = moonriverBalance / 2n;1185    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1186      targetAccount.addressRaw,1187      {1188        Concrete: {1189          parents: 0,1190          interior: 'Here',1191        },1192      },1193      validTransferAmount,1194    );11951196    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1197      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11981199      // Needed to bypass the call filter.1200      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1201      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1202    });12031204    await helper.wait.newBlocks(maxWaitBlocks);12051206    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1207    expect(targetAccountBalance).to.be.equal(validTransferAmount);1208  });12091210  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1211    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1212    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12131214    const quartzMultilocation = {1215      V2: {1216        parents: 1,1217        interior: {1218          X1: {1219            Parachain: QUARTZ_CHAIN,1220          },1221        },1222      },1223    };12241225    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1226      targetAccount.addressRaw,1227      {1228        Concrete: {1229          parents: 0,1230          interior: {1231            X1: {1232              Parachain: QUARTZ_CHAIN,1233            },1234          },1235        },1236      },1237      testAmount,1238    );12391240    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1241      targetAccount.addressRaw,1242      {1243        Concrete: {1244          parents: 0,1245          interior: 'Here',1246        },1247      },1248      testAmount,1249    );12501251    let maliciousXcmProgramFullIdSent: any;1252    let maliciousXcmProgramHereIdSent: any;1253    const maxWaitBlocks = 3;12541255    // Try to trick Quartz using full QTZ identification1256    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1257      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12581259      // Needed to bypass the call filter.1260      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1261      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12621263      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1264    });12651266    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1267        && event.outcome().isUntrustedReserveLocation);12681269    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1270    expect(accountBalance).to.be.equal(0n);12711272    // Try to trick Quartz using shortened QTZ identification1273    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1274      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12751276      // Needed to bypass the call filter.1277      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1278      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12791280      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1281    });12821283    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1284        && event.outcome().isUntrustedReserveLocation);12851286    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1287    expect(accountBalance).to.be.equal(0n);1288  });1289});12901291describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1292  let alice: IKeyringPair;1293  let sender: IKeyringPair;12941295  const QTZ_ASSET_ID_ON_SHIDEN = 1;1296  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12971298  // Quartz -> Shiden1299  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1300  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1301  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1302  const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens13031304  // Shiden -> Quartz1305  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1306  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ13071308  let balanceAfterQuartzToShidenXCM: bigint;13091310  before(async () => {1311    await usingPlaygrounds(async (helper, privateKey) => {1312      alice = await privateKey('//Alice');1313      [sender] = await helper.arrange.createAccounts([100n], alice);1314      console.log('sender', sender.address);13151316      // Set the default version to wrap the first message to other chains.1317      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1318    });13191320    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1321      console.log('1. Create foreign asset and metadata');1322      // TODO update metadata with values from production1323      await helper.assets.create(1324        alice,1325        QTZ_ASSET_ID_ON_SHIDEN,1326        alice.address,1327        QTZ_MINIMAL_BALANCE_ON_SHIDEN,1328      );13291330      await helper.assets.setMetadata(1331        alice,1332        QTZ_ASSET_ID_ON_SHIDEN,1333        'Cross chain QTZ',1334        'xcQTZ',1335        Number(QTZ_DECIMALS),1336      );13371338      console.log('2. Register asset location on Shiden');1339      const assetLocation = {1340        V2: {1341          parents: 1,1342          interior: {1343            X1: {1344              Parachain: QUARTZ_CHAIN,1345            },1346          },1347        },1348      };13491350      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13511352      console.log('3. Set QTZ payment for XCM execution on Shiden');1353      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13541355      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1356      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1357    });1358  });13591360  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1361    const destination = {1362      V2: {1363        parents: 1,1364        interior: {1365          X1: {1366            Parachain: SHIDEN_CHAIN,1367          },1368        },1369      },1370    };13711372    const beneficiary = {1373      V2: {1374        parents: 0,1375        interior: {1376          X1: {1377            AccountId32: {1378              network: 'Any',1379              id: sender.addressRaw,1380            },1381          },1382        },1383      },1384    };13851386    const assets = {1387      V2: [1388        {1389          id: {1390            Concrete: {1391              parents: 0,1392              interior: 'Here',1393            },1394          },1395          fun: {1396            Fungible: qtzToShidenTransferred,1397          },1398        },1399      ],1400    };14011402    // Initial balance is 100 QTZ1403    const balanceBefore = await helper.balance.getSubstrate(sender.address);1404    console.log(`Initial balance is: ${balanceBefore}`);14051406    const feeAssetItem = 0;1407    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14081409    // Balance after reserve transfer is less than 901410    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1411    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1412    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1413    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14141415    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1416      await helper.wait.newBlocks(3);1417      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1418      const shidenBalance = await helper.balance.getSubstrate(sender.address);14191420      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1421      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14221423      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1424      // SHD balance does not changed:1425      expect(shidenBalance).to.eq(shidenInitialBalance);1426    });1427  });14281429  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1430    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1431      const destination = {1432        V2: {1433          parents: 1,1434          interior: {1435            X1: {1436              Parachain: QUARTZ_CHAIN,1437            },1438          },1439        },1440      };14411442      const beneficiary = {1443        V2: {1444          parents: 0,1445          interior: {1446            X1: {1447              AccountId32: {1448                network: 'Any',1449                id: sender.addressRaw,1450              },1451            },1452          },1453        },1454      };14551456      const assets = {1457        V2: [1458          {1459            id: {1460              Concrete: {1461                parents: 1,1462                interior: {1463                  X1: {1464                    Parachain: QUARTZ_CHAIN,1465                  },1466                },1467              },1468            },1469            fun: {1470              Fungible: qtzFromShidenTransfered,1471            },1472          },1473        ],1474      };14751476      // Initial balance is 1 SDN1477      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1478      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1479      expect(balanceSDNbefore).to.eq(shidenInitialBalance);14801481      const feeAssetItem = 0;1482      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1483      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14841485      // Balance after reserve transfer is less than 1 SDN1486      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1487      const balanceSDN = await helper.balance.getSubstrate(sender.address);1488      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14891490      // Assert: xcQTZ balance correctly decreased1491      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1492      // Assert: SDN balance is 0.996...1493      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1494    });14951496    await helper.wait.newBlocks(3);1497    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1498    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1499    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1500  });15011502  itSub('Shiden can send only up to its balance', async ({helper}) => {1503    // set Shiden's sovereign account's balance1504    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1505    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1506    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15071508    const moreThanShidenHas = shidenBalance * 2n;15091510    let targetAccountBalance = 0n;1511    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15121513    const quartzMultilocation = {1514      V2: {1515        parents: 1,1516        interior: {1517          X1: {Parachain: QUARTZ_CHAIN},1518        },1519      },1520    };15211522    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1523      targetAccount.addressRaw,1524      {1525        Concrete: {1526          parents: 0,1527          interior: 'Here',1528        },1529      },1530      moreThanShidenHas,1531    );15321533    let maliciousXcmProgramSent: any;1534    const maxWaitBlocks = 3;15351536    // Try to trick Quartz1537    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1538      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15391540      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1541    });15421543    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1544        && event.outcome().isFailedToTransactAsset);15451546    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1547    expect(targetAccountBalance).to.be.equal(0n);15481549    // But Shiden still can send the correct amount1550    const validTransferAmount = shidenBalance / 2n;1551    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1552      targetAccount.addressRaw,1553      {1554        Concrete: {1555          parents: 0,1556          interior: 'Here',1557        },1558      },1559      validTransferAmount,1560    );15611562    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1563      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1564    });15651566    await helper.wait.newBlocks(maxWaitBlocks);15671568    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1569    expect(targetAccountBalance).to.be.equal(validTransferAmount);1570  });15711572  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1573    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1574    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15751576    const quartzMultilocation = {1577      V2: {1578        parents: 1,1579        interior: {1580          X1: {1581            Parachain: QUARTZ_CHAIN,1582          },1583        },1584      },1585    };15861587    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1588      targetAccount.addressRaw,1589      {1590        Concrete: {1591          parents: 1,1592          interior: {1593            X1: {1594              Parachain: QUARTZ_CHAIN,1595            },1596          },1597        },1598      },1599      testAmount,1600    );16011602    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1603      targetAccount.addressRaw,1604      {1605        Concrete: {1606          parents: 0,1607          interior: 'Here',1608        },1609      },1610      testAmount,1611    );16121613    let maliciousXcmProgramFullIdSent: any;1614    let maliciousXcmProgramHereIdSent: any;1615    const maxWaitBlocks = 3;16161617    // Try to trick Quartz using full QTZ identification1618    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1619      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16201621      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1622    });16231624    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1625        && event.outcome().isUntrustedReserveLocation);16261627    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1628    expect(accountBalance).to.be.equal(0n);16291630    // Try to trick Quartz using shortened QTZ identification1631    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1632      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16331634      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1635    });16361637    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1638        && event.outcome().isUntrustedReserveLocation);16391640    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1641    expect(accountBalance).to.be.equal(0n);1642  });1643});