--- /dev/null +++ b/.docker/additional/xcm-rococo/.env @@ -0,0 +1,16 @@ +RUST_TOOLCHAIN=nightly-2022-07-24 +UNIQUE_BRANCH="develop" + +POLKADOT_BUILD_BRANCH=release-v0.9.29 + +KARURA_BUILD_BRANCH=2.9.1 +ACALA_BUILD_BRANCH=2.9.2 + +MOONRIVER_BUILD_BRANCH=runtime-1701 +MOONBEAM_BUILD_BRANCH=runtime-1701 + +STATEMINE_BUILD_BRANCH=parachains-v9270 +STATEMINT_BUILD_BRANCH=release-parachains-v9230 +WESTMINT_BUILD_BRANCH=parachains-v9270 + +POLKADOT_LAUNCH_BRANCH="unique-network" --- /dev/null +++ b/.docker/additional/xcm-rococo/Dockerfile-xcm-opal-rococo @@ -0,0 +1,89 @@ +ARG CHAIN=opal +ARG LAUNCH_CONFIG_FILE=launch-config-xcm-opal-rococo.json + +# ===== Rust builder ===== +FROM ubuntu:20.04 as rust-builder +LABEL maintainer="Unique.Network" + +ARG RUST_TOOLCHAIN +ENV RUST_TOOLCHAIN $RUST_TOOLCHAIN +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 && \ + apt-get clean && \ + rm -r /var/lib/apt/lists/* + +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none + +RUN rustup toolchain 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 ====== +FROM rust-builder as builder-unique + +ARG UNIQUE_BRANCH +ARG CHAIN +ARG LAUNCH_CONFIG_FILE +ARG PROFILE=release + +WORKDIR /unique_parachain +#COPY . . + +RUN git clone -b ${UNIQUE_BRANCH} https://github.com/UniqueNetwork/unique-chain.git . && \ +# cd unique-chain && \ + cargo build --features=${CHAIN}-runtime --$PROFILE + +# ===== RUN ====== +FROM ubuntu:20.04 + +ARG POLKADOT_LAUNCH_BRANCH +ARG LAUNCH_CONFIG_FILE +ENV POLKADOT_LAUNCH_BRANCH $POLKADOT_LAUNCH_BRANCH +ENV LAUNCH_CONFIG_FILE $LAUNCH_CONFIG_FILE + +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} + +RUN export NVM_DIR="$HOME/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + cd /polkadot-launch && \ + npm install --global yarn && \ + yarn install + +COPY --from=builder-unique /unique_parachain/.docker/xcm-config/${LAUNCH_CONFIG_FILE} /polkadot-launch/ + +COPY --from=builder-unique /unique_parachain/target/release/unique-collator /unique-chain/target/release/ +COPY --from=uniquenetwork/builder-polkadot:${POLKADOT_BUILD_BRANCH} /unique_parachain/polkadot/target/release/polkadot /polkadot/target/release/ +COPY --from=uniquenetwork/builder-cumulus:${STATEMINE_BUILD_BRANCH} /unique_parachain/cumulus/target/release/polkadot-parachain /cumulus/target/release/cumulus +COPY --from=uniquenetwork/builder-chainql:latest /chainql/target/release/chainql /chainql/target/release/ + +EXPOSE 9844 +EXPOSE 9944 +EXPOSE 9946 +EXPOSE 9947 +EXPOSE 9948 + +CMD export NVM_DIR="$HOME/.nvm" PATH="$PATH:/chainql/target/release" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + cd /polkadot-launch && \ + yarn start ${LAUNCH_CONFIG_FILE} + + --- /dev/null +++ b/.docker/additional/xcm-rococo/Dockerfile-xcm-quartz-rococo @@ -0,0 +1,93 @@ +ARG CHAIN=quartz +ARG LAUNCH_CONFIG_FILE=launch-config-xcm-quartz-rococo.json + +# ===== Rust builder ===== +FROM ubuntu:20.04 as rust-builder +LABEL maintainer="Unique.Network" + +ARG RUST_TOOLCHAIN +ENV RUST_TOOLCHAIN $RUST_TOOLCHAIN +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 && \ + apt-get clean && \ + rm -r /var/lib/apt/lists/* + +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none + +RUN rustup toolchain 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 ====== +FROM rust-builder as builder-unique + +ARG UNIQUE_BRANCH +ARG CHAIN +ARG LAUNCH_CONFIG_FILE +ARG PROFILE=release + +WORKDIR /unique_parachain +#COPY . . + +RUN git clone -b ${UNIQUE_BRANCH} https://github.com/UniqueNetwork/unique-chain.git . && \ +# cd unique-chain && \ + cargo build --features=${CHAIN}-runtime --$PROFILE + +# ===== RUN ====== +FROM ubuntu:20.04 + +ARG POLKADOT_LAUNCH_BRANCH +ARG LAUNCH_CONFIG_FILE +ENV POLKADOT_LAUNCH_BRANCH $POLKADOT_LAUNCH_BRANCH +ENV LAUNCH_CONFIG_FILE $LAUNCH_CONFIG_FILE + +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} + +RUN export NVM_DIR="$HOME/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + cd /polkadot-launch && \ + npm install --global yarn && \ + yarn install + +COPY --from=builder-unique /unique_parachain/.docker/xcm-config/${LAUNCH_CONFIG_FILE} /polkadot-launch/ +COPY --from=builder-unique /unique_parachain/.docker/xcm-config/5validators.jsonnet /polkadot-launch/5validators.jsonnet +COPY --from=builder-unique /unique_parachain/.docker/xcm-config/minBondFix.jsonnet /polkadot-launch/minBondFix.jsonnet + +COPY --from=builder-unique /unique_parachain/target/release/unique-collator /unique-chain/target/release/ +COPY --from=uniquenetwork/builder-polkadot:${POLKADOT_BUILD_BRANCH} /unique_parachain/polkadot/target/release/polkadot /polkadot/target/release/ +COPY --from=uniquenetwork/builder-moonbeam:${MOONRIVER_BUILD_BRANCH} /unique_parachain/moonbeam/target/release/moonbeam /moonbeam/target/release/ +COPY --from=uniquenetwork/builder-cumulus:${STATEMINE_BUILD_BRANCH} /unique_parachain/cumulus/target/release/polkadot-parachain /cumulus/target/release/cumulus +COPY --from=uniquenetwork/builder-acala:${KARURA_BUILD_BRANCH} /unique_parachain/Acala/target/production/acala /acala/target/release/ +COPY --from=uniquenetwork/builder-chainql:latest /chainql/target/release/chainql /chainql/target/release/ + +EXPOSE 9844 +EXPOSE 9944 +EXPOSE 9946 +EXPOSE 9947 +EXPOSE 9948 + +CMD export NVM_DIR="$HOME/.nvm" PATH="$PATH:/chainql/target/release" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + cd /polkadot-launch && \ + yarn start ${LAUNCH_CONFIG_FILE} + + --- /dev/null +++ b/.docker/additional/xcm-rococo/Dockerfile-xcm-unique-rococo @@ -0,0 +1,93 @@ +ARG CHAIN=unique +ARG LAUNCH_CONFIG_FILE=launch-config-xcm-unique-rococo.json + +# ===== Rust builder ===== +FROM ubuntu:20.04 as rust-builder +LABEL maintainer="Unique.Network" + +ARG RUST_TOOLCHAIN +ENV RUST_TOOLCHAIN $RUST_TOOLCHAIN +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 && \ + apt-get clean && \ + rm -r /var/lib/apt/lists/* + +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none + +RUN rustup toolchain 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 ====== +FROM rust-builder as builder-unique + +ARG UNIQUE_BRANCH +ARG CHAIN +ARG LAUNCH_CONFIG_FILE +ARG PROFILE=release + +WORKDIR /unique_parachain +#COPY . . + +RUN git clone -b ${UNIQUE_BRANCH} https://github.com/UniqueNetwork/unique-chain.git . && \ +# cd unique-chain && \ + cargo build --features=${CHAIN}-runtime --$PROFILE + +# ===== RUN ====== +FROM ubuntu:20.04 + +ARG POLKADOT_LAUNCH_BRANCH +ARG LAUNCH_CONFIG_FILE +ENV POLKADOT_LAUNCH_BRANCH $POLKADOT_LAUNCH_BRANCH +ENV LAUNCH_CONFIG_FILE $LAUNCH_CONFIG_FILE + +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} + +RUN export NVM_DIR="$HOME/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + cd /polkadot-launch && \ + npm install --global yarn && \ + yarn install + +COPY --from=builder-unique /unique_parachain/.docker/xcm-config/${LAUNCH_CONFIG_FILE} /polkadot-launch/ +COPY --from=builder-unique /unique_parachain/.docker/xcm-config/5validators.jsonnet /polkadot-launch/5validators.jsonnet +COPY --from=builder-unique /unique_parachain/.docker/xcm-config/minBondFix.jsonnet /polkadot-launch/minBondFix.jsonnet + +COPY --from=builder-unique /unique_parachain/target/release/unique-collator /unique-chain/target/release/ +COPY --from=uniquenetwork/builder-polkadot:${POLKADOT_BUILD_BRANCH} /unique_parachain/polkadot/target/release/polkadot /polkadot/target/release/ +COPY --from=uniquenetwork/builder-moonbeam:${MOONBEAM_BUILD_BRANCH} /unique_parachain/moonbeam/target/release/moonbeam /moonbeam/target/release/ +COPY --from=uniquenetwork/builder-cumulus:${STATEMINT_BUILD_BRANCH} /unique_parachain/cumulus/target/release/polkadot-parachain /cumulus/target/release/cumulus +COPY --from=uniquenetwork/builder-acala:${ACALA_BUILD_BRANCH} /unique_parachain/Acala/target/production/acala /acala/target/release/ +COPY --from=uniquenetwork/builder-chainql:latest /chainql/target/release/chainql /chainql/target/release/ + +EXPOSE 9844 +EXPOSE 9944 +EXPOSE 9946 +EXPOSE 9947 +EXPOSE 9948 + +CMD export NVM_DIR="$HOME/.nvm" PATH="$PATH:/chainql/target/release" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + cd /polkadot-launch && \ + yarn start ${LAUNCH_CONFIG_FILE} + + --- /dev/null +++ b/.docker/additional/xcm-rococo/docker-compose-xcm-opal-rococo.yml @@ -0,0 +1,26 @@ +version: "3.5" + +services: + xcm_opal_rococo: + build: + context: . + dockerfile: .Dockerfile-xcm-opal-rococo + container_name: xcm-opal-rococo + image: xcm-opal-rococo:latest + env_file: .env + expose: + - 9844 + - 9944 + - 9946 + - 9947 + - 9948 + ports: + - 127.0.0.1:9844:9844 + - 127.0.0.1:9944:9944 + - 127.0.0.1:9946:9946 + - 127.0.0.1:9947:9947 + - 127.0.0.1:9948:9948 + logging: + options: + max-size: "1m" + max-file: "3" --- /dev/null +++ b/.docker/additional/xcm-rococo/docker-compose-xcm-quartz-rococo.yml @@ -0,0 +1,26 @@ +version: "3.5" + +services: + xcm_quartz_rococo: + build: + context: . + dockerfile: .Dockerfile-xcm-quartz-rococo + container_name: xcm-quartz-rococo + image: xcm-quartz-rococo:latest + env_file: .env + expose: + - 9844 + - 9944 + - 9946 + - 9947 + - 9948 + ports: + - 127.0.0.1:9844:9844 + - 127.0.0.1:9944:9944 + - 127.0.0.1:9946:9946 + - 127.0.0.1:9947:9947 + - 127.0.0.1:9948:9948 + logging: + options: + max-size: "1m" + max-file: "3" --- /dev/null +++ b/.docker/additional/xcm-rococo/docker-compose-xcm-unique-rococo.yml @@ -0,0 +1,26 @@ +version: "3.5" + +services: + xcm_unique_rococo: + build: + context: . + dockerfile: .Dockerfile-xcm-unique-rococo + container_name: xcm-unique-rococo + image: xcm-unique-rococo:latest + env_file: .env + expose: + - 9844 + - 9944 + - 9946 + - 9947 + - 9948 + ports: + - 127.0.0.1:9844:9844 + - 127.0.0.1:9944:9944 + - 127.0.0.1:9946:9946 + - 127.0.0.1:9947:9947 + - 127.0.0.1:9948:9948 + logging: + options: + max-size: "1m" + max-file: "3" --- /dev/null +++ b/.docker/xcm-config/launch-config-xcm-opal-rococo.json @@ -0,0 +1,134 @@ +{ + "relaychain": { + "bin": "/polkadot/target/release/polkadot", + "chain": "rococo-local", + "chainInitializer": [ + "chainql", + "--tla-code=spec=import '${spec}'", + "5validators.jsonnet" + ], + "nodes": [ + { + "name": "alice", + "wsPort": 9844, + "rpcPort": 9843, + "port": 30444, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + }, + { + "name": "bob", + "wsPort": 9855, + "rpcPort": 9854, + "port": 30555, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + }, + { + "name": "charlie", + "wsPort": 9866, + "rpcPort": 9865, + "port": 30666, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + }, + { + "name": "dave", + "wsPort": 9877, + "rpcPort": 9876, + "port": 30777, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + }, + { + "name": "eve", + "wsPort": 9888, + "rpcPort": 9887, + "port": 30888, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + } + + ], + "genesis": { + "runtime": { + "runtime_genesis_config": { + "parachainsConfiguration": { + "config": { + "validation_upgrade_frequency": 1, + "validation_upgrade_delay": 1 + } + } + } + } + } + }, + "parachains": [ + { + "bin": "/unique-chain/target/release/unique-collator", + "id": "2095", + "balance": "1000000000000000000000000", + "nodes": [ + { + "port": 31200, + "wsPort": 9944, + "rpcPort": 9933, + "name": "alice", + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + } + ] + }, + { + "bin": "/cumulus/target/release/cumulus", + "id": "1000", + "chain": "westmint-local", + "balance": "1000000000000000000000000", + "nodes": [ + { + "wsPort": 9948, + "port": 31204, + "name": "alice", + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + } + ] + } + ], + "simpleParachains": [], + "hrmpChannels": [ + { + "sender": 2095, + "recipient": 1000, + "maxCapacity": 8, + "maxMessageSize": 512 + }, + { + "sender": 1000, + "recipient": 2095, + "maxCapacity": 8, + "maxMessageSize": 512 + } + ], + "finalization": false +} + --- /dev/null +++ b/.docker/xcm-config/launch-config-xcm-quartz-rococo.json @@ -0,0 +1,199 @@ +{ + "relaychain": { + "bin": "/polkadot/target/release/polkadot", + "chain": "rococo-local", + "chainInitializer": [ + "chainql", + "--tla-code=spec=import '${spec}'", + "5validators.jsonnet" + ], + "nodes": [ + { + "name": "alice", + "wsPort": 9844, + "rpcPort": 9843, + "port": 30444, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + }, + { + "name": "bob", + "wsPort": 9855, + "rpcPort": 9854, + "port": 30555, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + }, + { + "name": "charlie", + "wsPort": 9866, + "rpcPort": 9865, + "port": 30666, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + }, + { + "name": "dave", + "wsPort": 9877, + "rpcPort": 9876, + "port": 30777, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + }, + { + "name": "eve", + "wsPort": 9888, + "rpcPort": 9887, + "port": 30888, + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lparachain::candidate_validation=debug" + ] + } + + ], + "genesis": { + "runtime": { + "runtime_genesis_config": { + "parachainsConfiguration": { + "config": { + "validation_upgrade_frequency": 1, + "validation_upgrade_delay": 1 + } + } + } + } + } + }, + "parachains": [ + { + "bin": "/unique-chain/target/release/unique-collator", + "id": "2095", + "balance": "1000000000000000000000000", + "nodes": [ + { + "port": 31200, + "wsPort": 9944, + "rpcPort": 9933, + "name": "alice", + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + } + ] + }, + { + "bin": "/acala/target/release/acala", + "id": "2000", + "chain": "karura-dev", + "balance": "1000000000000000000000", + "nodes": [ + { + "wsPort": 9946, + "port": 31202, + "name": "alice", + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + } + ] + }, + { + "bin": "/moonbeam/target/release/moonbeam", + "id": 2023, + "balance": "1000000000000000000000", + "chain": "moonriver-local", + "chainInitializer": [ + "chainql", + "--tla-code=spec=import '${spec}'", + "minBondFix.jsonnet" + ], + "nodes": [ + { + "wsPort": 9947, + "port": 31203, + "name": "alice", + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external", + "--", + "--execution=wasm" + ] + } + ] + }, + { + "bin": "/cumulus/target/release/cumulus", + "id": "1000", + "chain": "statemine-local", + "balance": "1000000000000000000000000", + "nodes": [ + { + "wsPort": 9948, + "port": 31204, + "name": "alice", + "flags": [ + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + } + ] + } + ], + "simpleParachains": [], + "hrmpChannels": [ + { + "sender": 2095, + "recipient": 2000, + "maxCapacity": 8, + "maxMessageSize": 512 + }, + { + "sender": 2000, + "recipient": 2095, + "maxCapacity": 8, + "maxMessageSize": 512 + }, + { + "sender": 2095, + "recipient": 2023, + "maxCapacity": 8, + "maxMessageSize": 512 + }, + { + "sender": 2023, + "recipient": 2095, + "maxCapacity": 8, + "maxMessageSize": 512 + }, + { + "sender": 2095, + "recipient": 1000, + "maxCapacity": 8, + "maxMessageSize": 512 + }, + { + "sender": 1000, + "recipient": 2095, + "maxCapacity": 8, + "maxMessageSize": 512 + } + ], + "finalization": false +} + --- a/.docker/xcm-config/launch-config-xcm-unique-rococo.json +++ b/.docker/xcm-config/launch-config-xcm-unique-rococo.json @@ -90,8 +90,8 @@ "rpcPort": 9933, "name": "alice", "flags": [ - "-lruntime=trace", - "-lxcm=trace", + "-lruntime=trace", + "-lxcm=trace", "--unsafe-rpc-external", "--unsafe-ws-external" ] @@ -114,8 +114,8 @@ "port": 31202, "name": "alice", "flags": [ - "-lruntime=trace", - "-lxcm=trace", + "-lruntime=trace", + "-lxcm=trace", "--unsafe-rpc-external", "--unsafe-ws-external" ] @@ -133,11 +133,11 @@ "port": 31203, "name": "alice", "flags": [ - "-lruntime=trace", - "-lxcm=trace", + "-lruntime=trace", + "-lxcm=trace", "--unsafe-rpc-external", "--unsafe-ws-external", - "--", + "--", "--execution=wasm" ] } @@ -154,8 +154,8 @@ "port": 31204, "name": "alice", "flags": [ - "-lruntime=trace", - "-lxcm=trace", + "-lruntime=trace", + "-lxcm=trace", "--unsafe-rpc-external", "--unsafe-ws-external" ] --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RUST_TOOLCHAIN=nightly-2022-07-24 -POLKADOT_BUILD_BRANCH=release-v0.9.27 +POLKADOT_BUILD_BRANCH=release-v0.9.29 POLKADOT_MAINNET_BRANCH=release-v0.9.26 UNIQUE_MAINNET_TAG=v924010 @@ -13,7 +13,7 @@ QUARTZ_REPLICA_FROM=wss://eu-ws-quartz.unique.network:443 UNIQUE_REPLICA_FROM=wss://eu-ws.unique.network:443 -POLKADOT_LAUNCH_BRANCH=feature/rewrite-chain-id-in-spec +POLKADOT_LAUNCH_BRANCH=unique-network KARURA_BUILD_BRANCH=2.9.1 ACALA_BUILD_BRANCH=2.9.2 --- a/.github/workflows/testnet-build.yml +++ b/.github/workflows/testnet-build.yml @@ -125,13 +125,10 @@ run: docker pull uniquenetwork/builder-polkadot:${{ env.POLKADOT_BUILD_BRANCH }} - name: Build the stack - run: cd .docker/ && docker build --file ./Dockerfile-testnet.${{ matrix.network }}.yml --tag uniquenetwork/${{ matrix.network }}-testnet-local:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }} --tag uniquenetwork/${{ matrix.network }}-testnet-local:latest . + run: cd .docker/ && docker build --file ./Dockerfile-testnet.${{ matrix.network }}.yml --tag uniquenetwork/${{ matrix.network }}-testnet-local-nightly:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }} . - name: Push docker version image - run: docker push uniquenetwork/${{ matrix.network }}-testnet-local:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }} - - - name: Push docker latest image - run: docker push uniquenetwork/${{ matrix.network }}-testnet-local:latest + run: docker push uniquenetwork/${{ matrix.network }}-testnet-local-nightly:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }} - name: Clean Workspace if: always() --- a/tests/src/config.ts +++ b/tests/src/config.ts @@ -19,6 +19,12 @@ const config = { substrateUrl: process.env.substrateUrl || 'ws://127.0.0.1:9944', frontierUrl: process.env.frontierUrl || 'http://127.0.0.1:9933', + relayUrl: process.env.relayUrl || 'ws://127.0.0.1:9844', + acalaUrl: process.env.acalaUrl || 'ws://127.0.0.1:9946', + karuraUrl: process.env.acalaUrl || 'ws://127.0.0.1:9946', + moonbeamUrl: process.env.moonbeamUrl || 'ws://127.0.0.1:9947', + moonriverUrl: process.env.moonbeamUrl || 'ws://127.0.0.1:9947', + westmintUrl: process.env.westmintUrl || 'ws://127.0.0.1:9948', }; -export default config; \ No newline at end of file +export default config; --- a/tests/src/deprecated-helpers/contracthelpers.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import {submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; -import fs from 'fs'; -import {Abi, CodePromise, ContractPromise as Contract} from '@polkadot/api-contract'; -import {IKeyringPair} from '@polkadot/types/types'; -import {ApiPromise} from '@polkadot/api'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -import {findUnusedAddress, getGenericResult} from './helpers'; - -const value = 0; -const gasLimit = '200000000000'; -const endowment = '100000000000000000'; - -/* eslint no-async-promise-executor: "off" */ -function deployContract(alice: IKeyringPair, code: CodePromise, constructor = 'default', ...args: any[]): Promise { - return new Promise(async (resolve) => { - const unsub = await (code as any) - .tx[constructor]({value: endowment, gasLimit}, ...args) - .signAndSend(alice, (result: any) => { - if (result.status.isInBlock || result.status.isFinalized) { - // here we have an additional field in the result, containing the blueprint - resolve((result as any).contract); - unsub(); - } - }); - }); -} - -async function prepareDeployer(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair) { - // Find unused address - const deployer = await findUnusedAddress(api, privateKeyWrapper); - - // Transfer balance to it - const alice = privateKeyWrapper('//Alice'); - const amount = BigInt(endowment) + 10n**15n; - const tx = api.tx.balances.transfer(deployer.address, amount); - await submitTransactionAsync(alice, tx); - - return deployer; -} - -export async function deployFlipper(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair): Promise<[Contract, IKeyringPair]> { - const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8')); - const abi = new Abi(metadata); - - const deployer = await prepareDeployer(api, privateKeyWrapper); - - const wasm = fs.readFileSync('./src/flipper/flipper.wasm'); - - const code = new CodePromise(api, abi, wasm); - - const contract = (await deployContract(deployer, code, 'new', true)) as Contract; - - const initialGetResponse = await getFlipValue(contract, deployer); - expect(initialGetResponse).to.be.true; - - return [contract, deployer]; -} - -export async function getFlipValue(contract: Contract, deployer: IKeyringPair) { - const result = await contract.query.get(deployer.address, {value, gasLimit}); - - if(!result.result.isOk) { - throw 'Failed to get flipper value'; - } - return (result.result.asOk.data[0] == 0x00) ? false : true; -} - -export async function toggleFlipValueExpectSuccess(sender: IKeyringPair, contract: Contract) { - const tx = contract.tx.flip({value, gasLimit}); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; -} - -export async function toggleFlipValueExpectFailure(sender: IKeyringPair, contract: Contract) { - const tx = contract.tx.flip({value, gasLimit}); - await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; -} - -export async function deployTransferContract(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair): Promise<[Contract, IKeyringPair]> { - const metadata = JSON.parse(fs.readFileSync('./src/transfer_contract/metadata.json').toString('utf-8')); - const abi = new Abi(metadata); - - const deployer = await prepareDeployer(api, privateKeyWrapper); - - const wasm = fs.readFileSync('./src/transfer_contract/nft_transfer.wasm'); - - const code = new CodePromise(api, abi, wasm); - - const contract = await deployContract(deployer, code); - - return [contract, deployer]; -} --- a/tests/src/deprecated-helpers/eth/helpers.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -declare module 'solc'; \ No newline at end of file --- a/tests/src/deprecated-helpers/eth/helpers.ts +++ /dev/null @@ -1,451 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -// eslint-disable-next-line @typescript-eslint/triple-slash-reference -/// - -import {ApiPromise} from '@polkadot/api'; -import {IKeyringPair} from '@polkadot/types/types'; -import {addressToEvm, evmToAddress} from '@polkadot/util-crypto'; -import {expect} from 'chai'; -import * as solc from 'solc'; -import Web3 from 'web3'; -import config from '../../config'; -import getBalance from '../../substrate/get-balance'; -import usingApi, {submitTransactionAsync} from '../../substrate/substrate-api'; -import waitNewBlocks from '../../substrate/wait-new-blocks'; -import {CollectionMode, CrossAccountId, getDetailedCollectionInfo, getGenericResult, UNIQUE} from '../helpers'; -import collectionHelpersAbi from '../../eth/collectionHelpersAbi.json'; -import fungibleAbi from '../../eth/fungibleAbi.json'; -import nonFungibleAbi from '../../eth/nonFungibleAbi.json'; -import refungibleAbi from '../../eth/reFungibleAbi.json'; -import refungibleTokenAbi from '../../eth/reFungibleTokenAbi.json'; -import contractHelpersAbi from '../../eth/util/contractHelpersAbi.json'; - -export const GAS_ARGS = {gas: 2500000}; - -export enum SponsoringMode { - Disabled = 0, - Allowlisted = 1, - Generous = 2, -} - -let web3Connected = false; -export async function usingWeb3(cb: (web3: Web3) => Promise | T): Promise { - if (web3Connected) throw new Error('do not nest usingWeb3 calls'); - web3Connected = true; - - const provider = new Web3.providers.WebsocketProvider(config.substrateUrl); - const web3 = new Web3(provider); - - try { - return await cb(web3); - } finally { - // provider.disconnect(3000, 'normal disconnect'); - provider.connection.close(); - web3Connected = false; - } -} - -function encodeIntBE(v: number): number[] { - if (v >= 0xffffffff || v < 0) throw new Error('id overflow'); - return [ - v >> 24, - (v >> 16) & 0xff, - (v >> 8) & 0xff, - v & 0xff, - ]; -} - -export async function getCollectionAddressFromResult(api: ApiPromise, result: any) { - const collectionIdAddress = normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); - const collectionId = collectionIdFromAddress(collectionIdAddress); - const collection = (await getDetailedCollectionInfo(api, collectionId))!; - return {collectionIdAddress, collectionId, collection}; -} - -export function collectionIdToAddress(collection: number): string { - const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e, - ...encodeIntBE(collection), - ]); - return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')); -} -export function collectionIdFromAddress(address: string): number { - if (!address.startsWith('0x')) - throw 'address not starts with "0x"'; - if (address.length > 42) - throw 'address length is more than 20 bytes'; - return Number('0x' + address.substring(address.length - 8)); -} - -export function normalizeAddress(address: string): string { - return '0x' + address.substring(address.length - 40); -} - -export function tokenIdToAddress(collection: number, token: number): string { - const buf = Buffer.from([0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0, - ...encodeIntBE(collection), - ...encodeIntBE(token), - ]); - return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')); -} - -export function tokenIdFromAddress(address: string) { - if (!address.startsWith('0x')) - throw 'address not starts with "0x"'; - if (address.length > 42) - throw 'address length is more than 20 bytes'; - return { - collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)), - tokenId: Number('0x' + address.substring(address.length - 8)), - }; -} - -export function tokenIdToCross(collection: number, token: number): CrossAccountId { - return { - Ethereum: tokenIdToAddress(collection, token), - }; -} - -export function createEthAccount(web3: Web3) { - const account = web3.eth.accounts.create(); - web3.eth.accounts.wallet.add(account.privateKey); - return account.address; -} - -export async function createEthAccountWithBalance(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair) { - const alice = privateKeyWrapper('//Alice'); - const account = createEthAccount(web3); - await transferBalanceToEth(api, alice, account); - - return account; -} - -export async function transferBalanceToEth(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) { - const tx = api.tx.balances.transfer(evmToAddress(target), amount); - const events = await submitTransactionAsync(source, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; -} - -export async function createRFTCollection(api: ApiPromise, web3: Web3, owner: string) { - const collectionHelper = evmCollectionHelpers(web3, owner); - const result = await collectionHelper.methods - .createRFTCollection('A', 'B', 'C') - .send({value: Number(2n * UNIQUE)}); - return await getCollectionAddressFromResult(api, result); -} - - -export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) { - const collectionHelper = evmCollectionHelpers(web3, owner); - const result = await collectionHelper.methods - .createNonfungibleCollection('A', 'B', 'C') - .send({value: Number(2n * UNIQUE)}); - return await getCollectionAddressFromResult(api, result); -} - -export function uniqueNFT(web3: Web3, address: string, owner: string) { - return new web3.eth.Contract(nonFungibleAbi as any, address, { - from: owner, - ...GAS_ARGS, - }); -} - -export function uniqueRefungible(web3: Web3, collectionAddress: string, owner: string) { - return new web3.eth.Contract(refungibleAbi as any, collectionAddress, { - from: owner, - ...GAS_ARGS, - }); -} - -export function uniqueRefungibleToken(web3: Web3, tokenAddress: string, owner: string | undefined = undefined) { - return new web3.eth.Contract(refungibleTokenAbi as any, tokenAddress, { - from: owner, - ...GAS_ARGS, - }); -} - -export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) { - let i: any = it; - if (opts.only) i = i.only; - else if (opts.skip) i = i.skip; - i(name, async () => { - await usingApi(async (api, privateKeyWrapper) => { - await usingWeb3(async web3 => { - await cb({api, web3, privateKeyWrapper}); - }); - }); - }); -} -itWeb3.only = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {only: true}); -itWeb3.skip = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {skip: true}); - -export async function generateSubstrateEthPair(web3: Web3) { - const account = web3.eth.accounts.create(); - evmToAddress(account.address); -} - -type NormalizedEvent = { - address: string, - event: string, - args: { [key: string]: string } -}; - -export function normalizeEvents(events: any): NormalizedEvent[] { - const output = []; - for (const key of Object.keys(events)) { - if (key.match(/^[0-9]+$/)) { - output.push(events[key]); - } else if (Array.isArray(events[key])) { - output.push(...events[key]); - } else { - output.push(events[key]); - } - } - output.sort((a, b) => a.logIndex - b.logIndex); - return output.map(({address, event, returnValues}) => { - const args: { [key: string]: string } = {}; - for (const key of Object.keys(returnValues)) { - if (!key.match(/^[0-9]+$/)) { - args[key] = returnValues[key]; - } - } - return { - address, - event, - args, - }; - }); -} - -export async function recordEvents(contract: any, action: () => Promise): Promise { - const out: any = []; - contract.events.allEvents((_: any, event: any) => { - out.push(event); - }); - await action(); - return normalizeEvents(out); -} - -export function subToEthLowercase(eth: string): string { - const bytes = addressToEvm(eth); - return '0x' + Buffer.from(bytes).toString('hex'); -} - -export function subToEth(eth: string): string { - return Web3.utils.toChecksumAddress(subToEthLowercase(eth)); -} - -export interface CompiledContract { - abi: any, - object: string, -} - -export function compileContract(name: string, src: string) : CompiledContract { - const out = JSON.parse(solc.compile(JSON.stringify({ - language: 'Solidity', - sources: { - [`${name}.sol`]: { - content: ` - // SPDX-License-Identifier: UNLICENSED - pragma solidity ^0.8.6; - - ${src} - `, - }, - }, - settings: { - outputSelection: { - '*': { - '*': ['*'], - }, - }, - }, - }))).contracts[`${name}.sol`][name]; - - return { - abi: out.abi, - object: '0x' + out.evm.bytecode.object, - }; -} - -export async function deployFlipper(web3: Web3, deployer: string) { - const compiled = compileContract('Flipper', ` - contract Flipper { - bool value = false; - function flip() public { - value = !value; - } - function getValue() public view returns (bool) { - return value; - } - } - `); - const flipperContract = new web3.eth.Contract(compiled.abi, undefined, { - data: compiled.object, - from: deployer, - ...GAS_ARGS, - }); - const flipper = await flipperContract.deploy({data: compiled.object}).send({from: deployer}); - - return flipper; -} - -export async function deployCollector(web3: Web3, deployer: string) { - const compiled = compileContract('Collector', ` - contract Collector { - uint256 collected; - fallback() external payable { - giveMoney(); - } - function giveMoney() public payable { - collected += msg.value; - } - function getCollected() public view returns (uint256) { - return collected; - } - function getUnaccounted() public view returns (uint256) { - return address(this).balance - collected; - } - - function withdraw(address payable target) public { - target.transfer(collected); - collected = 0; - } - } - `); - const collectorContract = new web3.eth.Contract(compiled.abi, undefined, { - data: compiled.object, - from: deployer, - ...GAS_ARGS, - }); - const collector = await collectorContract.deploy({data: compiled.object}).send({from: deployer}); - - return collector; -} - -/** - * pallet evm_contract_helpers - * @param web3 - * @param caller - eth address - * @returns - */ -export function contractHelpers(web3: Web3, caller: string) { - return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS}); -} - -/** - * evm collection helper - * @param web3 - * @param caller - eth address - * @returns - */ -export function evmCollectionHelpers(web3: Web3, caller: string) { - return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, ...GAS_ARGS}); -} - -/** - * evm collection - * @param web3 - * @param caller - eth address - * @returns - */ -export function evmCollection(web3: Web3, caller: string, collection: string, mode: CollectionMode = {type: 'NFT'}) { - let abi; - switch (mode.type) { - case 'Fungible': - abi = fungibleAbi; - break; - - case 'NFT': - abi = nonFungibleAbi; - break; - - case 'ReFungible': - abi = refungibleAbi; - break; - - default: - throw 'Bad collection mode'; - } - const contract = new web3.eth.Contract(abi as any, collection, {from: caller, ...GAS_ARGS}); - return contract; -} - -/** - * Execute ethereum method call using substrate account - * @param to target contract - * @param mkTx - closure, receiving `contract.methods`, and returning method call, - * to be used as following (assuming `to` = erc20 contract): - * `m => m.transfer(to, amount)` - * - * # Example - * ```ts - * executeEthTxOnSub(api, alice, erc20Contract, m => m.transfer(target, amount)); - * ``` - */ -export async function executeEthTxOnSub(web3: Web3, api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, {value = 0}: {value?: bigint | number} = { }) { - const tx = api.tx.evm.call( - subToEth(from.address), - to.options.address, - mkTx(to.methods).encodeABI(), - value, - GAS_ARGS.gas, - await web3.eth.getGasPrice(), - null, - null, - [], - ); - const events = await submitTransactionAsync(from, tx); - expect(events.some(({event: {section, method}}) => section == 'evm' && method == 'Executed')).to.be.true; -} - -export async function ethBalanceViaSub(api: ApiPromise, address: string): Promise { - return (await getBalance(api, [evmToAddress(address)]))[0]; -} - -/** - * Measure how much gas given closure consumes - * - * @param user which user balance will be checked - */ -export async function recordEthFee(api: ApiPromise, user: string, call: () => Promise): Promise { - const before = await ethBalanceViaSub(api, user); - - await call(); - - // In dev mode, the transaction might not finish processing in time - await waitNewBlocks(api, 1); - const after = await ethBalanceViaSub(api, user); - - // Can't use .to.be.less, because chai doesn't supports bigint - expect(after < before).to.be.true; - - return before - after; -} - -type ElementOf = A extends readonly (infer T)[] ? T : never; -// I want a fancier api, not a memory efficiency -export function* cartesian>, R extends Array>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf}]> { - if(args.length === 0) { - yield internalRest as any; - return; - } - for(const value of args[0]) { - yield* cartesian([...internalRest, value], ...args.slice(1)) as any; - } -} \ No newline at end of file --- a/tests/src/deprecated-helpers/helpers.ts +++ /dev/null @@ -1,1881 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -import '../interfaces/augment-api-rpc'; -import '../interfaces/augment-api-query'; -import {ApiPromise, Keyring} from '@polkadot/api'; -import type {AccountId, EventRecord, Event, BlockNumber} from '@polkadot/types/interfaces'; -import type {GenericEventData} from '@polkadot/types'; -import {AnyTuple, IEvent, IKeyringPair} from '@polkadot/types/types'; -import {evmToAddress} from '@polkadot/util-crypto'; -import {AnyNumber} from '@polkadot/types-codec/types'; -import BN from 'bn.js'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; -import {hexToStr, strToUTF16, utf16ToStr} from './util'; -import {UpDataStructsRpcCollection, UpDataStructsCreateItemData, UpDataStructsProperty} from '@polkadot/types/lookup'; -import {UpDataStructsTokenChild} from '../interfaces'; -import {Context} from 'mocha'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -export type CrossAccountId = { - Substrate: string, -} | { - Ethereum: string, -}; - - -export enum Pallets { - Inflation = 'inflation', - RmrkCore = 'rmrkcore', - RmrkEquip = 'rmrkequip', - ReFungible = 'refungible', - Fungible = 'fungible', - NFT = 'nonfungible', - Scheduler = 'scheduler', - AppPromotion = 'apppromotion', -} - -export async function isUnique(): Promise { - return usingApi(async api => { - const chain = await api.rpc.system.chain(); - - return chain.eq('UNIQUE'); - }); -} - -export async function isQuartz(): Promise { - return usingApi(async api => { - const chain = await api.rpc.system.chain(); - - return chain.eq('QUARTZ'); - }); -} - -let modulesNames: any; -export function getModuleNames(api: ApiPromise): string[] { - if (typeof modulesNames === 'undefined') - modulesNames = api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()); - return modulesNames; -} - -export async function missingRequiredPallets(requiredPallets: string[]): Promise { - return await usingApi(async api => { - const pallets = getModuleNames(api); - - return requiredPallets.filter(p => !pallets.includes(p)); - }); -} - -export async function checkPalletsPresence(requiredPallets: string[]): Promise { - return (await missingRequiredPallets(requiredPallets)).length == 0; -} - -export async function requirePallets(mocha: Context, requiredPallets: string[]) { - const missingPallets = await missingRequiredPallets(requiredPallets); - - if (missingPallets.length > 0) { - const skippingTestMsg = `\tSkipping test "${mocha.test?.title}".`; - const missingPalletsMsg = `\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; - const skipMsg = `${skippingTestMsg}\n${missingPalletsMsg}`; - - console.error('\x1b[38:5:208m%s\x1b[0m', skipMsg); - - mocha.skip(); - } -} - -export function bigIntToSub(api: ApiPromise, number: bigint) { - return api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON(); -} - -export function bigIntToDecimals(number: bigint, decimals = 18): string { - const numberStr = number.toString(); - const dotPos = numberStr.length - decimals; - - if (dotPos <= 0) { - return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr; - } else { - const intPart = numberStr.substring(0, dotPos); - const fractPart = numberStr.substring(dotPos); - return intPart + '.' + fractPart; - } -} - -export function normalizeAccountId(input: string | AccountId | CrossAccountId | IKeyringPair): CrossAccountId { - if (typeof input === 'string') { - if (input.length >= 47) { - return {Substrate: input}; - } else if (input.length === 42 && input.startsWith('0x')) { - return {Ethereum: input.toLowerCase()}; - } else if (input.length === 40 && !input.startsWith('0x')) { - return {Ethereum: '0x' + input.toLowerCase()}; - } else { - throw new Error(`Unknown address format: "${input}"`); - } - } - if ('address' in input) { - return {Substrate: input.address}; - } - if ('Ethereum' in input) { - return { - Ethereum: input.Ethereum.toLowerCase(), - }; - } else if ('ethereum' in input) { - return { - Ethereum: (input as any).ethereum.toLowerCase(), - }; - } else if ('Substrate' in input) { - return input; - } else if ('substrate' in input) { - return { - Substrate: (input as any).substrate, - }; - } - - // AccountId - return {Substrate: input.toString()}; -} -export function toSubstrateAddress(input: string | CrossAccountId | IKeyringPair): string { - input = normalizeAccountId(input); - if ('Substrate' in input) { - return input.Substrate; - } else { - return evmToAddress(input.Ethereum); - } -} - -export const U128_MAX = (1n << 128n) - 1n; - -const MICROUNIQUE = 1_000_000_000_000n; -const MILLIUNIQUE = 1_000n * MICROUNIQUE; -const CENTIUNIQUE = 10n * MILLIUNIQUE; -export const UNIQUE = 100n * CENTIUNIQUE; - -interface GenericResult { - success: boolean; - data: T | null; -} - -interface CreateCollectionResult { - success: boolean; - collectionId: number; -} - -interface CreateItemResult { - success: boolean; - collectionId: number; - itemId: number; - recipient?: CrossAccountId; - amount?: number; -} - -interface DestroyItemResult { - success: boolean; - collectionId: number; - itemId: number; - owner: CrossAccountId; - amount: number; -} - -interface TransferResult { - collectionId: number; - itemId: number; - sender?: CrossAccountId; - recipient?: CrossAccountId; - value: bigint; -} - -interface IReFungibleOwner { - fraction: BN; - owner: number[]; -} - -interface IGetMessage { - checkMsgUnqMethod: string; - checkMsgTrsMethod: string; - checkMsgSysMethod: string; -} - -export interface IFungibleTokenDataType { - value: number; -} - -export interface IChainLimits { - collectionNumbersLimit: number; - accountTokenOwnershipLimit: number; - collectionsAdminsLimit: number; - customDataLimit: number; - nftSponsorTransferTimeout: number; - fungibleSponsorTransferTimeout: number; - refungibleSponsorTransferTimeout: number; - //offchainSchemaLimit: number; - //constOnChainSchemaLimit: number; -} - -export interface IReFungibleTokenDataType { - owner: IReFungibleOwner[]; -} - -export function uniqueEventMessage(events: EventRecord[]): IGetMessage { - let checkMsgUnqMethod = ''; - let checkMsgTrsMethod = ''; - let checkMsgSysMethod = ''; - events.forEach(({event: {method, section}}) => { - if (section === 'common') { - checkMsgUnqMethod = method; - } else if (section === 'treasury') { - checkMsgTrsMethod = method; - } else if (section === 'system') { - checkMsgSysMethod = method; - } else { return null; } - }); - const result: IGetMessage = { - checkMsgUnqMethod, - checkMsgTrsMethod, - checkMsgSysMethod, - }; - return result; -} - -export function getEvent(events: EventRecord[], check: (event: IEvent) => event is T): T | undefined { - const event = events.find(r => check(r.event)); - if (!event) return; - return event.event as T; -} - -export function getGenericResult(events: EventRecord[]): GenericResult; -export function getGenericResult( - events: EventRecord[], - expectSection: string, - expectMethod: string, - extractAction: (data: GenericEventData) => T -): GenericResult; - -export function getGenericResult( - events: EventRecord[], - expectSection?: string, - expectMethod?: string, - extractAction?: (data: GenericEventData) => T, -): GenericResult { - let success = false; - let successData = null; - - events.forEach(({event: {data, method, section}}) => { - // console.log(` ${phase}: ${section}.${method}:: ${data}`); - if (method === 'ExtrinsicSuccess') { - success = true; - } else if ((expectSection == section) && (expectMethod == method)) { - successData = extractAction!(data as any); - } - }); - - const result: GenericResult = { - success, - data: successData, - }; - return result; -} - -export function getCreateCollectionResult(events: EventRecord[]): CreateCollectionResult { - const genericResult = getGenericResult(events, 'common', 'CollectionCreated', (data) => parseInt(data[0].toString(), 10)); - const result: CreateCollectionResult = { - success: genericResult.success, - collectionId: genericResult.data ?? 0, - }; - return result; -} - -export function getCreateItemsResult(events: EventRecord[]): CreateItemResult[] { - const results: CreateItemResult[] = []; - - const genericResult = getGenericResult(events, 'common', 'ItemCreated', (data) => { - const collectionId = parseInt(data[0].toString(), 10); - const itemId = parseInt(data[1].toString(), 10); - const recipient = normalizeAccountId(data[2].toJSON() as any); - const amount = parseInt(data[3].toString(), 10); - - const itemRes: CreateItemResult = { - success: true, - collectionId, - itemId, - recipient, - amount, - }; - - results.push(itemRes); - return results; - }); - - if (!genericResult.success) return []; - return results; -} - -export function getCreateItemResult(events: EventRecord[]): CreateItemResult { - const genericResult = getGenericResult(events, 'common', 'ItemCreated', (data) => data.map(function(value) { return value.toJSON(); })); - - if (genericResult.data == null) - return { - success: genericResult.success, - collectionId: 0, - itemId: 0, - amount: 0, - }; - else - return { - success: genericResult.success, - collectionId: genericResult.data[0] as number, - itemId: genericResult.data[1] as number, - recipient: normalizeAccountId(genericResult.data![2] as any), - amount: genericResult.data[3] as number, - }; -} - -export function getDestroyItemsResult(events: EventRecord[]): DestroyItemResult[] { - const results: DestroyItemResult[] = []; - - const genericResult = getGenericResult(events, 'common', 'ItemDestroyed', (data) => { - const collectionId = parseInt(data[0].toString(), 10); - const itemId = parseInt(data[1].toString(), 10); - const owner = normalizeAccountId(data[2].toJSON() as any); - const amount = parseInt(data[3].toString(), 10); - - const itemRes: DestroyItemResult = { - success: true, - collectionId, - itemId, - owner, - amount, - }; - - results.push(itemRes); - return results; - }); - - if (!genericResult.success) return []; - return results; -} - -export function getTransferResult(api: ApiPromise, events: EventRecord[]): TransferResult { - for (const {event} of events) { - if (api.events.common.Transfer.is(event)) { - const [collection, token, sender, recipient, value] = event.data; - return { - collectionId: collection.toNumber(), - itemId: token.toNumber(), - sender: normalizeAccountId(sender.toJSON() as any), - recipient: normalizeAccountId(recipient.toJSON() as any), - value: value.toBigInt(), - }; - } - } - throw new Error('no transfer event'); -} - -interface Nft { - type: 'NFT'; -} - -interface Fungible { - type: 'Fungible'; - decimalPoints: number; -} - -interface ReFungible { - type: 'ReFungible'; -} - -export type CollectionMode = Nft | Fungible | ReFungible; - -export type Property = { - key: any, - value: any, -}; - -type Permission = { - mutable: boolean; - collectionAdmin: boolean; - tokenOwner: boolean; -} - -type PropertyPermission = { - key: any; - permission: Permission; -} - -export type CreateCollectionParams = { - mode: CollectionMode, - name: string, - description: string, - tokenPrefix: string, - properties?: Array, - propPerm?: Array -}; - -const defaultCreateCollectionParams: CreateCollectionParams = { - description: 'description', - mode: {type: 'NFT'}, - name: 'name', - tokenPrefix: 'prefix', -}; - -export async function -createCollection( - api: ApiPromise, - sender: IKeyringPair, - params: Partial = {}, -): Promise { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - let modeprm = {}; - if (mode.type === 'NFT') { - modeprm = {nft: null}; - } else if (mode.type === 'Fungible') { - modeprm = {fungible: mode.decimalPoints}; - } else if (mode.type === 'ReFungible') { - modeprm = {refungible: null}; - } - - const tx = api.tx.unique.createCollectionEx({ - name: strToUTF16(name), - description: strToUTF16(description), - tokenPrefix: strToUTF16(tokenPrefix), - mode: modeprm as any, - }); - const events = await executeTransaction(api, sender, tx); - return getCreateCollectionResult(events); -} - -export async function createCollectionExpectSuccess(params: Partial = {}): Promise { - const {name, description, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - let collectionId = 0; - await usingApi(async (api, privateKeyWrapper) => { - // Get number of collections before the transaction - const collectionCountBefore = await getCreatedCollectionCount(api); - - // Run the CreateCollection transaction - const alicePrivateKey = privateKeyWrapper('//Alice'); - - const result = await createCollection(api, alicePrivateKey, params); - - // Get number of collections after the transaction - const collectionCountAfter = await getCreatedCollectionCount(api); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, result.collectionId); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - expect(result.collectionId).to.be.equal(collectionCountAfter); - // tslint:disable-next-line:no-unused-expression - expect(collection).to.be.not.null; - expect(collectionCountAfter).to.be.equal(collectionCountBefore + 1, 'Error: NFT collection NOT created.'); - expect(collection.owner.toString()).to.be.equal(toSubstrateAddress(alicePrivateKey)); - expect(utf16ToStr(collection.name.toJSON() as any)).to.be.equal(name); - expect(utf16ToStr(collection.description.toJSON() as any)).to.be.equal(description); - expect(hexToStr(collection.tokenPrefix.toJSON())).to.be.equal(tokenPrefix); - - collectionId = result.collectionId; - }); - - return collectionId; -} - -export async function createCollectionWithPropsExpectSuccess(params: Partial = {}): Promise { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - let collectionId = 0; - await usingApi(async (api, privateKeyWrapper) => { - // Get number of collections before the transaction - const collectionCountBefore = await getCreatedCollectionCount(api); - - // Run the CreateCollection transaction - const alicePrivateKey = privateKeyWrapper('//Alice'); - - let modeprm = {}; - if (mode.type === 'NFT') { - modeprm = {nft: null}; - } else if (mode.type === 'Fungible') { - modeprm = {fungible: mode.decimalPoints}; - } else if (mode.type === 'ReFungible') { - modeprm = {refungible: null}; - } - - const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm}); - const events = await submitTransactionAsync(alicePrivateKey, tx); - const result = getCreateCollectionResult(events); - - // Get number of collections after the transaction - const collectionCountAfter = await getCreatedCollectionCount(api); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, result.collectionId); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - expect(result.collectionId).to.be.equal(collectionCountAfter); - // tslint:disable-next-line:no-unused-expression - expect(collection).to.be.not.null; - expect(collectionCountAfter).to.be.equal(collectionCountBefore + 1, 'Error: NFT collection NOT created.'); - expect(collection.owner.toString()).to.be.equal(toSubstrateAddress(alicePrivateKey)); - expect(utf16ToStr(collection.name.toJSON() as any)).to.be.equal(name); - expect(utf16ToStr(collection.description.toJSON() as any)).to.be.equal(description); - expect(hexToStr(collection.tokenPrefix.toJSON())).to.be.equal(tokenPrefix); - - - collectionId = result.collectionId; - }); - - return collectionId; -} - -export async function createCollectionWithPropsExpectFailure(params: Partial = {}) { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - await usingApi(async (api, privateKeyWrapper) => { - // Get number of collections before the transaction - const collectionCountBefore = await getCreatedCollectionCount(api); - - // Run the CreateCollection transaction - const alicePrivateKey = privateKeyWrapper('//Alice'); - - let modeprm = {}; - if (mode.type === 'NFT') { - modeprm = {nft: null}; - } else if (mode.type === 'Fungible') { - modeprm = {fungible: mode.decimalPoints}; - } else if (mode.type === 'ReFungible') { - modeprm = {refungible: null}; - } - - const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm}); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - - - // Get number of collections after the transaction - const collectionCountAfter = await getCreatedCollectionCount(api); - - expect(collectionCountAfter).to.be.equal(collectionCountBefore, 'Error: Collection with incorrect data created.'); - }); -} - -export async function createCollectionExpectFailure(params: Partial = {}) { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - let modeprm = {}; - if (mode.type === 'NFT') { - modeprm = {nft: null}; - } else if (mode.type === 'Fungible') { - modeprm = {fungible: mode.decimalPoints}; - } else if (mode.type === 'ReFungible') { - modeprm = {refungible: null}; - } - - await usingApi(async (api, privateKeyWrapper) => { - // Get number of collections before the transaction - const collectionCountBefore = await getCreatedCollectionCount(api); - - // Run the CreateCollection transaction - const alicePrivateKey = privateKeyWrapper('//Alice'); - const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any}); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - - // Get number of collections after the transaction - const collectionCountAfter = await getCreatedCollectionCount(api); - - // What to expect - expect(collectionCountAfter).to.be.equal(collectionCountBefore, 'Error: Collection with incorrect data created.'); - }); -} - -export async function findUnusedAddress(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair, seedAddition = ''): Promise { - let bal = 0n; - let unused; - do { - const randomSeed = 'seed' + Math.floor(Math.random() * Math.floor(10000)) + seedAddition; - unused = privateKeyWrapper(`//${randomSeed}`); - bal = (await api.query.system.account(unused.address)).data.free.toBigInt(); - } while (bal !== 0n); - return unused; -} - -export async function getAllowance(api: ApiPromise, collectionId: number, owner: CrossAccountId | string | IKeyringPair, approved: CrossAccountId | string | IKeyringPair, tokenId: number) { - return (await api.rpc.unique.allowance(collectionId, normalizeAccountId(owner), normalizeAccountId(approved), tokenId)).toBigInt(); -} - -export function findUnusedAddresses(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair, amount: number): Promise { - return Promise.all(new Array(amount).fill(null).map(() => findUnusedAddress(api, privateKeyWrapper, '_' + Date.now()))); -} - -export async function findNotExistingCollection(api: ApiPromise): Promise { - const totalNumber = await getCreatedCollectionCount(api); - const newCollection: number = totalNumber + 1; - return newCollection; -} - -function getDestroyResult(events: EventRecord[]): boolean { - let success = false; - events.forEach(({event: {method}}) => { - if (method == 'ExtrinsicSuccess') { - success = true; - } - }); - return success; -} - -export async function destroyCollectionExpectFailure(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - // Run the DestroyCollection transaction - const alicePrivateKey = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.destroyCollection(collectionId); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - }); -} - -export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - // Run the DestroyCollection transaction - const alicePrivateKey = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.destroyCollection(collectionId); - const events = await submitTransactionAsync(alicePrivateKey, tx); - const result = getDestroyResult(events); - expect(result).to.be.true; - - // What to expect - expect(await getDetailedCollectionInfo(api, collectionId)).to.be.null; - }); -} - -export async function setCollectionLimitsExpectSuccess(sender: IKeyringPair, collectionId: number, limits: any) { - await usingApi(async (api) => { - const tx = api.tx.unique.setCollectionLimits(collectionId, limits); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export const setCollectionPermissionsExpectSuccess = async (sender: IKeyringPair, collectionId: number, permissions: any) => { - await usingApi(async(api) => { - const tx = api.tx.unique.setCollectionPermissions(collectionId, permissions); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -}; - -export async function setCollectionLimitsExpectFailure(sender: IKeyringPair, collectionId: number, limits: any) { - await usingApi(async (api) => { - const tx = api.tx.unique.setCollectionLimits(collectionId, limits); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string, sender = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const senderPrivateKey = privateKeyWrapper(sender); - const tx = api.tx.unique.setCollectionSponsor(collectionId, sponsor); - const events = await submitTransactionAsync(senderPrivateKey, tx); - const result = getGenericResult(events); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - // What to expect - expect(result.success).to.be.true; - expect(collection.sponsorship.toJSON()).to.deep.equal({ - unconfirmed: sponsor, - }); - }); -} - -export async function removeCollectionSponsorExpectSuccess(collectionId: number, sender = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const alicePrivateKey = privateKeyWrapper(sender); - const tx = api.tx.unique.removeCollectionSponsor(collectionId); - const events = await submitTransactionAsync(alicePrivateKey, tx); - const result = getGenericResult(events); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - // What to expect - expect(result.success).to.be.true; - expect(collection.sponsorship.toJSON()).to.be.deep.equal({disabled: null}); - }); -} - -export async function removeCollectionSponsorExpectFailure(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const alicePrivateKey = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.removeCollectionSponsor(collectionId); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - }); -} - -export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const alicePrivateKey = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.setCollectionSponsor(collectionId, sponsor); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - }); -} - -export async function confirmSponsorshipExpectSuccess(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const sender = privateKeyWrapper(senderSeed); - await confirmSponsorshipByKeyExpectSuccess(collectionId, sender); - }); -} - -export async function confirmSponsorshipByKeyExpectSuccess(collectionId: number, sender: IKeyringPair) { - await usingApi(async (api) => { - - // Run the transaction - const tx = api.tx.unique.confirmSponsorship(collectionId); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - // What to expect - expect(result.success).to.be.true; - expect(collection.sponsorship.toJSON()).to.be.deep.equal({ - confirmed: sender.address, - }); - }); -} - - -export async function confirmSponsorshipExpectFailure(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const sender = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.confirmSponsorship(collectionId); - await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - }); -} - -export async function enableContractSponsoringExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) { - await usingApi(async (api) => { - const tx = api.tx.unique.enableContractSponsoring(contractAddress, enable); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function enableContractSponsoringExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) { - await usingApi(async (api) => { - const tx = api.tx.unique.enableContractSponsoring(contractAddress, enable); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function setTransferFlagExpectSuccess(sender: IKeyringPair, collectionId: number, enabled: boolean) { - - await usingApi(async (api) => { - - const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function setTransferFlagExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) { - - await usingApi(async (api) => { - - const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function setContractSponsoringRateLimitExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) { - await usingApi(async (api) => { - const tx = api.tx.unique.setContractSponsoringRateLimit(contractAddress, rateLimit); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function setContractSponsoringRateLimitExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) { - await usingApi(async (api) => { - const tx = api.tx.unique.setContractSponsoringRateLimit(contractAddress, rateLimit); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function getNextSponsored( - api: ApiPromise, - collectionId: number, - account: string | CrossAccountId, - tokenId: number, -): Promise { - return Number((await api.rpc.unique.nextSponsored(collectionId, account, tokenId)).unwrapOr(-1)); -} - -export async function toggleContractAllowlistExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, value = true) { - await usingApi(async (api) => { - const tx = api.tx.unique.toggleContractAllowList(contractAddress, value); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function isAllowlistedInContract(contractAddress: AccountId | string, user: string) { - let allowlisted = false; - await usingApi(async (api) => { - allowlisted = (await api.query.unique.contractAllowList(contractAddress, user)).toJSON() as boolean; - }); - return allowlisted; -} - -export async function addToContractAllowListExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) { - await usingApi(async (api) => { - const tx = api.tx.unique.addToContractAllowList(contractAddress.toString(), user.toString()); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function removeFromContractAllowListExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) { - await usingApi(async (api) => { - const tx = api.tx.unique.removeFromContractAllowList(contractAddress.toString(), user.toString()); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function removeFromContractAllowListExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) { - await usingApi(async (api) => { - const tx = api.tx.unique.removeFromContractAllowList(contractAddress.toString(), user.toString()); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export interface CreateFungibleData { - readonly Value: bigint; -} - -export interface CreateReFungibleData { } -export interface CreateNftData { } - -export type CreateItemData = { - NFT: CreateNftData; -} | { - Fungible: CreateFungibleData; -} | { - ReFungible: CreateReFungibleData; -}; - -export async function burnItem(api: ApiPromise, sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint) : Promise { - const tx = api.tx.unique.burnItem(collectionId, tokenId, value); - const events = await submitTransactionAsync(sender, tx); - return getGenericResult(events).success; -} - -export async function burnItemExpectSuccess(sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint = 1) { - await usingApi(async (api) => { - const balanceBefore = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId); - // if burning token by admin - use adminButnItemExpectSuccess - expect(balanceBefore >= BigInt(value)).to.be.true; - - expect(await burnItem(api, sender, collectionId, tokenId, value)).to.be.true; - - const balanceAfter = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId); - expect(balanceAfter + BigInt(value)).to.be.equal(balanceBefore); - }); -} - -export async function burnItemExpectFailure(sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint = 1) { - await usingApi(async (api) => { - const tx = api.tx.unique.burnItem(collectionId, tokenId, value); - - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function burnFromExpectSuccess(sender: IKeyringPair, from: IKeyringPair | CrossAccountId, collectionId: number, tokenId: number, value: number | bigint = 1) { - await usingApi(async (api) => { - const tx = api.tx.unique.burnFrom(collectionId, normalizeAccountId(from), tokenId, value); - const events = await submitTransactionAsync(sender, tx); - return getGenericResult(events).success; - }); -} - -export async function -approve( - api: ApiPromise, - collectionId: number, - tokenId: number, owner: IKeyringPair, approved: CrossAccountId | string | IKeyringPair, amount: number | bigint, -) { - const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); - const events = await submitTransactionAsync(owner, approveUniqueTx); - return getGenericResult(events).success; -} - -export async function -approveExpectSuccess( - collectionId: number, - tokenId: number, owner: IKeyringPair, approved: CrossAccountId | string, amount: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const result = await approve(api, collectionId, tokenId, owner, approved, amount); - expect(result).to.be.true; - - expect(await getAllowance(api, collectionId, owner, approved, tokenId)).to.be.equal(BigInt(amount)); - }); -} - -export async function adminApproveFromExpectSuccess( - collectionId: number, - tokenId: number, admin: IKeyringPair, owner: CrossAccountId | string, approved: CrossAccountId | string, amount: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); - const events = await submitTransactionAsync(admin, approveUniqueTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - expect(await getAllowance(api, collectionId, owner, approved, tokenId)).to.be.equal(BigInt(amount)); - }); -} - -export async function -transferFrom( - api: ApiPromise, - collectionId: number, - tokenId: number, - accountApproved: IKeyringPair, - accountFrom: IKeyringPair | CrossAccountId, - accountTo: IKeyringPair | CrossAccountId, - value: number | bigint, -) { - const from = normalizeAccountId(accountFrom); - const to = normalizeAccountId(accountTo); - const transferFromTx = api.tx.unique.transferFrom(from, to, collectionId, tokenId, value); - const events = await submitTransactionAsync(accountApproved, transferFromTx); - return getGenericResult(events).success; -} - -export async function -transferFromExpectSuccess( - collectionId: number, - tokenId: number, - accountApproved: IKeyringPair, - accountFrom: IKeyringPair | CrossAccountId, - accountTo: IKeyringPair | CrossAccountId, - value: number | bigint = 1, - type = 'NFT', -) { - await usingApi(async (api: ApiPromise) => { - const from = normalizeAccountId(accountFrom); - const to = normalizeAccountId(accountTo); - let balanceBefore = 0n; - if (type === 'Fungible' || type === 'ReFungible') { - balanceBefore = await getBalance(api, collectionId, to, tokenId); - } - expect(await transferFrom(api, collectionId, tokenId, accountApproved, accountFrom, accountTo, value)).to.be.true; - if (type === 'NFT') { - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to); - } - if (type === 'Fungible') { - const balanceAfter = await getBalance(api, collectionId, to, tokenId); - if (JSON.stringify(to) !== JSON.stringify(from)) { - expect(balanceAfter - balanceBefore).to.be.equal(BigInt(value)); - } else { - expect(balanceAfter).to.be.equal(balanceBefore); - } - } - if (type === 'ReFungible') { - expect(await getBalance(api, collectionId, to, tokenId)).to.be.equal(balanceBefore + BigInt(value)); - } - }); -} - -export async function -transferFromExpectFail( - collectionId: number, - tokenId: number, - accountApproved: IKeyringPair, - accountFrom: IKeyringPair, - accountTo: IKeyringPair, - value: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const transferFromTx = api.tx.unique.transferFrom(normalizeAccountId(accountFrom.address), normalizeAccountId(accountTo.address), collectionId, tokenId, value); - const events = await expect(submitTransactionExpectFailAsync(accountApproved, transferFromTx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -/* eslint no-async-promise-executor: "off" */ -export async function getBlockNumber(api: ApiPromise): Promise { - return new Promise(async (resolve) => { - const unsubscribe = await api.rpc.chain.subscribeNewHeads((head) => { - unsubscribe(); - resolve(head.number.toNumber()); - }); - }); -} - -export async function addCollectionAdminExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | CrossAccountId) { - await usingApi(async (api) => { - const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(address)); - const events = await submitTransactionAsync(sender, changeAdminTx); - const result = getCreateCollectionResult(events); - expect(result.success).to.be.true; - }); -} - -export async function adminApproveFromExpectFail( - collectionId: number, - tokenId: number, admin: IKeyringPair, owner: CrossAccountId | string, approved: CrossAccountId | string, amount: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); - const events = await expect(submitTransactionAsync(admin, approveUniqueTx)).to.be.rejected; - const result = getGenericResult(events); - expect(result.success).to.be.false; - }); -} - -export async function -getFreeBalance(account: IKeyringPair): Promise { - let balance = 0n; - await usingApi(async (api) => { - balance = BigInt((await api.query.system.account(account.address)).data.free.toString()); - }); - - return balance; -} - -export async function paraSiblingSovereignAccount(paraid: number): Promise { - return usingApi(async api => { - // We are getting a *sibling* parachain sovereign account, - // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c - const siblingPrefix = '0x7369626c'; - - const encodedParaId = api.createType('u32', paraid).toHex(true).substring(2); - const suffix = '000000000000000000000000000000000000000000000000'; - - return siblingPrefix + encodedParaId + suffix; - }); -} - -export async function transferBalanceTo(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) { - const tx = api.tx.balances.transfer(target, amount); - const events = await submitTransactionAsync(source, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; -} - -export async function -scheduleExpectSuccess( - operationTx: any, - sender: IKeyringPair, - blockSchedule: number, - scheduledId: string, - period = 1, - repetitions = 1, -) { - await usingApi(async (api: ApiPromise) => { - const blockNumber: number | undefined = await getBlockNumber(api); - const expectedBlockNumber = blockNumber + blockSchedule; - - expect(blockNumber).to.be.greaterThan(0); - const scheduleTx = api.tx.scheduler.scheduleNamed( // schedule - scheduledId, - expectedBlockNumber, - repetitions > 1 ? [period, repetitions] : null, - 0, - {Value: operationTx as any}, - ); - - const events = await submitTransactionAsync(sender, scheduleTx); - expect(getGenericResult(events).success).to.be.true; - }); -} - -export async function -scheduleExpectFailure( - operationTx: any, - sender: IKeyringPair, - blockSchedule: number, - scheduledId: string, - period = 1, - repetitions = 1, -) { - await usingApi(async (api: ApiPromise) => { - const blockNumber: number | undefined = await getBlockNumber(api); - const expectedBlockNumber = blockNumber + blockSchedule; - - expect(blockNumber).to.be.greaterThan(0); - const scheduleTx = api.tx.scheduler.scheduleNamed( // schedule - scheduledId, - expectedBlockNumber, - repetitions <= 1 ? null : [period, repetitions], - 0, - {Value: operationTx as any}, - ); - - //const events = - await expect(submitTransactionExpectFailAsync(sender, scheduleTx)).to.be.rejected; - //expect(getGenericResult(events).success).to.be.false; - }); -} - -export async function -scheduleTransferAndWaitExpectSuccess( - collectionId: number, - tokenId: number, - sender: IKeyringPair, - recipient: IKeyringPair, - value: number | bigint = 1, - blockSchedule: number, - scheduledId: string, -) { - await usingApi(async (api: ApiPromise) => { - await scheduleTransferExpectSuccess(collectionId, tokenId, sender, recipient, value, blockSchedule, scheduledId); - - const recipientBalanceBefore = (await api.query.system.account(recipient.address)).data.free.toBigInt(); - - // sleep for n + 1 blocks - await waitNewBlocks(blockSchedule + 1); - - const recipientBalanceAfter = (await api.query.system.account(recipient.address)).data.free.toBigInt(); - - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(recipient.address)); - expect(recipientBalanceAfter).to.be.equal(recipientBalanceBefore); - }); -} - -export async function -scheduleTransferExpectSuccess( - collectionId: number, - tokenId: number, - sender: IKeyringPair, - recipient: IKeyringPair, - value: number | bigint = 1, - blockSchedule: number, - scheduledId: string, -) { - await usingApi(async (api: ApiPromise) => { - const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value); - - await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId); - - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(sender.address)); - }); -} - -export async function -scheduleTransferFundsPeriodicExpectSuccess( - amount: bigint, - sender: IKeyringPair, - recipient: IKeyringPair, - blockSchedule: number, - scheduledId: string, - period: number, - repetitions: number, -) { - await usingApi(async (api: ApiPromise) => { - const transferTx = api.tx.balances.transfer(recipient.address, amount); - - const balanceBefore = await getFreeBalance(recipient); - - await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId, period, repetitions); - - expect(await getFreeBalance(recipient)).to.be.equal(balanceBefore); - }); -} - -export async function -transfer( - api: ApiPromise, - collectionId: number, - tokenId: number, - sender: IKeyringPair, - recipient: IKeyringPair | CrossAccountId, - value: number | bigint, -) : Promise { - const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); - const events = await executeTransaction(api, sender, transferTx); - return getGenericResult(events).success; -} - -export async function -transferExpectSuccess( - collectionId: number, - tokenId: number, - sender: IKeyringPair, - recipient: IKeyringPair | CrossAccountId, - value: number | bigint = 1, - type = 'NFT', -) { - await usingApi(async (api: ApiPromise) => { - const from = normalizeAccountId(sender); - const to = normalizeAccountId(recipient); - - let balanceBefore = 0n; - if (type === 'Fungible' || type === 'ReFungible') { - balanceBefore = await getBalance(api, collectionId, to, tokenId); - } - - const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); - const events = await executeTransaction(api, sender, transferTx); - const result = getTransferResult(api, events); - - expect(result.collectionId).to.be.equal(collectionId); - expect(result.itemId).to.be.equal(tokenId); - expect(result.sender).to.be.deep.equal(normalizeAccountId(sender.address)); - expect(result.recipient).to.be.deep.equal(to); - expect(result.value).to.be.equal(BigInt(value)); - - if (type === 'NFT') { - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to); - } - if (type === 'Fungible' || type === 'ReFungible') { - const balanceAfter = await getBalance(api, collectionId, to, tokenId); - if (JSON.stringify(to) !== JSON.stringify(from)) { - expect(balanceAfter - balanceBefore).to.be.equal(BigInt(value)); - } else { - expect(balanceAfter).to.be.equal(balanceBefore); - } - } - }); -} - -export async function -transferExpectFailure( - collectionId: number, - tokenId: number, - sender: IKeyringPair, - recipient: IKeyringPair | CrossAccountId, - value: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); - const events = await expect(submitTransactionExpectFailAsync(sender, transferTx)).to.be.rejected; - const result = getGenericResult(events); - // if (events && Array.isArray(events)) { - // const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - //} - }); -} - -export async function -approveExpectFail( - collectionId: number, - tokenId: number, owner: IKeyringPair, approved: IKeyringPair, amount: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved.address), collectionId, tokenId, amount); - const events = await expect(submitTransactionExpectFailAsync(owner, approveUniqueTx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function getBalance( - api: ApiPromise, - collectionId: number, - owner: string | CrossAccountId | IKeyringPair, - token: number, -): Promise { - return (await api.rpc.unique.balance(collectionId, normalizeAccountId(owner), token)).toBigInt(); -} -export async function getTokenOwner( - api: ApiPromise, - collectionId: number, - token: number, -): Promise { - const owner = (await api.rpc.unique.tokenOwner(collectionId, token)).toJSON() as any; - if (owner == null) throw new Error('owner == null'); - return normalizeAccountId(owner); -} -export async function getTopmostTokenOwner( - api: ApiPromise, - collectionId: number, - token: number, -): Promise { - const owner = (await api.rpc.unique.topmostTokenOwner(collectionId, token)).toJSON() as any; - if (owner == null) throw new Error('owner == null'); - return normalizeAccountId(owner); -} -export async function getTokenChildren( - api: ApiPromise, - collectionId: number, - tokenId: number, -): Promise { - return (await api.rpc.unique.tokenChildren(collectionId, tokenId)).toJSON() as any; -} -export async function isTokenExists( - api: ApiPromise, - collectionId: number, - token: number, -): Promise { - return (await api.rpc.unique.tokenExists(collectionId, token)).toJSON(); -} -export async function getLastTokenId( - api: ApiPromise, - collectionId: number, -): Promise { - return (await api.rpc.unique.lastTokenId(collectionId)).toJSON(); -} -export async function getAdminList( - api: ApiPromise, - collectionId: number, -): Promise { - return (await api.rpc.unique.adminlist(collectionId)).toHuman() as any; -} -export async function getTokenProperties( - api: ApiPromise, - collectionId: number, - tokenId: number, - propertyKeys: string[], -): Promise { - return (await api.rpc.unique.tokenProperties(collectionId, tokenId, propertyKeys)).toHuman() as any; -} - -export async function createFungibleItemExpectSuccess( - sender: IKeyringPair, - collectionId: number, - data: CreateFungibleData, - owner: CrossAccountId | string = sender.address, -) { - return await usingApi(async (api) => { - const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), {Fungible: data}); - - const events = await submitTransactionAsync(sender, tx); - const result = getCreateItemResult(events); - - expect(result.success).to.be.true; - return result.itemId; - }); -} - -export async function createMultipleItemsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) { - await usingApi(async (api) => { - const to = normalizeAccountId(owner); - const tx = api.tx.unique.createMultipleItems(collectionId, to, itemsData); - - const events = await submitTransactionAsync(sender, tx); - expect(getGenericResult(events).success).to.be.true; - }); -} - -export async function createMultipleItemsWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) { - await usingApi(async (api) => { - const to = normalizeAccountId(owner); - const tx = api.tx.unique.createMultipleItems(collectionId, to, itemsData); - - const events = await submitTransactionAsync(sender, tx); - const result = getCreateItemsResult(events); - - for (const res of result) { - expect(await api.rpc.unique.tokenProperties(collectionId, res.itemId)).not.to.be.empty; - } - }); -} - -export async function createMultipleItemsExWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any) { - await usingApi(async (api) => { - const tx = api.tx.unique.createMultipleItemsEx(collectionId, itemsData); - - const events = await submitTransactionAsync(sender, tx); - const result = getCreateItemsResult(events); - - for (const res of result) { - expect(await api.rpc.unique.tokenProperties(collectionId, res.itemId)).not.to.be.empty; - } - }); -} - -export async function createItemWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, props: Array, owner: CrossAccountId | string = sender.address) { - let newItemId = 0; - await usingApi(async (api) => { - const to = normalizeAccountId(owner); - const itemCountBefore = await getLastTokenId(api, collectionId); - const itemBalanceBefore = await getBalance(api, collectionId, to, newItemId); - - let tx; - if (createMode === 'Fungible') { - const createData = {fungible: {value: 10}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } else if (createMode === 'ReFungible') { - const createData = {refungible: {pieces: 100}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } else { - const data = api.createType('UpDataStructsCreateItemData', {NFT: {properties: props}}); - tx = api.tx.unique.createItem(collectionId, to, data as UpDataStructsCreateItemData); - } - - const events = await submitTransactionAsync(sender, tx); - const result = getCreateItemResult(events); - - const itemCountAfter = await getLastTokenId(api, collectionId); - const itemBalanceAfter = await getBalance(api, collectionId, to, newItemId); - - if (createMode === 'NFT') { - expect(await api.rpc.unique.tokenProperties(collectionId, result.itemId)).not.to.be.empty; - } - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - if (createMode === 'Fungible') { - expect(itemBalanceAfter - itemBalanceBefore).to.be.equal(10n); - } else { - expect(itemCountAfter).to.be.equal(itemCountBefore + 1); - } - expect(collectionId).to.be.equal(result.collectionId); - expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString()); - expect(to).to.be.deep.equal(result.recipient); - newItemId = result.itemId; - }); - return newItemId; -} - -export async function createItemWithPropsExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, props: Array, owner: CrossAccountId | string = sender.address) { - await usingApi(async (api) => { - - let tx; - if (createMode === 'NFT') { - const data = api.createType('UpDataStructsCreateItemData', {NFT: {properties: props}}) as UpDataStructsCreateItemData; - tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), data); - } else { - tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode); - } - - - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - if(events.message && events.message.toString().indexOf('1002: Verification Error') > -1) return; - const result = getCreateItemResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function createItemExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) { - let newItemId = 0; - await usingApi(async (api) => { - const to = normalizeAccountId(owner); - const itemCountBefore = await getLastTokenId(api, collectionId); - const itemBalanceBefore = await getBalance(api, collectionId, to, newItemId); - - let tx; - if (createMode === 'Fungible') { - const createData = {fungible: {value: 10}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } else if (createMode === 'ReFungible') { - const createData = {refungible: {pieces: 100}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } else { - const createData = {nft: {}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } - - const events = await executeTransaction(api, sender, tx); - const result = getCreateItemResult(events); - - const itemCountAfter = await getLastTokenId(api, collectionId); - const itemBalanceAfter = await getBalance(api, collectionId, to, newItemId); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - if (createMode === 'Fungible') { - expect(itemBalanceAfter - itemBalanceBefore).to.be.equal(10n); - } else { - expect(itemCountAfter).to.be.equal(itemCountBefore + 1); - } - expect(collectionId).to.be.equal(result.collectionId); - expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString()); - expect(to).to.be.deep.equal(result.recipient); - newItemId = result.itemId; - }); - return newItemId; -} - -export async function createRefungibleToken(api: ApiPromise, sender: IKeyringPair, collectionId: number, amount: bigint, owner: CrossAccountId | IKeyringPair | string = sender.address) : Promise { - const createData = {refungible: {pieces: amount}}; - const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createData as any); - - const events = await submitTransactionAsync(sender, tx); - return getCreateItemResult(events); -} - -export async function createItemExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) { - await usingApi(async (api) => { - const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode); - - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getCreateItemResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function setPublicAccessModeExpectSuccess( - sender: IKeyringPair, collectionId: number, - accessMode: 'Normal' | 'AllowList', -) { - await usingApi(async (api) => { - - // Run the transaction - const tx = api.tx.unique.setCollectionPermissions(collectionId, {access: accessMode}); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - expect(collection.permissions.access.toHuman()).to.be.equal(accessMode); - }); -} - -export async function setPublicAccessModeExpectFail( - sender: IKeyringPair, collectionId: number, - accessMode: 'Normal' | 'AllowList', -) { - await usingApi(async (api) => { - - // Run the transaction - const tx = api.tx.unique.setCollectionPermissions(collectionId, {access: accessMode}); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function enableAllowListExpectSuccess(sender: IKeyringPair, collectionId: number) { - await setPublicAccessModeExpectSuccess(sender, collectionId, 'AllowList'); -} - -export async function enableAllowListExpectFail(sender: IKeyringPair, collectionId: number) { - await setPublicAccessModeExpectFail(sender, collectionId, 'AllowList'); -} - -export async function disableAllowListExpectSuccess(sender: IKeyringPair, collectionId: number) { - await setPublicAccessModeExpectSuccess(sender, collectionId, 'Normal'); -} - -export async function setMintPermissionExpectSuccess(sender: IKeyringPair, collectionId: number, enabled: boolean) { - await usingApi(async (api) => { - - // Run the transaction - const tx = api.tx.unique.setCollectionPermissions(collectionId, {mintMode: enabled}); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - expect(collection.permissions.mintMode.toHuman()).to.be.equal(enabled); - }); -} - -export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) { - await setMintPermissionExpectSuccess(sender, collectionId, true); -} - -export async function setMintPermissionExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) { - await usingApi(async (api) => { - // Run the transaction - const tx = api.tx.unique.setCollectionPermissions(collectionId, {mintMode: enabled}); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function setChainLimitsExpectFailure(sender: IKeyringPair, limits: IChainLimits) { - await usingApi(async (api) => { - // Run the transaction - const tx = api.tx.unique.setChainLimits(limits); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function isAllowlisted(api: ApiPromise, collectionId: number, address: string | CrossAccountId | IKeyringPair) { - return (await api.rpc.unique.allowed(collectionId, normalizeAccountId(address))).toJSON(); -} - -export async function addToAllowListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId | CrossAccountId) { - await usingApi(async (api) => { - expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.false; - - // Run the transaction - const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address)); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true; - }); -} - -export async function addToAllowListAgainExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId) { - await usingApi(async (api) => { - - expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true; - - // Run the transaction - const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address)); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true; - }); -} - -export async function addToAllowListExpectFail(sender: IKeyringPair, collectionId: number, address: string | AccountId) { - await usingApi(async (api) => { - - // Run the transaction - const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address)); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function removeFromAllowListExpectSuccess(sender: IKeyringPair, collectionId: number, address: CrossAccountId) { - await usingApi(async (api) => { - // Run the transaction - const tx = api.tx.unique.removeFromAllowList(collectionId, normalizeAccountId(address)); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - }); -} - -export async function removeFromAllowListExpectFailure(sender: IKeyringPair, collectionId: number, address: CrossAccountId) { - await usingApi(async (api) => { - // Run the transaction - const tx = api.tx.unique.removeFromAllowList(collectionId, normalizeAccountId(address)); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export const getDetailedCollectionInfo = async (api: ApiPromise, collectionId: number) - : Promise => { - return (await api.rpc.unique.collectionById(collectionId)).unwrapOr(null); -}; - -export const getCreatedCollectionCount = async (api: ApiPromise): Promise => { - // set global object - collectionsCount - return (await api.rpc.unique.collectionStats()).created.toNumber(); -}; - -export async function queryCollectionExpectSuccess(api: ApiPromise, collectionId: number): Promise { - return (await api.rpc.unique.collectionById(collectionId)).unwrap(); -} - -export async function describeXCM(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) { - (process.env.RUN_XCM_TESTS && !opts.skip - ? describe - : describe.skip)(title, fn); -} - -describeXCM.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeXCM(name, fn, {skip: true}); - -export async function waitNewBlocks(blocksCount = 1): Promise { - await usingApi(async (api) => { - const promise = new Promise(async (resolve) => { - const unsubscribe = await api.rpc.chain.subscribeNewHeads(() => { - if (blocksCount > 0) { - blocksCount--; - } else { - unsubscribe(); - resolve(); - } - }); - }); - return promise; - }); -} - -export async function waitEvent( - api: ApiPromise, - maxBlocksToWait: number, - eventSection: string, - eventMethod: string, -): Promise { - - const promise = new Promise(async (resolve) => { - const unsubscribe = await api.rpc.chain.subscribeNewHeads(async header => { - const blockNumber = header.number.toHuman(); - const blockHash = header.hash; - const eventIdStr = `${eventSection}.${eventMethod}`; - const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`; - - console.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`); - - const apiAt = await api.at(blockHash); - const eventRecords = await apiAt.query.system.events(); - - const neededEvent = eventRecords.find(r => { - return r.event.section == eventSection && r.event.method == eventMethod; - }); - - if (neededEvent) { - unsubscribe(); - resolve(neededEvent); - } else if (maxBlocksToWait > 0) { - maxBlocksToWait--; - } else { - console.log(`Event \`${eventIdStr}\` is NOT found`); - - unsubscribe(); - resolve(null); - } - }); - }); - return promise; -} - -export async function repartitionRFT( - api: ApiPromise, - collectionId: number, - sender: IKeyringPair, - tokenId: number, - amount: bigint, -): Promise { - const tx = api.tx.unique.repartition(collectionId, tokenId, amount); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - return result.success; -} - -export async function itApi(name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) { - let i: any = it; - if (opts.only) i = i.only; - else if (opts.skip) i = i.skip; - i(name, async () => { - await usingApi(async (api, privateKeyWrapper) => { - await cb({api, privateKeyWrapper}); - }); - }); -} - -itApi.only = (name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itApi(name, cb, {only: true}); -itApi.skip = (name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itApi(name, cb, {skip: true}); - -let accountSeed = 10000; -export function generateKeyringPair(keyring: Keyring) { - const privateKey = `0xDEADBEEF${(Date.now() + (accountSeed++)).toString(16).padStart(64 - 8, '0')}`; - return keyring.addFromUri(privateKey); -} - -export async function expectSubstrateEventsAtBlock(api: ApiPromise, blockNumber: AnyNumber | BlockNumber, section: string, methods: string[], dryRun = false) { - const blockHash = await api.rpc.chain.getBlockHash(blockNumber); - const subEvents = (await api.query.system.events.at(blockHash)) - .filter(x => x.event.section === section) - .map((x) => x.toHuman()); - const events = methods.map((m) => { - return { - event: { - method: m, - section, - }, - }; - }); - if (!dryRun) { - expect(subEvents).to.be.like(events); - } - return subEvents; -} --- a/tests/src/deprecated-helpers/util.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -export function strToUTF16(str: string): any { - const buf: number[] = []; - for (let i=0, strLen=str.length; i < strLen; i++) { - buf.push(str.charCodeAt(i)); - } - return buf; -} - -export function utf16ToStr(buf: number[]): string { - let str = ''; - for (let i=0, strLen=buf.length; i < strLen; i++) { - if (buf[i] != 0) str += String.fromCharCode(buf[i]); - else break; - } - return str; -} - -export function hexToStr(buf: string): string { - let str = ''; - let hexStart = buf.indexOf('0x'); - if (hexStart < 0) hexStart = 0; - else hexStart = 2; - for (let i=hexStart, strLen=buf.length; i < strLen; i+=2) { - const ch = buf[i] + buf[i+1]; - const num = parseInt(ch, 16); - if (num != 0) str += String.fromCharCode(num); - else break; - } - return str; -} --- a/tests/src/eth/util/playgrounds/unique.dev.ts +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -361,6 +361,7 @@ } clearApi() { + super.clearApi(); this.web3 = null; } --- a/tests/src/rmrk/acceptNft.seqtest.ts +++ b/tests/src/rmrk/acceptNft.seqtest.ts @@ -7,8 +7,7 @@ acceptNft, } from './util/tx'; import {NftIdTuple} from './util/fetch'; -import {isNftChildOfAnother, expectTxFailure} from './util/helpers'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; +import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers'; describe('integration test: accept NFT', () => { let api: any; @@ -104,5 +103,5 @@ expect(isChild).to.be.false; }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/addResource.seqtest.ts +++ b/tests/src/rmrk/addResource.seqtest.ts @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; import {NftIdTuple} from './util/fetch'; -import {expectTxFailure, getResourceById} from './util/helpers'; +import {expectTxFailure, getResourceById, requirePallets, Pallets} from './util/helpers'; import { addNftBasicResource, acceptNftResource, @@ -12,7 +12,6 @@ addNftComposableResource, } from './util/tx'; import {RmrkTraitsResourceResourceInfo as ResourceInfo} from '@polkadot/types/lookup'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; describe('integration test: add NFT resource', () => { const alice = '//Alice'; @@ -433,6 +432,6 @@ after(() => { - api.disconnect(); + after(async() => { await api.disconnect(); }); }); }); --- a/tests/src/rmrk/addTheme.seqtest.ts +++ b/tests/src/rmrk/addTheme.seqtest.ts @@ -1,9 +1,8 @@ import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; import {createBase, addTheme} from './util/tx'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import {getThemeNames} from './util/fetch'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; describe('integration test: add Theme to Base', () => { let api: any; @@ -126,5 +125,5 @@ await expectTxFailure(/rmrkEquip\.PermissionError/, tx); }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/burnNft.seqtest.ts +++ b/tests/src/rmrk/burnNft.seqtest.ts @@ -1,11 +1,10 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import {NftIdTuple, getChildren} from './util/fetch'; import {burnNft, createCollection, sendNft, mintNft} from './util/tx'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; chai.use(chaiAsPromised); const expect = chai.expect; @@ -167,7 +166,5 @@ }); }); - after(() => { - api.disconnect(); - }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/changeCollectionIssuer.seqtest.ts +++ b/tests/src/rmrk/changeCollectionIssuer.seqtest.ts @@ -1,6 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import { changeIssuer, createCollection, @@ -50,7 +49,5 @@ }); }); - after(() => { - api.disconnect(); - }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/createBase.seqtest.ts +++ b/tests/src/rmrk/createBase.seqtest.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; +import {requirePallets, Pallets} from './util/helpers'; import {createCollection, createBase} from './util/tx'; describe('integration test: create new Base', () => { @@ -84,5 +84,5 @@ ]); }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/createCollection.seqtest.ts +++ b/tests/src/rmrk/createCollection.seqtest.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; +import {requirePallets, Pallets} from './util/helpers'; import {createCollection} from './util/tx'; describe('Integration test: create new collection', () => { @@ -21,5 +21,5 @@ await createCollection(api, alice, 'no-limit-metadata', null, 'no-limit-symbol'); }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/deleteCollection.seqtest.ts +++ b/tests/src/rmrk/deleteCollection.seqtest.ts @@ -1,6 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import {createCollection, deleteCollection} from './util/tx'; describe('integration test: delete collection', () => { @@ -43,7 +42,5 @@ }); }); - after(() => { - api.disconnect(); - }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/equipNft.seqtest.ts +++ b/tests/src/rmrk/equipNft.seqtest.ts @@ -1,9 +1,8 @@ import {ApiPromise} from '@polkadot/api'; import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {getNft, NftIdTuple} from './util/fetch'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import { addNftComposableResource, addNftSlotResource, @@ -337,7 +336,5 @@ await expectTxFailure(/rmrkEquip\.CollectionNotEquippable/, tx); }); - after(() => { - api.disconnect(); - }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/getOwnedNfts.seqtest.ts +++ b/tests/src/rmrk/getOwnedNfts.seqtest.ts @@ -1,6 +1,6 @@ import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; +import {requirePallets, Pallets} from './util/helpers'; import {getOwnedNfts} from './util/fetch'; import {mintNft, createCollection} from './util/tx'; @@ -76,5 +76,5 @@ }); }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/lockCollection.seqtest.ts +++ b/tests/src/rmrk/lockCollection.seqtest.ts @@ -1,6 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import {createCollection, lockCollection, mintNft} from './util/tx'; describe('integration test: lock collection', () => { @@ -113,7 +112,5 @@ }); }); - after(() => { - api.disconnect(); - }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/mintNft.seqtest.ts +++ b/tests/src/rmrk/mintNft.seqtest.ts @@ -1,8 +1,7 @@ import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {getNft} from './util/fetch'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import {createCollection, mintNft} from './util/tx'; describe('integration test: mint new NFT', () => { @@ -208,5 +207,5 @@ expect(nft.isSome).to.be.false; }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/rejectNft.seqtest.ts +++ b/tests/src/rmrk/rejectNft.seqtest.ts @@ -7,8 +7,7 @@ rejectNft, } from './util/tx'; import {NftIdTuple} from './util/fetch'; -import {isNftChildOfAnother, expectTxFailure} from './util/helpers'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; +import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers'; describe('integration test: reject NFT', () => { let api: any; @@ -91,5 +90,5 @@ await expectTxFailure(/rmrkCore\.CannotRejectNonPendingNft/, tx); }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/removeResource.seqtest.ts +++ b/tests/src/rmrk/removeResource.seqtest.ts @@ -1,7 +1,6 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {NftIdTuple} from './util/fetch'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import { acceptResourceRemoval, addNftBasicResource, createCollection, mintNft, removeNftResource, sendNft, } from './util/tx'; @@ -337,7 +336,5 @@ await expectTxFailure(/rmrkCore\.NoPermission/, tx); }); - after(() => { - api.disconnect(); - }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/rmrkIsolation.seqtest.ts +++ b/tests/src/rmrk/rmrkIsolation.seqtest.ts @@ -1,85 +1,80 @@ -import {expect} from 'chai'; -import usingApi, {executeTransaction} from '../substrate/substrate-api'; -import { - createCollectionExpectSuccess, - createItemExpectSuccess, - getCreateCollectionResult, - getDetailedCollectionInfo, - getGenericResult, - requirePallets, - normalizeAccountId, - Pallets, -} from '../deprecated-helpers/helpers'; +import {executeTransaction} from '../substrate/substrate-api'; import {IKeyringPair} from '@polkadot/types/types'; -import {ApiPromise} from '@polkadot/api'; -import {it} from 'mocha'; +import {itSub, expect, usingPlaygrounds, Pallets, requirePalletsOrSkip} from '../util'; +import {UniqueHelper} from '../util/playgrounds/unique'; let alice: IKeyringPair; let bob: IKeyringPair; -async function createRmrkCollection(api: ApiPromise, sender: IKeyringPair): Promise<{uniqueId: number, rmrkId: number}> { - const tx = api.tx.rmrkCore.createCollection('metadata', null, 'symbol'); - const events = await executeTransaction(api, sender, tx); +async function createRmrkCollection(helper: UniqueHelper, sender: IKeyringPair): Promise<{uniqueId: number, rmrkId: number}> { + const result = await helper.executeExtrinsic(sender, 'api.tx.rmrkCore.createCollection', ['metadata', null, 'symbol'], true); + + const uniqueId = helper.util.extractCollectionIdFromCreationResult(result); - const uniqueResult = getCreateCollectionResult(events); - const rmrkResult = getGenericResult(events, 'rmrkCore', 'CollectionCreated', (data) => { - return parseInt(data[1].toString(), 10); + let rmrkId = null; + result.result.events.forEach(({event: {data, method, section}}) => { + if ((section === 'rmrkCore') && (method === 'CollectionCreated')) { + rmrkId = parseInt(data[1].toString(), 10); + } }); + if (rmrkId === null) { + throw Error('No rmrkCore.CollectionCreated event was found!'); + } + return { - uniqueId: uniqueResult.collectionId, - rmrkId: rmrkResult.data!, + uniqueId, + rmrkId, }; } -async function createRmrkNft(api: ApiPromise, sender: IKeyringPair, collectionId: number): Promise { - const tx = api.tx.rmrkCore.mintNft( - sender.address, - collectionId, - sender.address, - null, - 'nft-metadata', +async function createRmrkNft(helper: UniqueHelper, sender: IKeyringPair, collectionId: number): Promise { + const result = await helper.executeExtrinsic( + sender, + 'api.tx.rmrkCore.mintNft', + [ + sender.address, + collectionId, + sender.address, + null, + 'nft-metadata', + true, + null, + ], true, - null, ); - const events = await executeTransaction(api, sender, tx); - const result = getGenericResult(events, 'rmrkCore', 'NftMinted', (data) => { - return parseInt(data[2].toString(), 10); + + let rmrkNftId = null; + result.result.events.forEach(({event: {data, method, section}}) => { + if ((section === 'rmrkCore') && (method === 'NftMinted')) { + rmrkNftId = parseInt(data[2].toString(), 10); + } }); - return result.data!; -} + if (rmrkNftId === null) { + throw Error('No rmrkCore.NftMinted event was found!'); + } -async function isUnique(): Promise { - return usingApi(async api => { - const chain = await api.rpc.system.chain(); - - return chain.eq('UNIQUE'); - }); + return rmrkNftId; } describe('RMRK External Integration Test', async () => { - const itRmrk = (await isUnique() ? it : it.skip); - before(async function() { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - await requirePallets(this, [Pallets.RmrkCore]); + await usingPlaygrounds(async (_helper, privateKey) => { + alice = await privateKey('//Alice'); }); }); - itRmrk('Creates a new RMRK collection that is mapped to a different ID and is tagged as external', async () => { - await usingApi(async api => { - // throwaway collection to bump last Unique collection ID to test ID mapping - await createCollectionExpectSuccess(); + itSub.ifWithPallets('Creates a new RMRK collection that is mapped to a different ID and is tagged as external', [Pallets.RmrkCore], async ({helper}) => { + // throw away collection to bump last Unique collection ID to test ID mapping + await helper.nft.mintCollection(alice, {tokenPrefix: 'unqt'}); - const collectionIds = await createRmrkCollection(api, alice); + const collectionIds = await createRmrkCollection(helper, alice); - expect(collectionIds.rmrkId).to.be.lessThan(collectionIds.uniqueId, 'collection ID mapping'); + expect(collectionIds.rmrkId).to.be.lessThan(collectionIds.uniqueId, 'collection ID mapping'); - const collection = (await getDetailedCollectionInfo(api, collectionIds.uniqueId))!; - expect(collection.readOnly.toHuman(), 'tagged external').to.be.true; - }); + const collection = (await helper.nft.getCollectionObject(collectionIds.uniqueId).getData())!; // (await getDetailedCollectionInfo(api, collectionIds.uniqueId))!; + expect(collection.raw.readOnly, 'tagged external').to.be.true; }); }); @@ -87,165 +82,144 @@ let uniqueCollectionId: number; let rmrkCollectionId: number; let rmrkNftId: number; + let normalizedAlice: {Substrate: string}; - const itRmrk = (await isUnique() ? it : it.skip); + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + bob = await privateKey('//Bob'); + normalizedAlice = {Substrate: helper.address.normalizeSubstrateToChainFormat(alice.address)}; - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + requirePalletsOrSkip(this, helper, [Pallets.RmrkCore]); - const collectionIds = await createRmrkCollection(api, alice); + const collectionIds = await createRmrkCollection(helper, alice); uniqueCollectionId = collectionIds.uniqueId; rmrkCollectionId = collectionIds.rmrkId; - rmrkNftId = await createRmrkNft(api, alice, rmrkCollectionId); + rmrkNftId = await createRmrkNft(helper, alice, rmrkCollectionId); }); }); - itRmrk('[Negative] Forbids Unique operations with an external collection, handled by dispatch_call', async () => { - await usingApi(async api => { - // Collection item creation + itSub.ifWithPallets('[Negative] Forbids Unique operations with an external collection, handled by dispatch_call', [Pallets.RmrkCore], async ({helper}) => { + // Collection item creation - const txCreateItem = api.tx.unique.createItem(uniqueCollectionId, normalizeAccountId(alice), 'NFT'); - await expect(executeTransaction(api, alice, txCreateItem), 'creating item') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.mintToken(alice, {collectionId: uniqueCollectionId, owner: {Substrate: alice.address}}), 'creating item') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txCreateMultipleItems = api.tx.unique.createMultipleItems(uniqueCollectionId, normalizeAccountId(alice), [{NFT: {}}, {NFT: {}}]); - await expect(executeTransaction(api, alice, txCreateMultipleItems), 'creating multiple') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + const txCreateMultipleItems = helper.getApi().tx.unique.createMultipleItems(uniqueCollectionId, normalizedAlice, [{NFT: {}}, {NFT: {}}]); + await expect(executeTransaction(helper.getApi(), alice, txCreateMultipleItems), 'creating multiple') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.mintMultipleTokens(alice, uniqueCollectionId, [{owner: {Substrate: alice.address}}]), 'creating multiple ex') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txCreateMultipleItemsEx = api.tx.unique.createMultipleItemsEx(uniqueCollectionId, {NFT: [{}]}); - await expect(executeTransaction(api, alice, txCreateMultipleItemsEx), 'creating multiple ex') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + // Collection properties - // Collection properties + await expect(helper.nft.setProperties(alice, uniqueCollectionId, [{key: 'a', value: '1'}, {key: 'b'}]), 'setting collection properties') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txSetCollectionProperties = api.tx.unique.setCollectionProperties(uniqueCollectionId, [{key: 'a', value: '1'}, {key: 'b'}]); - await expect(executeTransaction(api, alice, txSetCollectionProperties), 'setting collection properties') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.deleteProperties(alice, uniqueCollectionId, ['a']), 'deleting collection properties') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txDeleteCollectionProperties = api.tx.unique.deleteCollectionProperties(uniqueCollectionId, ['a']); - await expect(executeTransaction(api, alice, txDeleteCollectionProperties), 'deleting collection properties') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.setTokenPropertyPermissions(alice, uniqueCollectionId, [{key: 'a', permission: {mutable: true}}]), 'setting property permissions') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txsetTokenPropertyPermissions = api.tx.unique.setTokenPropertyPermissions(uniqueCollectionId, [{key: 'a', permission: {mutable: true}}]); - await expect(executeTransaction(api, alice, txsetTokenPropertyPermissions), 'setting property permissions') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + // NFT - // NFT + await expect(helper.nft.burnToken(alice, uniqueCollectionId, rmrkNftId, 1n), 'burning') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txBurn = api.tx.unique.burnItem(uniqueCollectionId, rmrkNftId, 1); - await expect(executeTransaction(api, alice, txBurn), 'burning').to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.burnTokenFrom(alice, uniqueCollectionId, rmrkNftId, {Substrate: alice.address}, 1n), 'burning-from') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txBurnFrom = api.tx.unique.burnFrom(uniqueCollectionId, normalizeAccountId(alice), rmrkNftId, 1); - await expect(executeTransaction(api, alice, txBurnFrom), 'burning-from').to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.transferToken(alice, uniqueCollectionId, rmrkNftId, {Substrate: bob.address}), 'transferring') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txTransfer = api.tx.unique.transfer(normalizeAccountId(bob), uniqueCollectionId, rmrkNftId, 1); - await expect(executeTransaction(api, alice, txTransfer), 'transferring').to.be.rejectedWith(/common\.CollectionIsExternal/); - - const txApprove = api.tx.unique.approve(normalizeAccountId(bob), uniqueCollectionId, rmrkNftId, 1); - await expect(executeTransaction(api, alice, txApprove), 'approving').to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.approveToken(alice, uniqueCollectionId, rmrkNftId, {Substrate: bob.address}), 'approving') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txTransferFrom = api.tx.unique.transferFrom(normalizeAccountId(alice), normalizeAccountId(bob), uniqueCollectionId, rmrkNftId, 1); - await expect(executeTransaction(api, alice, txTransferFrom), 'transferring-from').to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.transferTokenFrom(alice, uniqueCollectionId, rmrkNftId, {Substrate: alice.address}, {Substrate: bob.address}), 'transferring-from') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - // NFT properties + // NFT properties - const txSetTokenProperties = api.tx.unique.setTokenProperties(uniqueCollectionId, rmrkNftId, [{key: 'a', value: '2'}]); - await expect(executeTransaction(api, alice, txSetTokenProperties), 'setting token properties') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.setTokenProperties(alice, uniqueCollectionId, rmrkNftId, [{key: 'a', value: '2'}]), 'setting token properties') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txDeleteTokenProperties = api.tx.unique.deleteTokenProperties(uniqueCollectionId, rmrkNftId, ['a']); - await expect(executeTransaction(api, alice, txDeleteTokenProperties), 'deleting token properties') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - }); + await expect(helper.nft.deleteTokenProperties(alice, uniqueCollectionId, rmrkNftId, ['a'])) + .to.be.rejectedWith(/common\.CollectionIsExternal/); }); - itRmrk('[Negative] Forbids Unique collection operations with an external collection', async () => { - await usingApi(async api => { - const txDestroyCollection = api.tx.unique.destroyCollection(uniqueCollectionId); - await expect(executeTransaction(api, alice, txDestroyCollection), 'destroying collection') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + itSub.ifWithPallets('[Negative] Forbids Unique collection operations with an external collection', [Pallets.RmrkCore], async ({helper}) => { + await expect(helper.nft.burn(alice, uniqueCollectionId), 'destroying collection') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - // Allow list + // Allow list - const txAddAllowList = api.tx.unique.addToAllowList(uniqueCollectionId, normalizeAccountId(bob)); - await expect(executeTransaction(api, alice, txAddAllowList), 'adding to allow list') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.addToAllowList(alice, uniqueCollectionId, {Substrate: bob.address}), 'adding to allow list') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txRemoveAllowList = api.tx.unique.removeFromAllowList(uniqueCollectionId, normalizeAccountId(bob)); - await expect(executeTransaction(api, alice, txRemoveAllowList), 'removing from allowlist') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.removeFromAllowList(alice, uniqueCollectionId, {Substrate: bob.address}), 'removing from allowlist') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - // Owner / Admin / Sponsor + // Owner / Admin / Sponsor - const txChangeOwner = api.tx.unique.changeCollectionOwner(uniqueCollectionId, bob.address); - await expect(executeTransaction(api, alice, txChangeOwner), 'changing owner') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.changeOwner(alice, uniqueCollectionId, bob.address), 'changing owner') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txAddAdmin = api.tx.unique.addCollectionAdmin(uniqueCollectionId, normalizeAccountId(bob)); - await expect(executeTransaction(api, alice, txAddAdmin), 'adding admin') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.addAdmin(alice, uniqueCollectionId, {Substrate: bob.address}), 'adding admin') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txRemoveAdmin = api.tx.unique.removeCollectionAdmin(uniqueCollectionId, normalizeAccountId(bob)); - await expect(executeTransaction(api, alice, txRemoveAdmin), 'removing admin') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.removeAdmin(alice, uniqueCollectionId, {Substrate: bob.address}), 'removing admin') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txAddCollectionSponsor = api.tx.unique.setCollectionSponsor(uniqueCollectionId, bob.address); - await expect(executeTransaction(api, alice, txAddCollectionSponsor), 'setting sponsor') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.setSponsor(alice, uniqueCollectionId, bob.address), 'setting sponsor') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txConfirmCollectionSponsor = api.tx.unique.confirmSponsorship(uniqueCollectionId); - await expect(executeTransaction(api, alice, txConfirmCollectionSponsor), 'confirming sponsor') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.confirmSponsorship(alice, uniqueCollectionId), 'confirming sponsor') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txRemoveCollectionSponsor = api.tx.unique.removeCollectionSponsor(uniqueCollectionId); - await expect(executeTransaction(api, alice, txRemoveCollectionSponsor), 'removing sponsor') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - // Limits / permissions / transfers + await expect(helper.nft.removeSponsor(alice, uniqueCollectionId), 'removing sponsor') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + // Limits / permissions / transfers - const txSetTransfers = api.tx.unique.setTransfersEnabledFlag(uniqueCollectionId, true); - await expect(executeTransaction(api, alice, txSetTransfers), 'setting transfers enabled flag') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + const txSetTransfers = helper.getApi().tx.unique.setTransfersEnabledFlag(uniqueCollectionId, true); + await expect(executeTransaction(helper.getApi(), alice, txSetTransfers), 'setting transfers enabled flag') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txSetLimits = api.tx.unique.setCollectionLimits(uniqueCollectionId, {transfersEnabled: false}); - await expect(executeTransaction(api, alice, txSetLimits), 'setting collection limits') - .to.be.rejectedWith(/common\.CollectionIsExternal/); + await expect(helper.nft.setLimits(alice, uniqueCollectionId, {transfersEnabled: false}), 'setting collection limits') + .to.be.rejectedWith(/common\.CollectionIsExternal/); - const txSetPermissions = api.tx.unique.setCollectionPermissions(uniqueCollectionId, {access: 'AllowList'}); - await expect(executeTransaction(api, alice, txSetPermissions), 'setting collection permissions') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - }); + await expect(helper.nft.setPermissions(alice, uniqueCollectionId, {access: 'AllowList'}), 'setting collection permissions') + .to.be.rejectedWith(/common\.CollectionIsExternal/); }); }); describe('Negative Integration Test: Internal Collections, External Ops', async () => { let collectionId: number; let nftId: number; - - const itRmrk = (await isUnique() ? it : it.skip); before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + bob = await privateKey('//Bob'); - collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - nftId = await createItemExpectSuccess(alice, collectionId, 'NFT'); + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'iceo'}); + collectionId = collection.collectionId; + nftId = (await collection.mintToken(alice)).tokenId; }); }); - itRmrk('[Negative] Forbids RMRK operations with the internal collection and NFT (due to the lack of mapping)', async () => { - await usingApi(async api => { - const txChangeOwner = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address); - await expect(executeTransaction(api, alice, txChangeOwner), 'changing collection issuer') - .to.be.rejectedWith(/rmrkCore\.CollectionUnknown/); + itSub.ifWithPallets('[Negative] Forbids RMRK operations with the internal collection and NFT (due to the lack of mapping)', [Pallets.RmrkCore], async ({helper}) => { + const api = helper.getApi(); + + const txChangeOwner = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address); + await expect(executeTransaction(api, alice, txChangeOwner), 'changing collection issuer') + .to.be.rejectedWith(/rmrkCore\.CollectionUnknown/); - const maxBurns = 10; - const txBurnItem = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns); - await expect(executeTransaction(api, alice, txBurnItem), 'burning NFT').to.be.rejectedWith(/rmrkCore\.CollectionUnknown/); - }); + const maxBurns = 10; + const txBurnItem = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns); + await expect(executeTransaction(api, alice, txBurnItem), 'burning NFT').to.be.rejectedWith(/rmrkCore\.CollectionUnknown/); }); }); --- a/tests/src/rmrk/sendNft.seqtest.ts +++ b/tests/src/rmrk/sendNft.seqtest.ts @@ -2,8 +2,7 @@ import {getApiConnection} from '../substrate/substrate-api'; import {createCollection, mintNft, sendNft} from './util/tx'; import {NftIdTuple} from './util/fetch'; -import {isNftChildOfAnother, expectTxFailure} from './util/helpers'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; +import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers'; describe('integration test: send NFT', () => { let api: any; @@ -252,5 +251,5 @@ await expectTxFailure(/rmrkCore\.NoPermission/, tx); }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/setCollectionProperty.seqtest.ts +++ b/tests/src/rmrk/setCollectionProperty.seqtest.ts @@ -1,6 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import {createCollection, setPropertyCollection} from './util/tx'; describe('integration test: set collection property', () => { @@ -63,7 +62,5 @@ }); }); - after(() => { - api.disconnect(); - }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/setEquippableList.seqtest.ts +++ b/tests/src/rmrk/setEquippableList.seqtest.ts @@ -1,6 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import {createCollection, createBase, setEquippableList} from './util/tx'; describe("integration test: set slot's Equippable List", () => { @@ -111,5 +110,5 @@ await expectTxFailure(/rmrkEquip\.PartDoesntExist/, tx); }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/setNftProperty.seqtest.ts +++ b/tests/src/rmrk/setNftProperty.seqtest.ts @@ -1,7 +1,6 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {NftIdTuple} from './util/fetch'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import {createCollection, mintNft, sendNft, setNftProperty} from './util/tx'; describe('integration test: set NFT property', () => { @@ -85,5 +84,5 @@ await expectTxFailure(/rmrkCore\.NoPermission/, tx); }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/setResourcePriorities.seqtest.ts +++ b/tests/src/rmrk/setResourcePriorities.seqtest.ts @@ -1,6 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; -import {expectTxFailure} from './util/helpers'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; import {mintNft, createCollection, setResourcePriorities} from './util/tx'; describe('integration test: set NFT resource priorities', () => { @@ -53,5 +52,5 @@ await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); }); - after(() => { api.disconnect(); }); + after(async() => { await api.disconnect(); }); }); --- a/tests/src/rmrk/util/helpers.ts +++ b/tests/src/rmrk/util/helpers.ts @@ -10,6 +10,8 @@ import {NftIdTuple, getChildren, getOwnedNfts, getCollectionProperties, getNftProperties, getResources} from './fetch'; import chaiAsPromised from 'chai-as-promised'; import chai from 'chai'; +import {getApiConnection} from '../../substrate/substrate-api'; +import {Context} from 'mocha'; chai.use(chaiAsPromised); const expect = chai.expect; @@ -19,6 +21,46 @@ successData: T | null; } +export enum Pallets { + Inflation = 'inflation', + RmrkCore = 'rmrkcore', + RmrkEquip = 'rmrkequip', + ReFungible = 'refungible', + Fungible = 'fungible', + NFT = 'nonfungible', + Scheduler = 'scheduler', + AppPromotion = 'apppromotion', +} + +let modulesNames: any; +export function getModuleNames(api: ApiPromise): string[] { + if (typeof modulesNames === 'undefined') + modulesNames = api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()); + return modulesNames; +} + +export async function missingRequiredPallets(requiredPallets: string[]): Promise { + const api = await getApiConnection(); + const pallets = getModuleNames(api); + await api.disconnect(); + + return requiredPallets.filter(p => !pallets.includes(p)); +} + +export async function requirePallets(mocha: Context, requiredPallets: string[]) { + const missingPallets = await missingRequiredPallets(requiredPallets); + + if (missingPallets.length > 0) { + const skippingTestMsg = `\tSkipping test "${mocha.test?.title}".`; + const missingPalletsMsg = `\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; + const skipMsg = `${skippingTestMsg}\n${missingPalletsMsg}`; + + console.error('\x1b[38:5:208m%s\x1b[0m', skipMsg); + + mocha.skip(); + } +} + export function makeNftOwner(api: ApiPromise, owner: string | NftIdTuple): NftOwner { const isNftSending = (typeof owner !== 'string'); --- a/tests/src/util/index.ts +++ b/tests/src/util/index.ts @@ -8,8 +8,9 @@ import chaiAsPromised from 'chai-as-promised'; import {Context} from 'mocha'; import config from '../config'; -import '../interfaces/augment-api-events'; -import {DevUniqueHelper, SilentLogger, SilentConsole} from './playgrounds/unique.dev'; +import {ChainHelperBase} from './playgrounds/unique'; +import {ILogger} from './playgrounds/types'; +import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper} from './playgrounds/unique.dev'; chai.use(chaiAsPromised); export const expect = chai.expect; @@ -22,11 +23,11 @@ return `//Alice+${getTestHash(filename)}`; }; -export const usingPlaygrounds = async (code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename: string, ignoreFundsPresence?: boolean}) => Promise) => Promise, url: string = config.substrateUrl) => { +async function usingPlaygroundsGeneral(helperType: new(logger: ILogger) => T, url: string, code: (helper: T, privateKey: (seed: string | {filename: string, ignoreFundsPresence?: boolean}) => Promise) => Promise) { const silentConsole = new SilentConsole(); silentConsole.enable(); - const helper = new DevUniqueHelper(new SilentLogger()); + const helper = new helperType(new SilentLogger()); try { await helper.connect(url); @@ -38,7 +39,8 @@ else { const actualSeed = getTestSeed(seed.filename); let account = helper.util.fromSeed(actualSeed, ss58Format); - if (!seed.ignoreFundsPresence && await helper.balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND) { + // here's to hoping that no + if (!seed.ignoreFundsPresence && ((helper as any)['balance'] == undefined || await (helper as any).balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND)) { console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`); account = helper.util.fromSeed('//Alice', ss58Format); } @@ -51,8 +53,36 @@ await helper.disconnect(); silentConsole.disable(); } +} + +export const usingPlaygrounds = (code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename: string, ignoreFundsPresence?: boolean}) => Promise) => Promise, url: string = config.substrateUrl) => { + return usingPlaygroundsGeneral(DevUniqueHelper, url, code); +}; + +export const usingWestmintPlaygrounds = async (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => { + return usingPlaygroundsGeneral(DevWestmintHelper, url, code); +}; + +export const usingRelayPlaygrounds = async (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise) => Promise) => { + return usingPlaygroundsGeneral(DevRelayHelper, url, code); +}; + +export const usingAcalaPlaygrounds = async (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => Promise) => Promise) => { + return usingPlaygroundsGeneral(DevAcalaHelper, url, code); +}; + +export const usingKaruraPlaygrounds = async (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => Promise) => Promise) => { + return usingPlaygroundsGeneral(DevAcalaHelper, url, code); +}; + +export const usingMoonbeamPlaygrounds = async (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise) => { + return usingPlaygroundsGeneral(DevMoonbeamHelper, url, code); }; +export const usingMoonriverPlaygrounds = async (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise) => { + return usingPlaygroundsGeneral(DevMoonriverHelper, url, code); +}; + export const MINIMUM_DONOR_FUND = 100_000n; export const DONOR_FUNDING = 1_000_000n; @@ -98,3 +128,11 @@ itSubIfWithPallet.only = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSubIfWithPallet(name, required, cb, {only: true}); itSubIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSubIfWithPallet(name, required, cb, {skip: true}); itSub.ifWithPallets = itSubIfWithPallet; + +export async function describeXCM(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) { + (process.env.RUN_XCM_TESTS && !opts.skip + ? describe + : describe.skip)(title, fn); +} + +describeXCM.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeXCM(name, fn, {skip: true}); --- a/tests/src/util/playgrounds/types.ts +++ b/tests/src/util/playgrounds/types.ts @@ -172,8 +172,48 @@ }, } +export interface IForeignAssetMetadata { + name?: number | Uint8Array, + symbol?: string, + decimals?: number, + minimalBalance?: bigint, +} + +export interface MoonbeamAssetInfo { + location: any, + metadata: { + name: string, + symbol: string, + decimals: number, + isFrozen: boolean, + minimalBalance: bigint, + }, + existentialDeposit: bigint, + isSufficient: boolean, + unitsPerSecond: bigint, + numAssetsWeightHint: number, +} + +export interface AcalaAssetMetadata { + name: string, + symbol: string, + decimals: number, + minimalBalance: bigint, +} + +export interface DemocracyStandardAccountVote { + balance: bigint, + vote: { + aye: boolean, + conviction: number, + }, +} + export type TSubstrateAccount = string; export type TEthereumAccount = string; export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated'; export type TUniqueNetworks = 'opal' | 'quartz' | 'unique'; +export type TSiblingNetworkds = 'moonbeam' | 'moonriver' | 'acala' | 'karura' | 'westmint'; +export type TRelayNetworks = 'rococo' | 'westend'; +export type TNetworks = TUniqueNetworks | TSiblingNetworkds | TRelayNetworks; export type TSigner = IKeyringPair; // | 'string' --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -3,11 +3,13 @@ import {stringToU8a} from '@polkadot/util'; import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto'; -import {UniqueHelper} from './unique'; -import {ApiPromise, WsProvider} from '@polkadot/api'; +import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper} from './unique'; +import {ApiPromise, Keyring, WsProvider} from '@polkadot/api'; import * as defs from '../../interfaces/definitions'; import {IKeyringPair} from '@polkadot/types/types'; +import {EventRecord} from '@polkadot/types/interfaces'; import {ICrossAccountId} from './types'; +import {FrameSystemEventRecord} from '@polkadot/types/lookup'; export class SilentLogger { log(_msg: any, _level: any): void { } @@ -54,7 +56,6 @@ } } - export class DevUniqueHelper extends UniqueHelper { /** * Arrange methods for tests @@ -109,6 +110,47 @@ } } +export class DevRelayHelper extends RelayHelper {} + +export class DevWestmintHelper extends WestmintHelper { + wait: WaitGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevWestmintHelper; + + super(logger, options); + this.wait = new WaitGroup(this); + } +} + +export class DevMoonbeamHelper extends MoonbeamHelper { + account: MoonbeamAccountGroup; + wait: WaitGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevMoonbeamHelper; + + super(logger, options); + this.account = new MoonbeamAccountGroup(this); + this.wait = new WaitGroup(this); + } +} + +export class DevMoonriverHelper extends DevMoonbeamHelper {} + +export class DevAcalaHelper extends AcalaHelper { + wait: WaitGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevAcalaHelper; + + super(logger, options); + this.wait = new WaitGroup(this); + } +} + +export class DevKaruraHelper extends DevAcalaHelper {} + class ArrangeGroup { helper: DevUniqueHelper; @@ -252,10 +294,48 @@ } } +class MoonbeamAccountGroup { + helper: MoonbeamHelper; + + keyring: Keyring; + _alithAccount: IKeyringPair; + _baltatharAccount: IKeyringPair; + _dorothyAccount: IKeyringPair; + + constructor(helper: MoonbeamHelper) { + this.helper = helper; + + this.keyring = new Keyring({type: 'ethereum'}); + const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133'; + const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b'; + const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68'; + + this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum'); + this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum'); + this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum'); + } + + alithAccount() { + return this._alithAccount; + } + + baltatharAccount() { + return this._baltatharAccount; + } + + dorothyAccount() { + return this._dorothyAccount; + } + + create() { + return this.keyring.addFromUri(mnemonicGenerate()); + } +} + class WaitGroup { - helper: DevUniqueHelper; + helper: ChainHelperBase; - constructor(helper: DevUniqueHelper) { + constructor(helper: ChainHelperBase) { this.helper = helper; } @@ -303,6 +383,40 @@ }); }); } + + async event(maxBlocksToWait: number, eventSection: string, eventMethod: string) { + // eslint-disable-next-line no-async-promise-executor + const promise = new Promise(async (resolve) => { + const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => { + const blockNumber = header.number.toHuman(); + const blockHash = header.hash; + const eventIdStr = `${eventSection}.${eventMethod}`; + const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`; + + this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`); + + const apiAt = await this.helper.getApi().at(blockHash); + const eventRecords = (await apiAt.query.system.events()) as any; + + const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => { + return r.event.section == eventSection && r.event.method == eventMethod; + }); + + if (neededEvent) { + unsubscribe(); + resolve(neededEvent); + } else if (maxBlocksToWait > 0) { + maxBlocksToWait--; + } else { + this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`); + + unsubscribe(); + resolve(null); + } + }); + }); + return promise; + } } class AdminGroup { --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -9,7 +9,7 @@ import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types'; import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto'; import {IKeyringPair} from '@polkadot/types/types'; -import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types'; +import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, IForeignAssetMetadata, TNetworks, MoonbeamAssetInfo, DemocracyStandardAccountVote, AcalaAssetMetadata} from './types'; export class CrossAccountId implements ICrossAccountId { Substrate?: TSubstrateAccount; @@ -252,6 +252,19 @@ isSuccess = isSuccess && amount === transfer.amount; return isSuccess; } + + static bigIntToDecimals(number: bigint, decimals = 18) { + const numberStr = number.toString(); + const dotPos = numberStr.length - decimals; + + if (dotPos <= 0) { + return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr; + } else { + const intPart = numberStr.substring(0, dotPos); + const fractPart = numberStr.substring(dotPos); + return intPart + '.' + fractPart; + } + } } class UniqueEventHelper { @@ -308,19 +321,25 @@ } } -class ChainHelperBase { +export class ChainHelperBase { + helperBase: any; + transactionStatus = UniqueUtil.transactionStatus; chainLogType = UniqueUtil.chainLogType; util: typeof UniqueUtil; eventHelper: typeof UniqueEventHelper; logger: ILogger; api: ApiPromise | null; - forcedNetwork: TUniqueNetworks | null; - network: TUniqueNetworks | null; + forcedNetwork: TNetworks | null; + network: TNetworks | null; chainLog: IUniqueHelperLog[]; children: ChainHelperBase[]; + address: AddressGroup; + chain: ChainGroup; - constructor(logger?: ILogger) { + constructor(logger?: ILogger, helperBase?: any) { + this.helperBase = helperBase; + this.util = UniqueUtil; this.eventHelper = UniqueEventHelper; if (typeof logger == 'undefined') logger = this.util.getDefaultLogger(); @@ -330,8 +349,23 @@ this.network = null; this.chainLog = []; this.children = []; + this.address = new AddressGroup(this); + this.chain = new ChainGroup(this); } + clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) { + Object.setPrototypeOf(helperCls.prototype, this); + const newHelper = new helperCls(this.logger, options); + + newHelper.api = this.api; + newHelper.network = this.network; + newHelper.forceNetwork = this.forceNetwork; + + this.children.push(newHelper); + + return newHelper; + } + getApi(): ApiPromise { if(this.api === null) throw Error('API not initialized'); return this.api; @@ -341,7 +375,7 @@ this.chainLog = []; } - forceNetwork(value: TUniqueNetworks): void { + forceNetwork(value: TNetworks): void { this.forcedNetwork = value; } @@ -367,13 +401,17 @@ this.network = null; } - static async detectNetwork(api: ApiPromise): Promise { + static async detectNetwork(api: ApiPromise): Promise { const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any; + const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver']; + + if(xcmChains.indexOf(spec.specName) > -1) return spec.specName; + if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName; return 'opal'; } - static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise { + static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise { const api = new ApiPromise({provider: new WsProvider(wsEndpoint)}); await api.isReady; @@ -384,9 +422,9 @@ return network; } - static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{ + static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{ api: ApiPromise; - network: TUniqueNetworks; + network: TNetworks; }> { if(typeof network === 'undefined' || network === null) network = 'opal'; const supportedRPC = { @@ -399,6 +437,13 @@ unique: { unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc, }, + rococo: {}, + westend: {}, + moonbeam: {}, + moonriver: {}, + acala: {}, + karura: {}, + westmint: {}, }; if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint); const rpc = supportedRPC[network]; @@ -590,16 +635,16 @@ } -class HelperGroup { - helper: UniqueHelper; +class HelperGroup { + helper: T; - constructor(uniqueHelper: UniqueHelper) { + constructor(uniqueHelper: T) { this.helper = uniqueHelper; } } -class CollectionGroup extends HelperGroup { +class CollectionGroup extends HelperGroup { /** * Get number of blocks when sponsored transaction is available. * @@ -2000,7 +2045,7 @@ } -class ChainGroup extends HelperGroup { +class ChainGroup extends HelperGroup { /** * Get system properties of a chain * @example getChainProperties(); @@ -2054,10 +2099,106 @@ } } +class SubstrateBalanceGroup extends HelperGroup { + /** + * Get substrate address balance + * @param address substrate address + * @example getSubstrate("5GrwvaEF5zXb26Fz...") + * @returns amount of tokens on address + */ + async getSubstrate(address: TSubstrateAccount): Promise { + return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt(); + } + + /** + * Transfer tokens to substrate address + * @param signer keyring of signer + * @param address substrate address of a recipient + * @param amount amount of tokens to be transfered + * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise { + const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true/*, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`*/); + + let transfer = {from: null, to: null, amount: 0n} as any; + result.result.events.forEach(({event: {data, method, section}}) => { + if ((section === 'balances') && (method === 'Transfer')) { + transfer = { + from: this.helper.address.normalizeSubstrate(data[0]), + to: this.helper.address.normalizeSubstrate(data[1]), + amount: BigInt(data[2]), + }; + } + }); + const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from + && this.helper.address.normalizeSubstrate(address) === transfer.to + && BigInt(amount) === transfer.amount; + return isSuccess; + } + + /** + * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved + * @param address substrate address + * @returns + */ + async getSubstrateFull(address: TSubstrateAccount): Promise { + const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data; + return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()}; + } +} + +class EthereumBalanceGroup extends HelperGroup { + /** + * Get ethereum address balance + * @param address ethereum address + * @example getEthereum("0x9F0583DbB855d...") + * @returns amount of tokens on address + */ + async getEthereum(address: TEthereumAccount): Promise { + return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt(); + } -class BalanceGroup extends HelperGroup { + /** + * Transfer tokens to address + * @param signer keyring of signer + * @param address Ethereum address of a recipient + * @param amount amount of tokens to be transfered + * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise { + const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true); + + let transfer = {from: null, to: null, amount: 0n} as any; + result.result.events.forEach(({event: {data, method, section}}) => { + if ((section === 'balances') && (method === 'Transfer')) { + transfer = { + from: data[0].toString(), + to: data[1].toString(), + amount: BigInt(data[2]), + }; + } + }); + const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from + && address === transfer.to + && BigInt(amount) === transfer.amount; + return isSuccess; + } +} + +class BalanceGroup extends HelperGroup { + subBalanceGroup: SubstrateBalanceGroup; + ethBalanceGroup: EthereumBalanceGroup; + + constructor(helper: T) { + super(helper); + this.subBalanceGroup = new SubstrateBalanceGroup(helper); + this.ethBalanceGroup = new EthereumBalanceGroup(helper); + } + getCollectionCreationPrice(): bigint { - return 2n * this.helper.balance.getOneTokenNominal(); + return 2n * this.getOneTokenNominal(); } /** * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ). @@ -2076,7 +2217,7 @@ * @returns amount of tokens on address */ async getSubstrate(address: TSubstrateAccount): Promise { - return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt(); + return this.subBalanceGroup.getSubstrate(address); } /** @@ -2085,8 +2226,7 @@ * @returns */ async getSubstrateFull(address: TSubstrateAccount): Promise { - const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data; - return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()}; + return this.subBalanceGroup.getSubstrateFull(address); } /** @@ -2096,7 +2236,7 @@ * @returns amount of tokens on address */ async getEthereum(address: TEthereumAccount): Promise { - return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt(); + return this.ethBalanceGroup.getEthereum(address); } /** @@ -2108,27 +2248,11 @@ * @returns ```true``` if extrinsic success, otherwise ```false``` */ async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise { - const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true/*, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`*/); - - let transfer = {from: null, to: null, amount: 0n} as any; - result.result.events.forEach(({event: {data, method, section}}) => { - if ((section === 'balances') && (method === 'Transfer')) { - transfer = { - from: this.helper.address.normalizeSubstrate(data[0]), - to: this.helper.address.normalizeSubstrate(data[1]), - amount: BigInt(data[2]), - }; - } - }); - const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from - && this.helper.address.normalizeSubstrate(address) === transfer.to - && BigInt(amount) === transfer.amount; - return isSuccess; + return this.subBalanceGroup.transferToSubstrate(signer, address, amount); } } - -class AddressGroup extends HelperGroup { +class AddressGroup extends HelperGroup { /** * Normalizes the address to the specified ss58 format, by default ```42```. * @param address substrate address @@ -2170,9 +2294,20 @@ substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount { return CrossAccountId.translateSubToEth(subAddress); } + + paraSiblingSovereignAccount(paraid: number) { + // We are getting a *sibling* parachain sovereign account, + // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c + const siblingPrefix = '0x7369626c'; + + const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2); + const suffix = '000000000000000000000000000000000000000000000000'; + + return siblingPrefix + encodedParaId + suffix; + } } -class StakingGroup extends HelperGroup { +class StakingGroup extends HelperGroup { /** * Stake tokens for App Promotion * @param signer keyring of signer @@ -2258,7 +2393,7 @@ } } -class SchedulerGroup extends HelperGroup { +class SchedulerGroup extends HelperGroup { constructor(helper: UniqueHelper) { super(helper); } @@ -2314,53 +2449,281 @@ } } +class ForeignAssetsGroup extends HelperGroup { + async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) { + await this.helper.executeExtrinsic( + signer, + 'api.tx.foreignAssets.registerForeignAsset', + [ownerAddress, location, metadata], + true, + ); + } + + async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) { + await this.helper.executeExtrinsic( + signer, + 'api.tx.foreignAssets.updateForeignAsset', + [foreignAssetId, location, metadata], + true, + ); + } +} + +class XcmGroup extends HelperGroup { + palletName: string; + + constructor(helper: T, palletName: string) { + super(helper); + + this.palletName = palletName; + } + + async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true); + } +} + +class XTokensGroup extends HelperGroup { + async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true); + } + + async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true); + } + + async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true); + } +} + +class TokensGroup extends HelperGroup { + async accounts(address: string, currencyId: any) { + const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any; + return BigInt(free); + } +} + +class AssetsGroup extends HelperGroup { + async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) { + await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true); + } + + async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true); + } + + async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) { + await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true); + } + + async account(assetId: string | number, address: string) { + const accountAsset = ( + await this.helper.callRpc('api.query.assets.account', [assetId, address]) + ).toJSON()! as any; + + if (accountAsset !== null) { + return BigInt(accountAsset['balance']); + } else { + return null; + } + } +} + +class AcalaAssetRegistryGroup extends HelperGroup { + async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) { + await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true); + } +} + +class MoonbeamAssetManagerGroup extends HelperGroup { + makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) { + const apiPrefix = 'api.tx.assetManager.'; + + const registerTx = this.helper.constructApiCall( + apiPrefix + 'registerForeignAsset', + [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient], + ); + + const setUnitsTx = this.helper.constructApiCall( + apiPrefix + 'setAssetUnitsPerSecond', + [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint], + ); + + const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]); + const encodedProposal = batchCall?.method.toHex() || ''; + return encodedProposal; + } + + async assetTypeId(location: any) { + return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]); + } +} + +class MoonbeamDemocracyGroup extends HelperGroup { + async notePreimage(signer: TSigner, encodedProposal: string) { + await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true); + } + + externalProposeMajority(proposalHash: string) { + return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]); + } + + fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) { + return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); + } + + async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) { + await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true); + } +} + +class MoonbeamCollectiveGroup extends HelperGroup { + collective: string; + + constructor(helper: MoonbeamHelper, collective: string) { + super(helper); + + this.collective = collective; + } + + async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true); + } + + async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true); + } + + async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true); + } + + async proposalCount() { + return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])); + } +} + +export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase; export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper; export class UniqueHelper extends ChainHelperBase { - helperBase: any; - - chain: ChainGroup; - balance: BalanceGroup; - address: AddressGroup; + balance: BalanceGroup; collection: CollectionGroup; nft: NFTGroup; rft: RFTGroup; ft: FTGroup; staking: StakingGroup; scheduler: SchedulerGroup; + foreignAssets: ForeignAssetsGroup; + xcm: XcmGroup; + xTokens: XTokensGroup; + tokens: TokensGroup; constructor(logger?: ILogger, options: {[key: string]: any} = {}) { - super(logger); - - this.helperBase = options.helperBase ?? UniqueHelper; + super(logger, options.helperBase ?? UniqueHelper); - this.chain = new ChainGroup(this); this.balance = new BalanceGroup(this); - this.address = new AddressGroup(this); this.collection = new CollectionGroup(this); this.nft = new NFTGroup(this); this.rft = new RFTGroup(this); this.ft = new FTGroup(this); this.staking = new StakingGroup(this); this.scheduler = new SchedulerGroup(this); + this.foreignAssets = new ForeignAssetsGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); + this.xTokens = new XTokensGroup(this); + this.tokens = new TokensGroup(this); } - clone(helperCls: UniqueHelperConstructor, options: {[key: string]: any} = {}) { - Object.setPrototypeOf(helperCls.prototype, this); - const newHelper = new helperCls(this.logger, options); + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoHelper(this.helperBase); + return this.clone(SudoHelperType) as T; + } +} + +export class XcmChainHelper extends ChainHelperBase { + async connect(wsEndpoint: string, _listeners?: any): Promise { + const wsProvider = new WsProvider(wsEndpoint); + this.api = new ApiPromise({ + provider: wsProvider, + }); + await this.api.isReadyOrError; + this.network = await UniqueHelper.detectNetwork(this.api); + } +} - newHelper.api = this.api; - newHelper.network = this.network; - newHelper.forceNetwork = this.forceNetwork; +export class RelayHelper extends XcmChainHelper { + xcm: XcmGroup; - this.children.push(newHelper); + constructor(logger?: ILogger, options: {[key: string]: any} = {}) { + super(logger, options.helperBase ?? RelayHelper); - return newHelper; + this.xcm = new XcmGroup(this, 'xcmPallet'); } +} - getSudo() { +export class WestmintHelper extends XcmChainHelper { + balance: SubstrateBalanceGroup; + xcm: XcmGroup; + assets: AssetsGroup; + xTokens: XTokensGroup; + + constructor(logger?: ILogger, options: {[key: string]: any} = {}) { + super(logger, options.helperBase ?? WestmintHelper); + + this.balance = new SubstrateBalanceGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); + this.assets = new AssetsGroup(this); + this.xTokens = new XTokensGroup(this); + } +} + +export class MoonbeamHelper extends XcmChainHelper { + balance: EthereumBalanceGroup; + assetManager: MoonbeamAssetManagerGroup; + assets: AssetsGroup; + xTokens: XTokensGroup; + democracy: MoonbeamDemocracyGroup; + collective: { + council: MoonbeamCollectiveGroup, + techCommittee: MoonbeamCollectiveGroup, + }; + + constructor(logger?: ILogger, options: {[key: string]: any} = {}) { + super(logger, options.helperBase ?? MoonbeamHelper); + + this.balance = new EthereumBalanceGroup(this); + this.assetManager = new MoonbeamAssetManagerGroup(this); + this.assets = new AssetsGroup(this); + this.xTokens = new XTokensGroup(this); + this.democracy = new MoonbeamDemocracyGroup(this); + this.collective = { + council: new MoonbeamCollectiveGroup(this, 'councilCollective'), + techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'), + }; + } +} + +export class AcalaHelper extends XcmChainHelper { + balance: SubstrateBalanceGroup; + assetRegistry: AcalaAssetRegistryGroup; + xTokens: XTokensGroup; + tokens: TokensGroup; + + constructor(logger?: ILogger, options: {[key: string]: any} = {}) { + super(logger, options.helperBase ?? AcalaHelper); + + this.balance = new SubstrateBalanceGroup(this); + this.assetRegistry = new AcalaAssetRegistryGroup(this); + this.xTokens = new XTokensGroup(this); + this.tokens = new TokensGroup(this); + } + + getSudo() { // eslint-disable-next-line @typescript-eslint/naming-convention - const SudoHelperType = SudoUniqueHelper(this.helperBase); + const SudoHelperType = SudoHelper(this.helperBase); return this.clone(SudoHelperType) as T; } } @@ -2411,7 +2774,7 @@ } // eslint-disable-next-line @typescript-eslint/naming-convention -function SudoUniqueHelper(Base: T) { +function SudoHelper(Base: T) { return class extends Base { constructor(...args: any[]) { super(...args); --- a/tests/src/xcm/xcmOpal.test.ts +++ b/tests/src/xcm/xcmOpal.test.ts @@ -14,33 +14,22 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import {WsProvider} from '@polkadot/api'; -import {ApiOptions} from '@polkadot/api/types'; import {IKeyringPair} from '@polkadot/types/types'; -import usingApi, {executeTransaction} from './../substrate/substrate-api'; -import {bigIntToDecimals, describeXCM, getGenericResult, paraSiblingSovereignAccount, normalizeAccountId} from './../deprecated-helpers/helpers'; -import waitNewBlocks from './../substrate/wait-new-blocks'; -import getBalance from './../substrate/get-balance'; - - -chai.use(chaiAsPromised); -const expect = chai.expect; +import config from '../config'; +import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '../util'; const STATEMINE_CHAIN = 1000; const UNIQUE_CHAIN = 2095; -const RELAY_PORT = '9844'; -const UNIQUE_PORT = '9944'; -const STATEMINE_PORT = '9948'; +const relayUrl = config.relayUrl; +const westmintUrl = config.westmintUrl; + const STATEMINE_PALLET_INSTANCE = 50; const ASSET_ID = 100; const ASSET_METADATA_DECIMALS = 18; const ASSET_METADATA_NAME = 'USDT'; const ASSET_METADATA_DESCRIPTION = 'USDT'; -const ASSET_METADATA_MINIMAL_BALANCE = 1; +const ASSET_METADATA_MINIMAL_BALANCE = 1n; const WESTMINT_DECIMALS = 12; @@ -69,58 +58,26 @@ before(async () => { - console.log('hey babe its opal'); - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); // funds donor + await usingPlaygrounds(async (_helper, privateKey) => { + alice = await privateKey('//Alice'); + bob = await privateKey('//Bob'); // funds donor }); - const statemineApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + STATEMINE_PORT), - }; - - const uniqueApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT), - }; - - const relayApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + RELAY_PORT), - }; - - await usingApi(async (api) => { - + await usingWestmintPlaygrounds(westmintUrl, async (helper) => { // 350.00 (three hundred fifty) DOT - const fundingAmount = 3_500_000_000_000; - - const tx = api.tx.assets.create(ASSET_ID, alice.addressRaw, ASSET_METADATA_MINIMAL_BALANCE); - const events = await executeTransaction(api, alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - // set metadata - const tx2 = api.tx.assets.setMetadata(ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS); - const events2 = await executeTransaction(api, alice, tx2); - const result2 = getGenericResult(events2); - expect(result2.success).to.be.true; + const fundingAmount = 3_500_000_000_000n; - // mint some amount of asset - const tx3 = api.tx.assets.mint(ASSET_ID, alice.addressRaw, ASSET_AMOUNT); - const events3 = await executeTransaction(api, alice, tx3); - const result3 = getGenericResult(events3); - expect(result3.success).to.be.true; + await helper.assets.create(alice, ASSET_ID, alice.address, ASSET_METADATA_MINIMAL_BALANCE); + await helper.assets.setMetadata(alice, ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS); + await helper.assets.mint(alice, ASSET_ID, alice.address, ASSET_AMOUNT); // funding parachain sovereing account (Parachain: 2095) - const parachainSovereingAccount = await paraSiblingSovereignAccount(UNIQUE_CHAIN); - const tx4 = api.tx.balances.transfer(parachainSovereingAccount, fundingAmount); - const events4 = await executeTransaction(api, bob, tx4); - const result4 = getGenericResult(events4); - expect(result4.success).to.be.true; + const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN); + await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, fundingAmount); + }); - }, statemineApiOptions); - - await usingApi(async (api) => { - + await usingPlaygrounds(async (helper) => { const location = { V1: { parents: 1, @@ -145,20 +102,13 @@ decimals: ASSET_METADATA_DECIMALS, minimalBalance: ASSET_METADATA_MINIMAL_BALANCE, }; - //registerForeignAsset(owner, location, metadata) - const tx = api.tx.foreignAssets.registerForeignAsset(alice.addressRaw, location, metadata); - const sudoTx = api.tx.sudo.sudo(tx as any); - const events = await executeTransaction(api, alice, sudoTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - [balanceOpalBefore] = await getBalance(api, [alice.address]); - - }, uniqueApiOptions); + await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata); + balanceOpalBefore = await helper.balance.getSubstrate(alice.address); + }); // Providing the relay currency to the unique sender account - await usingApi(async (api) => { + await usingRelayPlaygrounds(relayUrl, async (helper) => { const destination = { V1: { parents: 0, @@ -197,31 +147,15 @@ }; const feeAssetItem = 0; + const weightLimit = 5_000_000_000; - const weightLimit = { - Limited: 5_000_000_000, - }; - - const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); - const events = await executeTransaction(api, alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - }, relayApiOptions); + await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit); + }); }); - it('Should connect and send USDT from Westmint to Opal', async () => { - - const statemineApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + STATEMINE_PORT), - }; - - const uniqueApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT), - }; - - await usingApi(async (api) => { - + itSub('Should connect and send USDT from Westmint to Opal', async ({helper}) => { + await usingWestmintPlaygrounds(westmintUrl, async (helper) => { const dest = { V1: { parents: 1, @@ -268,146 +202,105 @@ }; const feeAssetItem = 0; - - const weightLimit = { - Limited: 5000000000, - }; - - [balanceStmnBefore] = await getBalance(api, [alice.address]); + const weightLimit = 5000000000; - const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(dest, beneficiary, assets, feeAssetItem, weightLimit); - const events = await executeTransaction(api, alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + balanceStmnBefore = await helper.balance.getSubstrate(alice.address); + await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, weightLimit); - [balanceStmnAfter] = await getBalance(api, [alice.address]); + balanceStmnAfter = await helper.balance.getSubstrate(alice.address); // common good parachain take commission in it native token console.log( 'Opal to Westmint transaction fees on Westmint: %s WND', - bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, WESTMINT_DECIMALS), + helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, WESTMINT_DECIMALS), ); expect(balanceStmnBefore > balanceStmnAfter).to.be.true; - }, statemineApiOptions); + }); // ensure that asset has been delivered - await usingApi(async (api) => { - await waitNewBlocks(api, 3); - // expext collection id will be with id 1 - const free = (await api.query.fungible.balance(1, normalizeAccountId(alice.address))).toBigInt(); + await helper.wait.newBlocks(3); - [balanceOpalAfter] = await getBalance(api, [alice.address]); + // expext collection id will be with id 1 + const free = await helper.ft.getBalance(1, {Substrate: alice.address}); - // commission has not paid in USDT token - expect(free == TRANSFER_AMOUNT).to.be.true; - console.log( - 'Opal to Westmint transaction fees on Opal: %s USDT', - bigIntToDecimals(TRANSFER_AMOUNT - free), - ); - // ... and parachain native token - expect(balanceOpalAfter == balanceOpalBefore).to.be.true; - console.log( - 'Opal to Westmint transaction fees on Opal: %s WND', - bigIntToDecimals(balanceOpalAfter - balanceOpalBefore, WESTMINT_DECIMALS), - ); + balanceOpalAfter = await helper.balance.getSubstrate(alice.address); - }, uniqueApiOptions); - + // commission has not paid in USDT token + expect(free == TRANSFER_AMOUNT).to.be.true; + console.log( + 'Opal to Westmint transaction fees on Opal: %s USDT', + helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free), + ); + // ... and parachain native token + expect(balanceOpalAfter == balanceOpalBefore).to.be.true; + console.log( + 'Opal to Westmint transaction fees on Opal: %s WND', + helper.util.bigIntToDecimals(balanceOpalAfter - balanceOpalBefore, WESTMINT_DECIMALS), + ); }); - - it('Should connect and send USDT from Unique to Statemine back', async () => { - - const uniqueApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT), - }; - - const statemineApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + STATEMINE_PORT), - }; - - await usingApi(async (api) => { - const destination = { - V1: { - parents: 1, - interior: {X2: [ - { - Parachain: STATEMINE_CHAIN, - }, - { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }, - ]}, - }, - }; - const currencies: [any, bigint][] = [ - [ + itSub('Should connect and send USDT from Unique to Statemine back', async ({helper}) => { + const destination = { + V1: { + parents: 1, + interior: {X2: [ { - ForeignAssetId: 0, + Parachain: STATEMINE_CHAIN, }, - //10_000_000_000_000_000n, - TRANSFER_AMOUNT, - ], - [ { - NativeAssetId: 'Parent', + AccountId32: { + network: 'Any', + id: alice.addressRaw, + }, }, - 400_000_000_000_000n, - ], - ]; + ]}, + }, + }; - const feeItem = 1; - const destWeight = 500000000000; + const currencies: [any, bigint][] = [ + [ + { + ForeignAssetId: 0, + }, + //10_000_000_000_000_000n, + TRANSFER_AMOUNT, + ], + [ + { + NativeAssetId: 'Parent', + }, + 400_000_000_000_000n, + ], + ]; - const tx = api.tx.xTokens.transferMulticurrencies(currencies, feeItem, destination, destWeight); - const events = await executeTransaction(api, alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - // the commission has been paid in parachain native token - [balanceOpalFinal] = await getBalance(api, [alice.address]); - expect(balanceOpalAfter > balanceOpalFinal).to.be.true; - }, uniqueApiOptions); + const feeItem = 1; + const destWeight = 500000000000; - await usingApi(async (api) => { - await waitNewBlocks(api, 3); + await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, destWeight); + + // the commission has been paid in parachain native token + balanceOpalFinal = await helper.balance.getSubstrate(alice.address); + expect(balanceOpalAfter > balanceOpalFinal).to.be.true; + + await usingWestmintPlaygrounds(westmintUrl, async (helper) => { + await helper.wait.newBlocks(3); // The USDT token never paid fees. Its amount not changed from begin value. // Also check that xcm transfer has been succeeded - const free = ((await api.query.assets.account(100, alice.address)).toHuman()) as any; - expect(BigInt(free.balance.replace(/,/g, '')) == ASSET_AMOUNT).to.be.true; - }, statemineApiOptions); + expect((await helper.assets.account(ASSET_ID, alice.address))! == ASSET_AMOUNT).to.be.true; + }); }); - it('Should connect and send Relay token to Unique', async () => { - - const uniqueApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT), - }; - - const uniqueApiOptions2: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT), - }; - - const relayApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + RELAY_PORT), - }; - + itSub('Should connect and send Relay token to Unique', async ({helper}) => { const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n; - await usingApi(async (api) => { - [balanceBobBefore] = await getBalance(api, [bob.address]); - balanceBobRelayTokenBefore = BigInt(((await api.query.tokens.accounts(bob.addressRaw, {NativeAssetId: 'Parent'})).toJSON() as any).free); - - }, uniqueApiOptions); + balanceBobBefore = await helper.balance.getSubstrate(bob.address); + balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'}); // Providing the relay currency to the unique sender account - await usingApi(async (api) => { + await usingRelayPlaygrounds(relayUrl, async (helper) => { const destination = { V1: { parents: 0, @@ -446,82 +339,62 @@ }; const feeAssetItem = 0; - - const weightLimit = { - Limited: 5_000_000_000, - }; + const weightLimit = 5_000_000_000; - const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); - const events = await executeTransaction(api, bob, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - }, relayApiOptions); + await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, weightLimit); + }); + await helper.wait.newBlocks(3); - await usingApi(async (api) => { - await waitNewBlocks(api, 3); + balanceBobAfter = await helper.balance.getSubstrate(bob.address); + balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'}); - [balanceBobAfter] = await getBalance(api, [bob.address]); - balanceBobRelayTokenAfter = BigInt(((await api.query.tokens.accounts(bob.addressRaw, {NativeAssetId: 'Parent'})).toJSON() as any).free); - const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore; - console.log( - 'Relay (Westend) to Opal transaction fees: %s OPL', - bigIntToDecimals(balanceBobAfter - balanceBobBefore), - ); - console.log( - 'Relay (Westend) to Opal transaction fees: %s WND', - bigIntToDecimals(wndFee, WESTMINT_DECIMALS), - ); - expect(balanceBobBefore == balanceBobAfter).to.be.true; - expect(balanceBobRelayTokenBefore < balanceBobRelayTokenAfter).to.be.true; - }, uniqueApiOptions2); - + const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore; + console.log( + 'Relay (Westend) to Opal transaction fees: %s OPL', + helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore), + ); + console.log( + 'Relay (Westend) to Opal transaction fees: %s WND', + helper.util.bigIntToDecimals(wndFee, WESTMINT_DECIMALS), + ); + expect(balanceBobBefore == balanceBobAfter).to.be.true; + expect(balanceBobRelayTokenBefore < balanceBobRelayTokenAfter).to.be.true; }); - it('Should connect and send Relay token back', async () => { - const uniqueApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT), + itSub('Should connect and send Relay token back', async ({helper}) => { + const destination = { + V1: { + parents: 1, + interior: {X2: [ + { + Parachain: STATEMINE_CHAIN, + }, + { + AccountId32: { + network: 'Any', + id: bob.addressRaw, + }, + }, + ]}, + }, }; - await usingApi(async (api) => { - const destination = { - V1: { - parents: 1, - interior: {X2: [ - { - Parachain: STATEMINE_CHAIN, - }, - { - AccountId32: { - network: 'Any', - id: bob.addressRaw, - }, - }, - ]}, + const currencies: any = [ + [ + { + NativeAssetId: 'Parent', }, - }; + 50_000_000_000_000_000n, + ], + ]; - const currencies: any = [ - [ - { - NativeAssetId: 'Parent', - }, - 50_000_000_000_000_000n, - ], - ]; - - const feeItem = 0; - const destWeight = 500000000000; - - const tx = api.tx.xTokens.transferMulticurrencies(currencies, feeItem, destination, destWeight); - const events = await executeTransaction(api, bob, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + const feeItem = 0; + const destWeight = 500000000000; - [balanceBobFinal] = await getBalance(api, [bob.address]); - console.log('Relay (Westend) to Opal transaction fees: %s OPL', balanceBobAfter - balanceBobFinal); + await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, destWeight); - }, uniqueApiOptions); + balanceBobFinal = await helper.balance.getSubstrate(bob.address); + console.log('Relay (Westend) to Opal transaction fees: %s OPL', balanceBobAfter - balanceBobFinal); }); - }); --- a/tests/src/xcm/xcmQuartz.test.ts +++ b/tests/src/xcm/xcmQuartz.test.ts @@ -14,53 +14,24 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import {WsProvider, Keyring} from '@polkadot/api'; -import {ApiOptions} from '@polkadot/api/types'; import {IKeyringPair} from '@polkadot/types/types'; -import usingApi, {submitTransactionAsync} from '../substrate/substrate-api'; -import {getGenericResult, generateKeyringPair, waitEvent, describeXCM, bigIntToDecimals} from '../deprecated-helpers/helpers'; -import {MultiLocation} from '@polkadot/types/interfaces'; import {blake2AsHex} from '@polkadot/util-crypto'; -import waitNewBlocks from '../substrate/wait-new-blocks'; -import getBalance from '../substrate/get-balance'; +import config from '../config'; import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces'; - -chai.use(chaiAsPromised); -const expect = chai.expect; +import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds} from '../util'; const QUARTZ_CHAIN = 2095; const KARURA_CHAIN = 2000; const MOONRIVER_CHAIN = 2023; -const RELAY_PORT = 9844; -const KARURA_PORT = 9946; -const MOONRIVER_PORT = 9947; +const relayUrl = config.relayUrl; +const karuraUrl = config.karuraUrl; +const moonriverUrl = config.moonriverUrl; const KARURA_DECIMALS = 12; const TRANSFER_AMOUNT = 2000000000000000000000000n; -function parachainApiOptions(port: number): ApiOptions { - return { - provider: new WsProvider('ws://127.0.0.1:' + port.toString()), - }; -} - -function karuraOptions(): ApiOptions { - return parachainApiOptions(KARURA_PORT); -} - -function moonriverOptions(): ApiOptions { - return parachainApiOptions(MOONRIVER_PORT); -} - -function relayOptions(): ApiOptions { - return parachainApiOptions(RELAY_PORT); -} - describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -76,219 +47,162 @@ let balanceQuartzForeignTokenFinal: bigint; before(async () => { - console.log('hey babe'); - await usingApi(async (api, privateKeyWrapper) => { - const keyringSr25519 = new Keyring({type: 'sr25519'}); - - alice = privateKeyWrapper('//Alice'); - randomAccount = generateKeyringPair(keyringSr25519); + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + [randomAccount] = await helper.arrange.createAccounts([0n], alice); }); - // Karura side - await usingApi( - async (api) => { - const destination = { - V0: { - X2: [ - 'Parent', - { - Parachain: QUARTZ_CHAIN, - }, - ], - }, - }; - - const metadata = { - name: 'QTZ', - symbol: 'QTZ', - decimals: 18, - minimalBalance: 1, - }; - - const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata); - const sudoTx = api.tx.sudo.sudo(tx as any); - const events = await submitTransactionAsync(alice, sudoTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n); - const events1 = await submitTransactionAsync(alice, tx1); - const result1 = getGenericResult(events1); - expect(result1.success).to.be.true; - - [balanceKaruraTokenInit] = await getBalance(api, [randomAccount.address]); - { - const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any; - balanceQuartzForeignTokenInit = BigInt(free); - } - }, - karuraOptions(), - ); - - // Quartz side - await usingApi(async (api) => { - const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT); - const events0 = await submitTransactionAsync(alice, tx0); - const result0 = getGenericResult(events0); - expect(result0.success).to.be.true; - - [balanceQuartzTokenInit] = await getBalance(api, [randomAccount.address]); - }); - }); - - it('Should connect and send QTZ to Karura', async () => { - - // Quartz side - await usingApi(async (api) => { - + await usingKaruraPlaygrounds(karuraUrl, async (helper) => { const destination = { V0: { X2: [ 'Parent', { - Parachain: KARURA_CHAIN, + Parachain: QUARTZ_CHAIN, }, ], }, }; - const beneficiary = { - V0: { - X1: { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, - }, - }, + const metadata = { + name: 'QTZ', + symbol: 'QTZ', + decimals: 18, + minimalBalance: 1n, }; - const assets = { - V1: [ + await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); + await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); + balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address); + balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0}); + }); + + await usingPlaygrounds(async (helper) => { + await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT); + balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address); + }); + }); + + itSub('Should connect and send QTZ to Karura', async ({helper}) => { + const destination = { + V0: { + X2: [ + 'Parent', { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, + Parachain: KARURA_CHAIN, }, ], - }; - - const feeAssetItem = 0; + }, + }; - const weightLimit = { - Limited: 5000000000, - }; + const beneficiary = { + V0: { + X1: { + AccountId32: { + network: 'Any', + id: randomAccount.addressRaw, + }, + }, + }, + }; - const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); - const events = await submitTransactionAsync(randomAccount, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + const assets = { + V1: [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: TRANSFER_AMOUNT, + }, + }, + ], + }; - [balanceQuartzTokenMiddle] = await getBalance(api, [randomAccount.address]); + const feeAssetItem = 0; + const weightLimit = 5000000000; - const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT; - console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees)); - expect(qtzFees > 0n).to.be.true; - }); + await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit); + balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - // Karura side - await usingApi( - async (api) => { - await waitNewBlocks(api, 3); - const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any; - balanceQuartzForeignTokenMiddle = BigInt(free); + const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT; + console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees)); + expect(qtzFees > 0n).to.be.true; - [balanceKaruraTokenMiddle] = await getBalance(api, [randomAccount.address]); + await usingKaruraPlaygrounds(karuraUrl, async (helper) => { + await helper.wait.newBlocks(3); + balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0}); + balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle; - const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit; + const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle; + const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit; - console.log( - '[Quartz -> Karura] transaction fees on Karura: %s KAR', - bigIntToDecimals(karFees, KARURA_DECIMALS), - ); - console.log('[Quartz -> Karura] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer)); - expect(karFees == 0n).to.be.true; - expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true; - }, - karuraOptions(), - ); + console.log( + '[Quartz -> Karura] transaction fees on Karura: %s KAR', + helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS), + ); + console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer)); + expect(karFees == 0n).to.be.true; + expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true; + }); }); - it('Should connect to Karura and send QTZ back', async () => { - - // Karura side - await usingApi( - async (api) => { - const destination = { - V1: { - parents: 1, - interior: { - X2: [ - {Parachain: QUARTZ_CHAIN}, - { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, + itSub('Should connect to Karura and send QTZ back', async ({helper}) => { + await usingKaruraPlaygrounds(karuraUrl, async (helper) => { + const destination = { + V1: { + parents: 1, + interior: { + X2: [ + {Parachain: QUARTZ_CHAIN}, + { + AccountId32: { + network: 'Any', + id: randomAccount.addressRaw, }, - ], - }, + }, + ], }, - }; - - const id = { - ForeignAsset: 0, - }; + }, + }; - const destWeight = 50000000; + const id = { + ForeignAsset: 0, + }; - const tx = api.tx.xTokens.transfer(id as any, TRANSFER_AMOUNT, destination, destWeight); - const events = await submitTransactionAsync(randomAccount, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + const destWeight = 50000000; - [balanceKaruraTokenFinal] = await getBalance(api, [randomAccount.address]); - { - const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, id)).toJSON() as any; - balanceQuartzForeignTokenFinal = BigInt(free); - } + await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight); + balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address); + balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id); - const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal; - const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal; + const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal; + const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal; - console.log( - '[Karura -> Quartz] transaction fees on Karura: %s KAR', - bigIntToDecimals(karFees, KARURA_DECIMALS), - ); - console.log('[Karura -> Quartz] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer)); + console.log( + '[Karura -> Quartz] transaction fees on Karura: %s KAR', + helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS), + ); + console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer)); - expect(karFees > 0).to.be.true; - expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; - }, - karuraOptions(), - ); + expect(karFees > 0).to.be.true; + expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; + }); - // Quartz side - await usingApi(async (api) => { - await waitNewBlocks(api, 3); + await helper.wait.newBlocks(3); - [balanceQuartzTokenFinal] = await getBalance(api, [randomAccount.address]); - const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle; - expect(actuallyDelivered > 0).to.be.true; + balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address); + const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle; + expect(actuallyDelivered > 0).to.be.true; - console.log('[Karura -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered)); + console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered)); - const qtzFees = TRANSFER_AMOUNT - actuallyDelivered; - console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees)); - expect(qtzFees == 0n).to.be.true; - }); + const qtzFees = TRANSFER_AMOUNT - actuallyDelivered; + console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees)); + expect(qtzFees == 0n).to.be.true; }); }); @@ -297,13 +211,13 @@ let alice: IKeyringPair; before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); + await usingPlaygrounds(async (_helper, privateKey) => { + alice = await privateKey('//Alice'); }); }); - it('Quartz rejects tokens from the Relay', async () => { - await usingApi(async (api) => { + itSub('Quartz rejects tokens from the Relay', async ({helper}) => { + await usingRelayPlaygrounds(relayUrl, async (helper) => { const destination = { V1: { parents: 0, @@ -342,49 +256,37 @@ }; const feeAssetItem = 0; + const weightLimit = 5_000_000_000; - const weightLimit = { - Limited: 5_000_000_000, - }; + await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit); + }); - const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); - const events = await submitTransactionAsync(alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - }, relayOptions()); + const maxWaitBlocks = 3; - await usingApi(async api => { - const maxWaitBlocks = 3; - const dmpQueueExecutedDownward = await waitEvent( - api, - maxWaitBlocks, - 'dmpQueue', - 'ExecutedDownward', - ); + const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward'); - expect( - dmpQueueExecutedDownward != null, - '[Relay] dmpQueue.ExecutedDownward event is expected', - ).to.be.true; + expect( + dmpQueueExecutedDownward != null, + '[Relay] dmpQueue.ExecutedDownward event is expected', + ).to.be.true; - const event = dmpQueueExecutedDownward!.event; - const outcome = event.data[1] as XcmV2TraitsOutcome; + const event = dmpQueueExecutedDownward!.event; + const outcome = event.data[1] as XcmV2TraitsOutcome; - expect( - outcome.isIncomplete, - '[Relay] The outcome of the XCM should be `Incomplete`', - ).to.be.true; + expect( + outcome.isIncomplete, + '[Relay] The outcome of the XCM should be `Incomplete`', + ).to.be.true; - const incomplete = outcome.asIncomplete; - expect( - incomplete[1].toString() == 'AssetNotFound', - '[Relay] The XCM error should be `AssetNotFound`', - ).to.be.true; - }); + const incomplete = outcome.asIncomplete; + expect( + incomplete[1].toString() == 'AssetNotFound', + '[Relay] The XCM error should be `AssetNotFound`', + ).to.be.true; }); - it('Quartz rejects KAR tokens from Karura', async () => { - await usingApi(async (api) => { + itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => { + await usingKaruraPlaygrounds(karuraUrl, async (helper) => { const destination = { V1: { parents: 1, @@ -408,35 +310,31 @@ const destWeight = 50000000; - const tx = api.tx.xTokens.transfer(id as any, 100_000_000_000, destination, destWeight); - const events = await submitTransactionAsync(alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - }, karuraOptions()); + await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight); + }); - await usingApi(async api => { - const maxWaitBlocks = 3; - const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail'); + const maxWaitBlocks = 3; + + const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail'); - expect( - xcmpQueueFailEvent != null, - '[Karura] xcmpQueue.FailEvent event is expected', - ).to.be.true; + expect( + xcmpQueueFailEvent != null, + '[Karura] xcmpQueue.FailEvent event is expected', + ).to.be.true; - const event = xcmpQueueFailEvent!.event; - const outcome = event.data[1] as XcmV2TraitsError; + const event = xcmpQueueFailEvent!.event; + const outcome = event.data[1] as XcmV2TraitsError; - expect( - outcome.isUntrustedReserveLocation, - '[Karura] The XCM error should be `UntrustedReserveLocation`', - ).to.be.true; - }); + expect( + outcome.isUntrustedReserveLocation, + '[Karura] The XCM error should be `UntrustedReserveLocation`', + ).to.be.true; }); }); describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => { // Quartz constants - let quartzAlice: IKeyringPair; + let quartzDonor: IKeyringPair; let quartzAssetLocation; let randomAccountQuartz: IKeyringPair; @@ -444,15 +342,6 @@ // Moonriver constants let assetId: string; - - const moonriverKeyring = new Keyring({type: 'ethereum'}); - const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133'; - const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b'; - const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68'; - - const alithAccount = moonriverKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum'); - const baltatharAccount = moonriverKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum'); - const dorothyAccount = moonriverKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum'); const councilVotingThreshold = 2; const technicalCommitteeThreshold = 2; @@ -464,7 +353,7 @@ symbol: 'xcQTZ', decimals: 18, isFrozen: false, - minimalBalance: 1, + minimalBalance: 1n, }; let balanceQuartzTokenInit: bigint; @@ -478,322 +367,229 @@ let balanceMovrTokenFinal: bigint; before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - const keyringEth = new Keyring({type: 'ethereum'}); - const keyringSr25519 = new Keyring({type: 'sr25519'}); - - quartzAlice = privateKeyWrapper('//Alice'); - randomAccountQuartz = generateKeyringPair(keyringSr25519); - randomAccountMoonriver = generateKeyringPair(keyringEth); + await usingPlaygrounds(async (helper, privateKey) => { + quartzDonor = await privateKey('//Alice'); + [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor); balanceForeignQtzTokenInit = 0n; }); - await usingApi( - async (api) => { + await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { + const alithAccount = helper.account.alithAccount(); + const baltatharAccount = helper.account.baltatharAccount(); + const dorothyAccount = helper.account.dorothyAccount(); - // >>> Sponsoring Dorothy >>> - console.log('Sponsoring Dorothy.......'); - const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n); - const events0 = await submitTransactionAsync(alithAccount, tx0); - const result0 = getGenericResult(events0); - expect(result0.success).to.be.true; - console.log('Sponsoring Dorothy.......DONE'); - // <<< Sponsoring Dorothy <<< + randomAccountMoonriver = helper.account.create(); - const sourceLocation: MultiLocation = api.createType( - 'MultiLocation', - { - parents: 1, - interior: {X1: {Parachain: QUARTZ_CHAIN}}, - }, - ); + // >>> Sponsoring Dorothy >>> + console.log('Sponsoring Dorothy.......'); + await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n); + console.log('Sponsoring Dorothy.......DONE'); + // <<< Sponsoring Dorothy <<< - quartzAssetLocation = {XCM: sourceLocation}; - const existentialDeposit = 1; - const isSufficient = true; - const unitsPerSecond = '1'; - const numAssetsWeightHint = 0; + quartzAssetLocation = { + XCM: { + parents: 1, + interior: {X1: {Parachain: QUARTZ_CHAIN}}, + }, + }; + const existentialDeposit = 1n; + const isSufficient = true; + const unitsPerSecond = 1n; + const numAssetsWeightHint = 0; - const registerTx = api.tx.assetManager.registerForeignAsset( - quartzAssetLocation, - quartzAssetMetadata, - existentialDeposit, - isSufficient, - ); - console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || ''); - - const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond( - quartzAssetLocation, - unitsPerSecond, - numAssetsWeightHint, - ); - console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || ''); - - const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]); - console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || ''); - - // >>> Note motion preimage >>> - console.log('Note motion preimage.......'); - const encodedProposal = batchCall?.method.toHex() || ''; - const proposalHash = blake2AsHex(encodedProposal); - console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal); - console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash); - console.log('Encoded length %d', encodedProposal.length); + const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ + location: quartzAssetLocation, + metadata: quartzAssetMetadata, + existentialDeposit, + isSufficient, + unitsPerSecond, + numAssetsWeightHint, + }); + const proposalHash = blake2AsHex(encodedProposal); - const tx1 = api.tx.democracy.notePreimage(encodedProposal); - const events1 = await submitTransactionAsync(baltatharAccount, tx1); - const result1 = getGenericResult(events1); - expect(result1.success).to.be.true; - console.log('Note motion preimage.......DONE'); - // <<< Note motion preimage <<< + console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); + console.log('Encoded length %d', encodedProposal.length); + console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash); - // >>> Propose external motion through council >>> - console.log('Propose external motion through council.......'); - const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash); - const tx2 = api.tx.councilCollective.propose( - councilVotingThreshold, - externalMotion, - externalMotion.encodedLength, - ); - const events2 = await submitTransactionAsync(baltatharAccount, tx2); - const result2 = getGenericResult(events2); - expect(result2.success).to.be.true; + // >>> Note motion preimage >>> + console.log('Note motion preimage.......'); + await helper.democracy.notePreimage(baltatharAccount, encodedProposal); + console.log('Note motion preimage.......DONE'); + // <<< Note motion preimage <<< - const encodedMotion = externalMotion?.method.toHex() || ''; - const motionHash = blake2AsHex(encodedMotion); - console.log('Motion hash is %s', motionHash); + // >>> Propose external motion through council >>> + console.log('Propose external motion through council.......'); + const externalMotion = helper.democracy.externalProposeMajority(proposalHash); + const encodedMotion = externalMotion?.method.toHex() || ''; + const motionHash = blake2AsHex(encodedMotion); + console.log('Motion hash is %s', motionHash); - const tx3 = api.tx.councilCollective.vote(motionHash, 0, true); - { - const events3 = await submitTransactionAsync(dorothyAccount, tx3); - const result3 = getGenericResult(events3); - expect(result3.success).to.be.true; - } - { - const events3 = await submitTransactionAsync(baltatharAccount, tx3); - const result3 = getGenericResult(events3); - expect(result3.success).to.be.true; - } + await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength); - const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength); - const events4 = await submitTransactionAsync(dorothyAccount, tx4); - const result4 = getGenericResult(events4); - expect(result4.success).to.be.true; - console.log('Propose external motion through council.......DONE'); - // <<< Propose external motion through council <<< + const councilProposalIdx = await helper.collective.council.proposalCount() - 1; + await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true); + await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true); - // >>> Fast track proposal through technical committee >>> - console.log('Fast track proposal through technical committee.......'); - const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod); - const tx5 = api.tx.techCommitteeCollective.propose( - technicalCommitteeThreshold, - fastTrack, - fastTrack.encodedLength, - ); - const events5 = await submitTransactionAsync(alithAccount, tx5); - const result5 = getGenericResult(events5); - expect(result5.success).to.be.true; + await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength); + console.log('Propose external motion through council.......DONE'); + // <<< Propose external motion through council <<< - const encodedFastTrack = fastTrack?.method.toHex() || ''; - const fastTrackHash = blake2AsHex(encodedFastTrack); - console.log('FastTrack hash is %s', fastTrackHash); + // >>> Fast track proposal through technical committee >>> + console.log('Fast track proposal through technical committee.......'); + const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod); + const encodedFastTrack = fastTrack?.method.toHex() || ''; + const fastTrackHash = blake2AsHex(encodedFastTrack); + console.log('FastTrack hash is %s', fastTrackHash); - const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1; - const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true); - { - const events6 = await submitTransactionAsync(baltatharAccount, tx6); - const result6 = getGenericResult(events6); - expect(result6.success).to.be.true; - } - { - const events6 = await submitTransactionAsync(alithAccount, tx6); - const result6 = getGenericResult(events6); - expect(result6.success).to.be.true; - } + await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength); - const tx7 = api.tx.techCommitteeCollective - .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength); - const events7 = await submitTransactionAsync(baltatharAccount, tx7); - const result7 = getGenericResult(events7); - expect(result7.success).to.be.true; - console.log('Fast track proposal through technical committee.......DONE'); - // <<< Fast track proposal through technical committee <<< + const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1; + await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true); + await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true); - // >>> Referendum voting >>> - console.log('Referendum voting.......'); - const tx8 = api.tx.democracy.vote( - 0, - {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}}, - ); - const events8 = await submitTransactionAsync(dorothyAccount, tx8); - const result8 = getGenericResult(events8); - expect(result8.success).to.be.true; - console.log('Referendum voting.......DONE'); - // <<< Referendum voting <<< + await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength); + console.log('Fast track proposal through technical committee.......DONE'); + // <<< Fast track proposal through technical committee <<< - // >>> Acquire Quartz AssetId Info on Moonriver >>> - console.log('Acquire Quartz AssetId Info on Moonriver.......'); + // >>> Referendum voting >>> + console.log('Referendum voting.......'); + await helper.democracy.referendumVote(dorothyAccount, 0, { + balance: 10_000_000_000_000_000_000n, + vote: {aye: true, conviction: 1}, + }); + console.log('Referendum voting.......DONE'); + // <<< Referendum voting <<< - // Wait for the democracy execute - await waitNewBlocks(api, 5); + // >>> Acquire Quartz AssetId Info on Moonriver >>> + console.log('Acquire Quartz AssetId Info on Moonriver.......'); - assetId = (await api.query.assetManager.assetTypeId({ - XCM: sourceLocation, - })).toString(); + // Wait for the democracy execute + await helper.wait.newBlocks(5); - console.log('QTZ asset ID is %s', assetId); - console.log('Acquire Quartz AssetId Info on Moonriver.......DONE'); - // >>> Acquire Quartz AssetId Info on Moonriver >>> + assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString(); - // >>> Sponsoring random Account >>> - console.log('Sponsoring random Account.......'); - const tx10 = api.tx.balances.transfer(randomAccountMoonriver.address, 11_000_000_000_000_000_000n); - const events10 = await submitTransactionAsync(baltatharAccount, tx10); - const result10 = getGenericResult(events10); - expect(result10.success).to.be.true; - console.log('Sponsoring random Account.......DONE'); - // <<< Sponsoring random Account <<< + console.log('QTZ asset ID is %s', assetId); + console.log('Acquire Quartz AssetId Info on Moonriver.......DONE'); + // >>> Acquire Quartz AssetId Info on Moonriver >>> - [balanceMovrTokenInit] = await getBalance(api, [randomAccountMoonriver.address]); - }, - moonriverOptions(), - ); + // >>> Sponsoring random Account >>> + console.log('Sponsoring random Account.......'); + await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n); + console.log('Sponsoring random Account.......DONE'); + // <<< Sponsoring random Account <<< - await usingApi(async (api) => { - const tx0 = api.tx.balances.transfer(randomAccountQuartz.address, 10n * TRANSFER_AMOUNT); - const events0 = await submitTransactionAsync(quartzAlice, tx0); - const result0 = getGenericResult(events0); - expect(result0.success).to.be.true; + balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address); + }); - [balanceQuartzTokenInit] = await getBalance(api, [randomAccountQuartz.address]); + await usingPlaygrounds(async (helper) => { + await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT); + balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address); }); }); - it('Should connect and send QTZ to Moonriver', async () => { - await usingApi(async (api) => { - const currencyId = { - NativeAssetId: 'Here', - }; - const dest = { - V1: { - parents: 1, - interior: { - X2: [ - {Parachain: MOONRIVER_CHAIN}, - {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}}, - ], - }, + itSub('Should connect and send QTZ to Moonriver', async ({helper}) => { + const currencyId = { + NativeAssetId: 'Here', + }; + const dest = { + V1: { + parents: 1, + interior: { + X2: [ + {Parachain: MOONRIVER_CHAIN}, + {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}}, + ], }, - }; - const amount = TRANSFER_AMOUNT; - const destWeight = 850000000; + }, + }; + const amount = TRANSFER_AMOUNT; + const destWeight = 850000000; - const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight); - const events = await submitTransactionAsync(randomAccountQuartz, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, destWeight); - [balanceQuartzTokenMiddle] = await getBalance(api, [randomAccountQuartz.address]); - expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true; + balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address); + expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true; - const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT; - console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', bigIntToDecimals(transactionFees)); - expect(transactionFees > 0).to.be.true; - }); + const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT; + console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees)); + expect(transactionFees > 0).to.be.true; - await usingApi( - async (api) => { - await waitNewBlocks(api, 3); + await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { + await helper.wait.newBlocks(3); - [balanceMovrTokenMiddle] = await getBalance(api, [randomAccountMoonriver.address]); + balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address); - const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle; - console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',bigIntToDecimals(movrFees)); - expect(movrFees == 0n).to.be.true; + const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle; + console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees)); + expect(movrFees == 0n).to.be.true; - const qtzRandomAccountAsset = ( - await api.query.assets.account(assetId, randomAccountMoonriver.address) - ).toJSON()! as any; - - balanceForeignQtzTokenMiddle = BigInt(qtzRandomAccountAsset['balance']); - const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit; - console.log('[Quartz -> Moonriver] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer)); - expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true; - }, - moonriverOptions(), - ); + balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']); + const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit; + console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer)); + expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true; + }); }); - it('Should connect to Moonriver and send QTZ back', async () => { - await usingApi( - async (api) => { - const asset = { - V1: { - id: { - Concrete: { - parents: 1, - interior: { - X1: {Parachain: QUARTZ_CHAIN}, - }, + itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => { + await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { + const asset = { + V1: { + id: { + Concrete: { + parents: 1, + interior: { + X1: {Parachain: QUARTZ_CHAIN}, }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, }, }, - }; - const destination = { - V1: { - parents: 1, - interior: { - X2: [ - {Parachain: QUARTZ_CHAIN}, - {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}}, - ], - }, + fun: { + Fungible: TRANSFER_AMOUNT, }, - }; - const destWeight = 50000000; + }, + }; + const destination = { + V1: { + parents: 1, + interior: { + X2: [ + {Parachain: QUARTZ_CHAIN}, + {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}}, + ], + }, + }, + }; + const destWeight = 50000000; - const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight); - const events = await submitTransactionAsync(randomAccountMoonriver, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, destWeight); - [balanceMovrTokenFinal] = await getBalance(api, [randomAccountMoonriver.address]); + balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address); - const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal; - console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', bigIntToDecimals(movrFees)); - expect(movrFees > 0).to.be.true; + const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal; + console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees)); + expect(movrFees > 0).to.be.true; - const qtzRandomAccountAsset = ( - await api.query.assets.account(assetId, randomAccountMoonriver.address) - ).toJSON()! as any; + const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address); - expect(qtzRandomAccountAsset).to.be.null; + expect(qtzRandomAccountAsset).to.be.null; - balanceForeignQtzTokenFinal = 0n; + balanceForeignQtzTokenFinal = 0n; - const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal; - console.log('[Quartz -> Moonriver] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer)); - expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; - }, - moonriverOptions(), - ); + const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal; + console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer)); + expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; + }); - await usingApi(async (api) => { - await waitNewBlocks(api, 3); + await helper.wait.newBlocks(3); - [balanceQuartzTokenFinal] = await getBalance(api, [randomAccountQuartz.address]); - const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle; - expect(actuallyDelivered > 0).to.be.true; + balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address); + const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle; + expect(actuallyDelivered > 0).to.be.true; - console.log('[Moonriver -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered)); + console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered)); - const qtzFees = TRANSFER_AMOUNT - actuallyDelivered; - console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees)); - expect(qtzFees == 0n).to.be.true; - }); + const qtzFees = TRANSFER_AMOUNT - actuallyDelivered; + console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees)); + expect(qtzFees == 0n).to.be.true; }); }); --- a/tests/src/xcm/xcmUnique.test.ts +++ b/tests/src/xcm/xcmUnique.test.ts @@ -14,53 +14,24 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import {WsProvider, Keyring} from '@polkadot/api'; -import {ApiOptions} from '@polkadot/api/types'; import {IKeyringPair} from '@polkadot/types/types'; -import usingApi, {submitTransactionAsync} from '../substrate/substrate-api'; -import {getGenericResult, generateKeyringPair, waitEvent, describeXCM, bigIntToDecimals} from '../deprecated-helpers/helpers'; -import {MultiLocation} from '@polkadot/types/interfaces'; import {blake2AsHex} from '@polkadot/util-crypto'; -import waitNewBlocks from '../substrate/wait-new-blocks'; -import getBalance from '../substrate/get-balance'; +import config from '../config'; import {XcmV2TraitsError, XcmV2TraitsOutcome} from '../interfaces'; - -chai.use(chaiAsPromised); -const expect = chai.expect; +import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds} from '../util'; const UNIQUE_CHAIN = 2037; const ACALA_CHAIN = 2000; const MOONBEAM_CHAIN = 2004; -const RELAY_PORT = 9844; -const ACALA_PORT = 9946; -const MOONBEAM_PORT = 9947; +const relayUrl = config.relayUrl; +const acalaUrl = config.acalaUrl; +const moonbeamUrl = config.moonbeamUrl; const ACALA_DECIMALS = 12; const TRANSFER_AMOUNT = 2000000000000000000000000n; -function parachainApiOptions(port: number): ApiOptions { - return { - provider: new WsProvider('ws://127.0.0.1:' + port.toString()), - }; -} - -function acalaOptions(): ApiOptions { - return parachainApiOptions(ACALA_PORT); -} - -function moonbeamOptions(): ApiOptions { - return parachainApiOptions(MOONBEAM_PORT); -} - -function relayOptions(): ApiOptions { - return parachainApiOptions(RELAY_PORT); -} - describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -76,218 +47,166 @@ let balanceUniqueForeignTokenFinal: bigint; before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - const keyringSr25519 = new Keyring({type: 'sr25519'}); - - alice = privateKeyWrapper('//Alice'); - randomAccount = generateKeyringPair(keyringSr25519); + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + [randomAccount] = await helper.arrange.createAccounts([0n], alice); }); - // Acala side - await usingApi( - async (api) => { - const destination = { - V0: { - X2: [ - 'Parent', - { - Parachain: UNIQUE_CHAIN, - }, - ], - }, - }; - - const metadata = { - name: 'UNQ', - symbol: 'UNQ', - decimals: 18, - minimalBalance: 1, - }; - - const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata); - const sudoTx = api.tx.sudo.sudo(tx as any); - const events = await submitTransactionAsync(alice, sudoTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n); - const events1 = await submitTransactionAsync(alice, tx1); - const result1 = getGenericResult(events1); - expect(result1.success).to.be.true; - - [balanceAcalaTokenInit] = await getBalance(api, [randomAccount.address]); - { - const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any; - balanceUniqueForeignTokenInit = BigInt(free); - } - }, - acalaOptions(), - ); - - // Unique side - await usingApi(async (api) => { - const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT); - const events0 = await submitTransactionAsync(alice, tx0); - const result0 = getGenericResult(events0); - expect(result0.success).to.be.true; - - [balanceUniqueTokenInit] = await getBalance(api, [randomAccount.address]); - }); - }); - - it('Should connect and send UNQ to Acala', async () => { - - // Unique side - await usingApi(async (api) => { - + await usingAcalaPlaygrounds(acalaUrl, async (helper) => { const destination = { V0: { X2: [ 'Parent', { - Parachain: ACALA_CHAIN, + Parachain: UNIQUE_CHAIN, }, ], }, }; - const beneficiary = { - V0: { - X1: { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, - }, - }, + const metadata = { + name: 'UNQ', + symbol: 'UNQ', + decimals: 18, + minimalBalance: 1n, }; - const assets = { - V1: [ + await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); + await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); + balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address); + balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0}); + }); + + await usingPlaygrounds(async (helper) => { + await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT); + balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address); + }); + }); + + itSub('Should connect and send UNQ to Acala', async ({helper}) => { + + const destination = { + V0: { + X2: [ + 'Parent', { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, + Parachain: ACALA_CHAIN, }, ], - }; + }, + }; - const feeAssetItem = 0; + const beneficiary = { + V0: { + X1: { + AccountId32: { + network: 'Any', + id: randomAccount.addressRaw, + }, + }, + }, + }; - const weightLimit = { - Limited: 5000000000, - }; + const assets = { + V1: [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: TRANSFER_AMOUNT, + }, + }, + ], + }; + + const feeAssetItem = 0; + const weightLimit = 5000000000; - const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); - const events = await submitTransactionAsync(randomAccount, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit); - [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccount.address]); + balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT; - console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees)); - expect(unqFees > 0n).to.be.true; - }); + const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT; + console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees)); + expect(unqFees > 0n).to.be.true; - // Acala side - await usingApi( - async (api) => { - await waitNewBlocks(api, 3); - const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any; - balanceUniqueForeignTokenMiddle = BigInt(free); + await usingAcalaPlaygrounds(acalaUrl, async (helper) => { + await helper.wait.newBlocks(3); - [balanceAcalaTokenMiddle] = await getBalance(api, [randomAccount.address]); + balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0}); + balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle; - const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit; + const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle; + const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit; - console.log( - '[Unique -> Acala] transaction fees on Acala: %s ACA', - bigIntToDecimals(acaFees, ACALA_DECIMALS), - ); - console.log('[Unique -> Acala] income %s UNQ', bigIntToDecimals(unqIncomeTransfer)); - expect(acaFees == 0n).to.be.true; - expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true; - }, - acalaOptions(), - ); + console.log( + '[Unique -> Acala] transaction fees on Acala: %s ACA', + helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS), + ); + console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer)); + expect(acaFees == 0n).to.be.true; + expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true; + }); }); - it('Should connect to Acala and send UNQ back', async () => { - - // Acala side - await usingApi( - async (api) => { - const destination = { - V1: { - parents: 1, - interior: { - X2: [ - {Parachain: UNIQUE_CHAIN}, - { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, + itSub('Should connect to Acala and send UNQ back', async ({helper}) => { + await usingAcalaPlaygrounds(acalaUrl, async (helper) => { + const destination = { + V1: { + parents: 1, + interior: { + X2: [ + {Parachain: UNIQUE_CHAIN}, + { + AccountId32: { + network: 'Any', + id: randomAccount.addressRaw, }, - ], - }, + }, + ], }, - }; + }, + }; - const id = { - ForeignAsset: 0, - }; + const id = { + ForeignAsset: 0, + }; - const destWeight = 50000000; + const destWeight = 50000000; - const tx = api.tx.xTokens.transfer(id as any, TRANSFER_AMOUNT, destination, destWeight); - const events = await submitTransactionAsync(randomAccount, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight); - [balanceAcalaTokenFinal] = await getBalance(api, [randomAccount.address]); - { - const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, id)).toJSON() as any; - balanceUniqueForeignTokenFinal = BigInt(free); - } + balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address); + balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id); - const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal; - const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal; + const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal; + const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal; - console.log( - '[Acala -> Unique] transaction fees on Acala: %s ACA', - bigIntToDecimals(acaFees, ACALA_DECIMALS), - ); - console.log('[Acala -> Unique] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer)); + console.log( + '[Acala -> Unique] transaction fees on Acala: %s ACA', + helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS), + ); + console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer)); - expect(acaFees > 0).to.be.true; - expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; - }, - acalaOptions(), - ); + expect(acaFees > 0).to.be.true; + expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; + }); - // Unique side - await usingApi(async (api) => { - await waitNewBlocks(api, 3); + await helper.wait.newBlocks(3); - [balanceUniqueTokenFinal] = await getBalance(api, [randomAccount.address]); - const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle; - expect(actuallyDelivered > 0).to.be.true; + balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address); + const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle; + expect(actuallyDelivered > 0).to.be.true; - console.log('[Acala -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered)); + console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered)); - const unqFees = TRANSFER_AMOUNT - actuallyDelivered; - console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees)); - expect(unqFees == 0n).to.be.true; - }); + const unqFees = TRANSFER_AMOUNT - actuallyDelivered; + console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees)); + expect(unqFees == 0n).to.be.true; }); }); @@ -296,13 +215,13 @@ let alice: IKeyringPair; before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); + await usingPlaygrounds(async (_helper, privateKey) => { + alice = await privateKey('//Alice'); }); }); - it('Unique rejects tokens from the Relay', async () => { - await usingApi(async (api) => { + itSub('Unique rejects tokens from the Relay', async ({helper}) => { + await usingRelayPlaygrounds(relayUrl, async (helper) => { const destination = { V1: { parents: 0, @@ -341,49 +260,37 @@ }; const feeAssetItem = 0; + const weightLimit = 5_000_000_000; - const weightLimit = { - Limited: 5_000_000_000, - }; + await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit); + }); - const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); - const events = await submitTransactionAsync(alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - }, relayOptions()); + const maxWaitBlocks = 3; - await usingApi(async api => { - const maxWaitBlocks = 3; - const dmpQueueExecutedDownward = await waitEvent( - api, - maxWaitBlocks, - 'dmpQueue', - 'ExecutedDownward', - ); + const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward'); - expect( - dmpQueueExecutedDownward != null, - '[Relay] dmpQueue.ExecutedDownward event is expected', - ).to.be.true; + expect( + dmpQueueExecutedDownward != null, + '[Relay] dmpQueue.ExecutedDownward event is expected', + ).to.be.true; - const event = dmpQueueExecutedDownward!.event; - const outcome = event.data[1] as XcmV2TraitsOutcome; + const event = dmpQueueExecutedDownward!.event; + const outcome = event.data[1] as XcmV2TraitsOutcome; - expect( - outcome.isIncomplete, - '[Relay] The outcome of the XCM should be `Incomplete`', - ).to.be.true; + expect( + outcome.isIncomplete, + '[Relay] The outcome of the XCM should be `Incomplete`', + ).to.be.true; - const incomplete = outcome.asIncomplete; - expect( - incomplete[1].toString() == 'AssetNotFound', - '[Relay] The XCM error should be `AssetNotFound`', - ).to.be.true; - }); + const incomplete = outcome.asIncomplete; + expect( + incomplete[1].toString() == 'AssetNotFound', + '[Relay] The XCM error should be `AssetNotFound`', + ).to.be.true; }); - it('Unique rejects ACA tokens from Acala', async () => { - await usingApi(async (api) => { + itSub('Unique rejects ACA tokens from Acala', async ({helper}) => { + await usingAcalaPlaygrounds(acalaUrl, async (helper) => { const destination = { V1: { parents: 1, @@ -407,36 +314,31 @@ const destWeight = 50000000; - const tx = api.tx.xTokens.transfer(id as any, 100_000_000_000, destination, destWeight); - const events = await submitTransactionAsync(alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - }, acalaOptions()); + await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight); + }); - await usingApi(async api => { - const maxWaitBlocks = 3; - const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail'); + const maxWaitBlocks = 3; - expect( - xcmpQueueFailEvent != null, - '[Acala] xcmpQueue.FailEvent event is expected', - ).to.be.true; + const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail'); + + expect( + xcmpQueueFailEvent != null, + '[Acala] xcmpQueue.FailEvent event is expected', + ).to.be.true; - const event = xcmpQueueFailEvent!.event; - const outcome = event.data[1] as XcmV2TraitsError; + const event = xcmpQueueFailEvent!.event; + const outcome = event.data[1] as XcmV2TraitsError; - expect( - outcome.isUntrustedReserveLocation, - '[Acala] The XCM error should be `UntrustedReserveLocation`', - ).to.be.true; - }); + expect( + outcome.isUntrustedReserveLocation, + '[Acala] The XCM error should be `UntrustedReserveLocation`', + ).to.be.true; }); }); describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => { - // Unique constants - let uniqueAlice: IKeyringPair; + let uniqueDonor: IKeyringPair; let uniqueAssetLocation; let randomAccountUnique: IKeyringPair; @@ -445,15 +347,6 @@ // Moonbeam constants let assetId: string; - const moonbeamKeyring = new Keyring({type: 'ethereum'}); - const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133'; - const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b'; - const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68'; - - const alithAccount = moonbeamKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum'); - const baltatharAccount = moonbeamKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum'); - const dorothyAccount = moonbeamKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum'); - const councilVotingThreshold = 2; const technicalCommitteeThreshold = 2; const votingPeriod = 3; @@ -464,7 +357,7 @@ symbol: 'xcUNQ', decimals: 18, isFrozen: false, - minimalBalance: 1, + minimalBalance: 1n, }; let balanceUniqueTokenInit: bigint; @@ -478,322 +371,230 @@ let balanceGlmrTokenFinal: bigint; before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - const keyringEth = new Keyring({type: 'ethereum'}); - const keyringSr25519 = new Keyring({type: 'sr25519'}); + await usingPlaygrounds(async (helper, privateKey) => { + uniqueDonor = await privateKey('//Alice'); + [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor); - uniqueAlice = privateKeyWrapper('//Alice'); - randomAccountUnique = generateKeyringPair(keyringSr25519); - randomAccountMoonbeam = generateKeyringPair(keyringEth); - balanceForeignUnqTokenInit = 0n; }); - await usingApi( - async (api) => { + await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { + const alithAccount = helper.account.alithAccount(); + const baltatharAccount = helper.account.baltatharAccount(); + const dorothyAccount = helper.account.dorothyAccount(); - // >>> Sponsoring Dorothy >>> - console.log('Sponsoring Dorothy.......'); - const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n); - const events0 = await submitTransactionAsync(alithAccount, tx0); - const result0 = getGenericResult(events0); - expect(result0.success).to.be.true; - console.log('Sponsoring Dorothy.......DONE'); - // <<< Sponsoring Dorothy <<< + randomAccountMoonbeam = helper.account.create(); - const sourceLocation: MultiLocation = api.createType( - 'MultiLocation', - { - parents: 1, - interior: {X1: {Parachain: UNIQUE_CHAIN}}, - }, - ); + // >>> Sponsoring Dorothy >>> + console.log('Sponsoring Dorothy.......'); + await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n); + console.log('Sponsoring Dorothy.......DONE'); + // <<< Sponsoring Dorothy <<< - uniqueAssetLocation = {XCM: sourceLocation}; - const existentialDeposit = 1; - const isSufficient = true; - const unitsPerSecond = '1'; - const numAssetsWeightHint = 0; + uniqueAssetLocation = { + XCM: { + parents: 1, + interior: {X1: {Parachain: UNIQUE_CHAIN}}, + }, + }; + const existentialDeposit = 1n; + const isSufficient = true; + const unitsPerSecond = 1n; + const numAssetsWeightHint = 0; - const registerTx = api.tx.assetManager.registerForeignAsset( - uniqueAssetLocation, - uniqueAssetMetadata, - existentialDeposit, - isSufficient, - ); - console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || ''); - - const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond( - uniqueAssetLocation, - unitsPerSecond, - numAssetsWeightHint, - ); - console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || ''); - - const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]); - console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || ''); - - // >>> Note motion preimage >>> - console.log('Note motion preimage.......'); - const encodedProposal = batchCall?.method.toHex() || ''; - const proposalHash = blake2AsHex(encodedProposal); - console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal); - console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash); - console.log('Encoded length %d', encodedProposal.length); + const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ + location: uniqueAssetLocation, + metadata: uniqueAssetMetadata, + existentialDeposit, + isSufficient, + unitsPerSecond, + numAssetsWeightHint, + }); + const proposalHash = blake2AsHex(encodedProposal); - const tx1 = api.tx.democracy.notePreimage(encodedProposal); - const events1 = await submitTransactionAsync(baltatharAccount, tx1); - const result1 = getGenericResult(events1); - expect(result1.success).to.be.true; - console.log('Note motion preimage.......DONE'); - // <<< Note motion preimage <<< + console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); + console.log('Encoded length %d', encodedProposal.length); + console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash); - // >>> Propose external motion through council >>> - console.log('Propose external motion through council.......'); - const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash); - const tx2 = api.tx.councilCollective.propose( - councilVotingThreshold, - externalMotion, - externalMotion.encodedLength, - ); - const events2 = await submitTransactionAsync(baltatharAccount, tx2); - const result2 = getGenericResult(events2); - expect(result2.success).to.be.true; + // >>> Note motion preimage >>> + console.log('Note motion preimage.......'); + await helper.democracy.notePreimage(baltatharAccount, encodedProposal); + console.log('Note motion preimage.......DONE'); + // <<< Note motion preimage <<< - const encodedMotion = externalMotion?.method.toHex() || ''; - const motionHash = blake2AsHex(encodedMotion); - console.log('Motion hash is %s', motionHash); + // >>> Propose external motion through council >>> + console.log('Propose external motion through council.......'); + const externalMotion = helper.democracy.externalProposeMajority(proposalHash); + const encodedMotion = externalMotion?.method.toHex() || ''; + const motionHash = blake2AsHex(encodedMotion); + console.log('Motion hash is %s', motionHash); - const tx3 = api.tx.councilCollective.vote(motionHash, 0, true); - { - const events3 = await submitTransactionAsync(dorothyAccount, tx3); - const result3 = getGenericResult(events3); - expect(result3.success).to.be.true; - } - { - const events3 = await submitTransactionAsync(baltatharAccount, tx3); - const result3 = getGenericResult(events3); - expect(result3.success).to.be.true; - } + await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength); - const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength); - const events4 = await submitTransactionAsync(dorothyAccount, tx4); - const result4 = getGenericResult(events4); - expect(result4.success).to.be.true; - console.log('Propose external motion through council.......DONE'); - // <<< Propose external motion through council <<< + const councilProposalIdx = await helper.collective.council.proposalCount() - 1; + await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true); + await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true); - // >>> Fast track proposal through technical committee >>> - console.log('Fast track proposal through technical committee.......'); - const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod); - const tx5 = api.tx.techCommitteeCollective.propose( - technicalCommitteeThreshold, - fastTrack, - fastTrack.encodedLength, - ); - const events5 = await submitTransactionAsync(alithAccount, tx5); - const result5 = getGenericResult(events5); - expect(result5.success).to.be.true; + await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength); + console.log('Propose external motion through council.......DONE'); + // <<< Propose external motion through council <<< - const encodedFastTrack = fastTrack?.method.toHex() || ''; - const fastTrackHash = blake2AsHex(encodedFastTrack); - console.log('FastTrack hash is %s', fastTrackHash); + // >>> Fast track proposal through technical committee >>> + console.log('Fast track proposal through technical committee.......'); + const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod); + const encodedFastTrack = fastTrack?.method.toHex() || ''; + const fastTrackHash = blake2AsHex(encodedFastTrack); + console.log('FastTrack hash is %s', fastTrackHash); - const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1; - const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true); - { - const events6 = await submitTransactionAsync(baltatharAccount, tx6); - const result6 = getGenericResult(events6); - expect(result6.success).to.be.true; - } - { - const events6 = await submitTransactionAsync(alithAccount, tx6); - const result6 = getGenericResult(events6); - expect(result6.success).to.be.true; - } + await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength); - const tx7 = api.tx.techCommitteeCollective - .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength); - const events7 = await submitTransactionAsync(baltatharAccount, tx7); - const result7 = getGenericResult(events7); - expect(result7.success).to.be.true; - console.log('Fast track proposal through technical committee.......DONE'); - // <<< Fast track proposal through technical committee <<< + const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1; + await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true); + await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true); - // >>> Referendum voting >>> - console.log('Referendum voting.......'); - const tx8 = api.tx.democracy.vote( - 0, - {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}}, - ); - const events8 = await submitTransactionAsync(dorothyAccount, tx8); - const result8 = getGenericResult(events8); - expect(result8.success).to.be.true; - console.log('Referendum voting.......DONE'); - // <<< Referendum voting <<< + await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength); + console.log('Fast track proposal through technical committee.......DONE'); + // <<< Fast track proposal through technical committee <<< - // >>> Acquire Unique AssetId Info on Moonbeam >>> - console.log('Acquire Unique AssetId Info on Moonbeam.......'); + // >>> Referendum voting >>> + console.log('Referendum voting.......'); + await helper.democracy.referendumVote(dorothyAccount, 0, { + balance: 10_000_000_000_000_000_000n, + vote: {aye: true, conviction: 1}, + }); + console.log('Referendum voting.......DONE'); + // <<< Referendum voting <<< - // Wait for the democracy execute - await waitNewBlocks(api, 5); + // >>> Acquire Unique AssetId Info on Moonbeam >>> + console.log('Acquire Unique AssetId Info on Moonbeam.......'); - assetId = (await api.query.assetManager.assetTypeId({ - XCM: sourceLocation, - })).toString(); + // Wait for the democracy execute + await helper.wait.newBlocks(5); - console.log('UNQ asset ID is %s', assetId); - console.log('Acquire Unique AssetId Info on Moonbeam.......DONE'); - // >>> Acquire Unique AssetId Info on Moonbeam >>> + assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString(); - // >>> Sponsoring random Account >>> - console.log('Sponsoring random Account.......'); - const tx10 = api.tx.balances.transfer(randomAccountMoonbeam.address, 11_000_000_000_000_000_000n); - const events10 = await submitTransactionAsync(baltatharAccount, tx10); - const result10 = getGenericResult(events10); - expect(result10.success).to.be.true; - console.log('Sponsoring random Account.......DONE'); - // <<< Sponsoring random Account <<< + console.log('UNQ asset ID is %s', assetId); + console.log('Acquire Unique AssetId Info on Moonbeam.......DONE'); + // >>> Acquire Unique AssetId Info on Moonbeam >>> - [balanceGlmrTokenInit] = await getBalance(api, [randomAccountMoonbeam.address]); - }, - moonbeamOptions(), - ); + // >>> Sponsoring random Account >>> + console.log('Sponsoring random Account.......'); + await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n); + console.log('Sponsoring random Account.......DONE'); + // <<< Sponsoring random Account <<< - await usingApi(async (api) => { - const tx0 = api.tx.balances.transfer(randomAccountUnique.address, 10n * TRANSFER_AMOUNT); - const events0 = await submitTransactionAsync(uniqueAlice, tx0); - const result0 = getGenericResult(events0); - expect(result0.success).to.be.true; + balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address); + }); - [balanceUniqueTokenInit] = await getBalance(api, [randomAccountUnique.address]); + await usingPlaygrounds(async (helper) => { + await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT); + balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address); }); }); - it('Should connect and send UNQ to Moonbeam', async () => { - await usingApi(async (api) => { - const currencyId = { - NativeAssetId: 'Here', - }; - const dest = { - V1: { - parents: 1, - interior: { - X2: [ - {Parachain: MOONBEAM_CHAIN}, - {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}}, - ], - }, + itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => { + const currencyId = { + NativeAssetId: 'Here', + }; + const dest = { + V1: { + parents: 1, + interior: { + X2: [ + {Parachain: MOONBEAM_CHAIN}, + {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}}, + ], }, - }; - const amount = TRANSFER_AMOUNT; - const destWeight = 850000000; + }, + }; + const amount = TRANSFER_AMOUNT; + const destWeight = 850000000; - const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight); - const events = await submitTransactionAsync(randomAccountUnique, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, destWeight); - [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccountUnique.address]); - expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true; + balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address); + expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true; - const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT; - console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', bigIntToDecimals(transactionFees)); - expect(transactionFees > 0).to.be.true; - }); + const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT; + console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees)); + expect(transactionFees > 0).to.be.true; - await usingApi( - async (api) => { - await waitNewBlocks(api, 3); + await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { + await helper.wait.newBlocks(3); - [balanceGlmrTokenMiddle] = await getBalance(api, [randomAccountMoonbeam.address]); + balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address); - const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle; - console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees)); - expect(glmrFees == 0n).to.be.true; + const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle; + console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees)); + expect(glmrFees == 0n).to.be.true; - const unqRandomAccountAsset = ( - await api.query.assets.account(assetId, randomAccountMoonbeam.address) - ).toJSON()! as any; + balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!; - balanceForeignUnqTokenMiddle = BigInt(unqRandomAccountAsset['balance']); - const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit; - console.log('[Unique -> Moonbeam] income %s UNQ', bigIntToDecimals(unqIncomeTransfer)); - expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true; - }, - moonbeamOptions(), - ); + const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit; + console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer)); + expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true; + }); }); - it('Should connect to Moonbeam and send UNQ back', async () => { - await usingApi( - async (api) => { - const asset = { - V1: { - id: { - Concrete: { - parents: 1, - interior: { - X1: {Parachain: UNIQUE_CHAIN}, - }, + itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => { + await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { + const asset = { + V1: { + id: { + Concrete: { + parents: 1, + interior: { + X1: {Parachain: UNIQUE_CHAIN}, }, }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, }, - }; - const destination = { - V1: { - parents: 1, - interior: { - X2: [ - {Parachain: UNIQUE_CHAIN}, - {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}}, - ], - }, + fun: { + Fungible: TRANSFER_AMOUNT, }, - }; - const destWeight = 50000000; - - const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight); - const events = await submitTransactionAsync(randomAccountMoonbeam, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + }, + }; + const destination = { + V1: { + parents: 1, + interior: { + X2: [ + {Parachain: UNIQUE_CHAIN}, + {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}}, + ], + }, + }, + }; + const destWeight = 50000000; - [balanceGlmrTokenFinal] = await getBalance(api, [randomAccountMoonbeam.address]); + await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, destWeight); - const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal; - console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees)); - expect(glmrFees > 0).to.be.true; + balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address); - const unqRandomAccountAsset = ( - await api.query.assets.account(assetId, randomAccountMoonbeam.address) - ).toJSON()! as any; + const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal; + console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees)); + expect(glmrFees > 0).to.be.true; - expect(unqRandomAccountAsset).to.be.null; + const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address); - balanceForeignUnqTokenFinal = 0n; + expect(unqRandomAccountAsset).to.be.null; + + balanceForeignUnqTokenFinal = 0n; - const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal; - console.log('[Unique -> Moonbeam] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer)); - expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; - }, - moonbeamOptions(), - ); + const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal; + console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer)); + expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; + }); - await usingApi(async (api) => { - await waitNewBlocks(api, 3); + await helper.wait.newBlocks(3); - [balanceUniqueTokenFinal] = await getBalance(api, [randomAccountUnique.address]); - const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle; - expect(actuallyDelivered > 0).to.be.true; + balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address); + const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle; + expect(actuallyDelivered > 0).to.be.true; - console.log('[Moonbeam -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered)); + console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered)); - const unqFees = TRANSFER_AMOUNT - actuallyDelivered; - console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees)); - expect(unqFees == 0n).to.be.true; - }); + const unqFees = TRANSFER_AMOUNT - actuallyDelivered; + console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees)); + expect(unqFees == 0n).to.be.true; }); });