difftreelog
Merge pull request #983 from UniqueNetwork/ci/baedeker-node-only
in: master
Move node-only CI to baedeker
14 files changed
.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]
+ },
+}
.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],
.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']
},
.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 [
.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
.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"]
.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
.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
.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
+
.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 ./
.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 }}'}})
tests/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!"
tests/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)
+});
tests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth1// 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});