From 5aae9b6cfe20866e2ff8f295e28bdd5ad01fede3 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Wed, 11 Dec 2024 19:03:19 +0100 Subject: [PATCH] fix(tests): xcm tests and related stuff --- .baedeker/rewrites.example.jsonnet | 2 +- .baedeker/xcm-opal.jsonnet | 102 +- .baedeker/xcm-quartz.jsonnet | 115 +- .baedeker/xcm-unique.jsonnet | 128 +- .env | 18 +- .github/workflows/xcm.yml | 7 +- js-packages/scripts/createHrmp.ts | 23 +- js-packages/scripts/generateEnv.ts | 4 +- js-packages/test-utils/index.ts | 44 +- js-packages/test-utils/util.ts | 26 +- js-packages/test-utils/xcm/index.ts | 33 +- js-packages/tests/config.ts | 6 +- js-packages/tests/package.json | 5 +- ...wLevelXcmQuartz.test.ts => quartz.test.ts} | 258 ++- ...wLevelXcmUnique.test.ts => unique.test.ts} | 353 ++-- js-packages/tests/xcm/xcm.types.ts | 829 ++++---- js-packages/tests/xcm/xcmOpal.test.ts | 431 ---- js-packages/tests/xcm/xcmQuartz.test.ts | 1637 --------------- js-packages/tests/xcm/xcmUnique.test.ts | 1836 ----------------- 19 files changed, 1085 insertions(+), 4772 deletions(-) rename js-packages/tests/xcm/{lowLevelXcmQuartz.test.ts => quartz.test.ts} (56%) rename js-packages/tests/xcm/{lowLevelXcmUnique.test.ts => unique.test.ts} (62%) delete mode 100644 js-packages/tests/xcm/xcmOpal.test.ts delete mode 100644 js-packages/tests/xcm/xcmQuartz.test.ts delete mode 100644 js-packages/tests/xcm/xcmUnique.test.ts diff --git a/.baedeker/rewrites.example.jsonnet b/.baedeker/rewrites.example.jsonnet index 1ee5d08784..81482b742e 100644 --- a/.baedeker/rewrites.example.jsonnet +++ b/.baedeker/rewrites.example.jsonnet @@ -12,7 +12,7 @@ function(prev, repoDir) 'bin/polkadot': { dockerImage: 'parity/polkadot:%s' % dotenv.POLKADOT_MAINNET_BRANCH }, 'bin/acala': { dockerImage: 'acala/acala-node:%s' % dotenv.ACALA_BUILD_BRANCH }, 'bin/moonbeam': { dockerImage: 'moonbeamfoundation/moonbeam:%s' % dotenv.MOONBEAM_BUILD_BRANCH }, - 'bin/assethub': { dockerImage: 'parity/polkadot-parachain:%s' % dotenv.STATEMINE_BUILD_BRANCH }, + 'bin/assethub': { dockerImage: 'parity/polkadot-parachain:%s' % dotenv.KUSAMA_ASSETHUB_BUILD_BRANCH }, 'bin/astar': { dockerImage: 'staketechnologies/astar-collator:%s' % dotenv.ASTAR_BUILD_BRANCH }, 'bin/polkadex': { dockerImage: 'polkadex/mainnet:%s' % dotenv.POLKADEX_BUILD_BRANCH }, 'bin/hydradx': { dockerImage: 'galacticcouncil/hydra-dx:%s' % dotenv.HYDRADX_BUILD_BRANCH }, diff --git a/.baedeker/xcm-opal.jsonnet b/.baedeker/xcm-opal.jsonnet index 1c7c724b54..4d17a1d48f 100644 --- a/.baedeker/xcm-opal.jsonnet +++ b/.baedeker/xcm-opal.jsonnet @@ -5,30 +5,50 @@ m = import 'baedeker-library/mixin/spec.libsonnet', function(relay_spec) local relay = { - name: 'relay', - bin: 'bin/polkadot', - validatorIdAssignment: 'staking', - spec: {Genesis:{ - chain: relay_spec, - modify:: m.genericRelay($, hrmp = [ - // [$.parachains.opal.paraId, $.parachains.westmint.paraId, 8, 512], - // [$.parachains.westmint.paraId, $.parachains.opal.paraId, 8, 512], - ]), - }}, - nodes: { - [name]: { - bin: $.bin, - wantedKeys: 'relay', - expectedDataPath: '/parity', - }, - for name in ['alice', 'bob', 'charlie', 'dave', 'eve'] - }, + name: 'relay', + bin: 'bin/polkadot', + validatorIdAssignment: 'staking', + spec: {Genesis:{ + chain: relay_spec, + modify:: bdk.mixer([ + m.genericRelay($), + { + genesis+: { + runtimeGenesis+: { + runtime+: { + configuration+: { + config+: { + async_backing_params+: { + allowed_ancestry_len: 3, + max_candidate_depth: 4, + }, + scheduling_lookahead:5, + max_validators_per_core:2, + minimum_backing_votes:2, + needed_approvals:2, + on_demand_cores:5, + }, + }, + }, + }, + }, + }, + ]), + }}, + nodes: { + [name]: { + bin: $.bin, + wantedKeys: 'relay', + expectedDataPath: '/parity', + }, + for name in ['alice', 'bob', 'charlie', 'dave', 'eve'] + }, }; -local unique = { - name: 'unique', +local opal = { + name: 'opal', bin: 'bin/unique', - paraId: 1001, + paraId: 2037, spec: {Genesis:{ modify:: m.genericPara($), }}, @@ -40,32 +60,32 @@ local unique = { '--increase-future-pool', ], }, - for name in ['alice', 'bob'] + for name in ['alice', 'bob', 'charlie'] }, }; local assethub = { - name: 'assethub', - bin: 'bin/assethub', - paraId: 1002, - spec: {Genesis:{ - chain: 'asset-hub-westend-local', - modify:: m.genericPara($), - }}, - nodes: { - [name]: { - bin: $.bin, - wantedKeys: 'para', - parentConnection: 'internal-samedir', + name: 'assethub', + bin: 'bin/assethub', + paraId: 1000, + spec: {Genesis:{ + chain: 'asset-hub-westend-local', + modify:: m.genericPara($), + }}, + nodes: { + [name]: { + bin: $.bin, + wantedKeys: 'para', + parentConnection: 'internal-samedir', expectedDataPath: '/parity', - }, - for name in ['alice', 'bob'] - }, + }, + for name in ['alice', 'bob'] + }, }; relay + { - parachains: { - [para.name]: para, - for para in [unique, assethub] - }, + parachains: { + [para.name]: para, + for para in [opal, assethub] + }, } diff --git a/.baedeker/xcm-quartz.jsonnet b/.baedeker/xcm-quartz.jsonnet index fa9982a8a8..b17560671a 100644 --- a/.baedeker/xcm-quartz.jsonnet +++ b/.baedeker/xcm-quartz.jsonnet @@ -5,35 +5,50 @@ m = import 'baedeker-library/mixin/spec.libsonnet', function(relay_spec, assethub_spec) local relay = { - name: 'relay', - bin: 'bin/polkadot', - validatorIdAssignment: 'staking', - spec: {Genesis:{ - chain: relay_spec, - modify:: m.genericRelay($, hrmp = std.join([], [ - // [[$.parachains[a].paraId, $.parachains[b].paraId, 8, 512], [$.parachains[b].paraId, $.parachains[a].paraId, 8, 512]], - // for [a, b] in [ - // ['quartz', 'karura'], - // ['quartz', 'moonriver'], - // ['quartz', 'statemine'], - // ['quartz', 'shiden'], - // ] - ])), - }}, - nodes: { - [name]: { - bin: $.bin, - wantedKeys: 'relay', - expectedDataPath: '/parity', - }, - for name in ['alice', 'bob', 'charlie', 'dave', 'eve', 'ferdie', 'gregory', 'holly', 'iggy', 'john', 'kurt'] - }, + name: 'relay', + bin: 'bin/polkadot', + validatorIdAssignment: 'staking', + spec: {Genesis:{ + chain: relay_spec, + modify:: bdk.mixer([ + m.genericRelay($), + { + genesis+: { + runtimeGenesis+: { + runtime+: { + configuration+: { + config+: { + async_backing_params+: { + allowed_ancestry_len: 3, + max_candidate_depth: 4, + }, + scheduling_lookahead:5, + max_validators_per_core:2, + minimum_backing_votes:2, + needed_approvals:2, + on_demand_cores:5, + }, + }, + }, + }, + }, + }, + ]), + }}, + nodes: { + [name]: { + bin: $.bin, + wantedKeys: 'relay', + expectedDataPath: '/parity', + }, + for name in ['alice', 'bob', 'charlie', 'dave', 'eve'] + }, }; -local unique = { - name: 'unique', +local quartz = { + name: 'quartz', bin: 'bin/unique', - paraId: 1001, + paraId: 2095, spec: {Genesis:{ modify:: m.genericPara($), }}, @@ -49,10 +64,30 @@ local unique = { }, }; +local assethub = { + name: 'assethub', + bin: 'bin/assethub', + paraId: 1000, + spec: { + FromScratchGenesis: { + spec: m.genericPara($)(assethub_spec), + } + }, + nodes: { + [name]: { + bin: $.bin, + wantedKeys: 'para', + parentConnection: 'internal-samedir', + expectedDataPath: '/parity', + }, + for name in ['alice', 'bob'] + }, +}; + local karura = { name: 'karura', bin: 'bin/acala', - paraId: 1002, + paraId: 2000, spec: {Genesis:{ chain: 'karura-dev', modify:: bdk.mixer([ @@ -75,7 +110,7 @@ local moonriver = { name: 'moonriver', bin: 'bin/moonbeam', signatureSchema: 'Ethereum', - paraId: 1003, + paraId: 2023, spec: {Genesis:{ chain: 'moonriver-local', specFilePrefix: 'moonriver-local-', @@ -92,30 +127,10 @@ local moonriver = { }, }; -local assethub = { - name: 'assethub', - bin: 'bin/assethub', - paraId: 1004, - spec: { - FromScratchGenesis: { - spec: m.genericPara($)(assethub_spec), - } - }, - nodes: { - [name]: { - bin: $.bin, - wantedKeys: 'para', - parentConnection: 'internal-samedir', - expectedDataPath: '/parity', - }, - for name in ['alice', 'bob'] - }, -}; - local shiden = { name: 'shiden', bin: 'bin/astar', - paraId: 1005, + paraId: 2007, spec: {Genesis:{ chain: 'shiden-dev', modify:: m.genericPara($), @@ -134,6 +149,6 @@ local shiden = { relay + { parachains: { [para.name]: para, - for para in [unique, karura, moonriver, assethub, shiden] + for para in [quartz, assethub, karura, moonriver, shiden] }, } diff --git a/.baedeker/xcm-unique.jsonnet b/.baedeker/xcm-unique.jsonnet index 04356ddba8..4e239c2dd5 100644 --- a/.baedeker/xcm-unique.jsonnet +++ b/.baedeker/xcm-unique.jsonnet @@ -5,36 +5,50 @@ m = import 'baedeker-library/mixin/spec.libsonnet', function(relay_spec, assethub_spec) local relay = { - name: 'relay', - bin: 'bin/polkadot', - validatorIdAssignment: 'staking', - spec: {Genesis:{ - chain: relay_spec, - modify:: m.genericRelay($, hrmp = std.join([], [ - // [[$.parachains[a].paraId, $.parachains[b].paraId, 8, 512], [$.parachains[b].paraId, $.parachains[a].paraId, 8, 512]], - // for [a, b] in [ - // ['unique', 'acala'], - // ['unique', 'moonbeam'], - // ['unique', 'statemint'], - // ['unique', 'astar'], - // ['unique', 'polkadex'], - // ] - ])), - }}, - nodes: { - [name]: { - bin: $.bin, - wantedKeys: 'relay', - expectedDataPath: '/parity', - }, - for name in ['alice', 'bob', 'charlie', 'dave', 'eve', 'ferdie', 'gregory', 'holly', 'iggy', 'john', 'kurt', 'larry', 'mike', 'norman', 'osvald'] - }, + name: 'relay', + bin: 'bin/polkadot', + validatorIdAssignment: 'staking', + spec: {Genesis:{ + chain: relay_spec, + modify:: bdk.mixer([ + m.genericRelay($), + { + genesis+: { + runtimeGenesis+: { + runtime+: { + configuration+: { + config+: { + async_backing_params+: { + allowed_ancestry_len: 3, + max_candidate_depth: 4, + }, + scheduling_lookahead:5, + max_validators_per_core:2, + minimum_backing_votes:2, + needed_approvals:2, + on_demand_cores:5, + }, + }, + }, + }, + }, + }, + ]), + }}, + nodes: { + [name]: { + bin: $.bin, + wantedKeys: 'relay', + expectedDataPath: '/parity', + }, + for name in ['alice', 'bob', 'charlie', 'dave', 'eve'] + }, }; local unique = { name: 'unique', bin: 'bin/unique', - paraId: 1001, + paraId: 2037, spec: {Genesis:{ modify:: m.genericPara($), }}, @@ -50,10 +64,30 @@ local unique = { }, }; +local assethub = { + name: 'assethub', + bin: 'bin/assethub', + paraId: 1000, + spec: { + FromScratchGenesis: { + spec: m.genericPara($)(assethub_spec), + } + }, + nodes: { + [name]: { + bin: $.bin, + wantedKeys: 'para-ed', + parentConnection: 'internal-samedir', + expectedDataPath: '/parity', + }, + for name in ['alice', 'bob'] + }, +}; + local acala = { name: 'acala', bin: 'bin/acala', - paraId: 1002, + paraId: 2000, spec: {Genesis:{ chain: 'acala-dev', modify:: bdk.mixer([ @@ -76,7 +110,7 @@ local moonbeam = { name: 'moonbeam', bin: 'bin/moonbeam', signatureSchema: 'Ethereum', - paraId: 1003, + paraId: 2004, spec: {Genesis:{ chain: 'moonbeam-local', specFilePrefix: 'moonbeam-local-', @@ -93,30 +127,10 @@ local moonbeam = { }, }; -local assethub = { - name: 'assethub', - bin: 'bin/assethub', - paraId: 1004, - spec: { - FromScratchGenesis: { - spec: m.genericPara($)(assethub_spec), - } - }, - nodes: { - [name]: { - bin: $.bin, - wantedKeys: 'para-ed', - parentConnection: 'internal-samedir', - expectedDataPath: '/parity', - }, - for name in ['alice', 'bob'] - }, -}; - local astar = { name: 'astar', bin: 'bin/astar', - paraId: 1005, + paraId: 2006, spec: {Genesis:{ chain: 'astar-dev', modify:: m.genericPara($), @@ -135,7 +149,7 @@ local astar = { local polkadex = { name: 'polkadex', bin: 'bin/polkadex', - paraId: 1006, + paraId: 2040, spec: {Genesis:{ chain: 'mainnet', modify:: m.genericPara($), @@ -152,10 +166,10 @@ local polkadex = { }, }; -local hydraDx = { - name: 'hydraDx', +local hydration = { + name: 'hydration', bin: 'bin/hydradx', - paraId: 1007, + paraId: 2034, spec: {Genesis:{ chain: 'local', modify:: m.genericPara($), @@ -175,6 +189,14 @@ local hydraDx = { relay + { parachains: { [para.name]: para, - for para in [unique, acala, moonbeam, assethub, astar, polkadex, hydraDx] + for para in [ + unique, + acala, + moonbeam, + assethub, + astar, + polkadex, + hydration, + ] }, } diff --git a/.env b/.env index 90bcc51a8a..5efa385b8e 100644 --- a/.env +++ b/.env @@ -9,24 +9,24 @@ MAINNET_HASH=387b0373b6f516481e7df757c7d436465b67ae2c # Unique POLKADOT_MAINNET_BRANCH=v1.14.0 -STATEMINT_BUILD_BRANCH=1.14.0 +POLKADOT_ASSETHUB_BUILD_BRANCH=1.14.0 ACALA_BUILD_BRANCH=2.25.0 -MOONBEAM_BUILD_BRANCH=runtime-2901 -ASTAR_BUILD_BRANCH=v5.39.1 -HYDRADX_BUILD_BRANCH=latest@sha256:50d9e0cfb582af6058f298479981a10f3e3c3da2d49995d2b0d31292f08b2589 -POLKADEX_BUILD_BRANCH=v1.0.0 +MOONBEAM_BUILD_BRANCH=runtime-3300 +ASTAR_BUILD_BRANCH=v5.45.0 +HYDRADX_BUILD_BRANCH=latest@sha256:6a7b285f66352ed13086071fa4b305b9b919b5851d203450538419b5e3bd9b6e +POLKADEX_BUILD_BRANCH=v1.0.0 UNIQUE_REPLICA_FROM=wss://ws.unique.network:443 # Quartz KUSAMA_MAINNET_BRANCH=v1.14.0 -STATEMINE_BUILD_BRANCH=1.14.0 +KUSAMA_ASSETHUB_BUILD_BRANCH=1.14.0 KARURA_BUILD_BRANCH=2.25.0 -MOONRIVER_BUILD_BRANCH=runtime-2901 -SHIDEN_BUILD_BRANCH=v5.39.1 +MOONRIVER_BUILD_BRANCH=runtime-3300 +SHIDEN_BUILD_BRANCH=v5.45.0 QUARTZ_REPLICA_FROM=wss://ws-quartz.unique.network:443 # Opal UNIQUEWEST_MAINNET_BRANCH=v1.14.0 -WESTMINT_BUILD_BRANCH=1.14.0 +WESTEND_ASSETHUB_BUILD_BRANCH=1.14.0 OPAL_REPLICA_FROM=wss://ws-opal.unique.network:443 diff --git a/.github/workflows/xcm.yml b/.github/workflows/xcm.yml index ea6d9e50ba..1d6927f5d3 100644 --- a/.github/workflows/xcm.yml +++ b/.github/workflows/xcm.yml @@ -37,9 +37,8 @@ jobs: id: create_matrix with: matrix: | - network {opal}, relay_name {polkadot}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, assethub_version {${{ env.WESTMINT_BUILD_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, acala_repository {acala-node}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, runtest {testXcmOpal}, runs_on {ci} - network {quartz}, relay_name {kusama}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, assethub_version {${{ env.STATEMINE_BUILD_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, acala_repository {karura-node}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, runtest {testFullXcmQuartz}, runs_on {L} - network {unique}, relay_name {polkadot}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, assethub_version {${{ env.STATEMINT_BUILD_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, acala_repository {acala-node}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runs_on {XL} + network {quartz}, relay_name {kusama}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, assethub_version {${{ env.KUSAMA_ASSETHUB_BUILD_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, acala_repository {karura-node}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, runtest {testXcmQuartz}, runs_on {L} + network {unique}, relay_name {polkadot}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, assethub_version {${{ env.POLKADOT_ASSETHUB_BUILD_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, acala_repository {acala-node}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, runtest {testXcmUnique}, runs_on {XL} xcm: needs: prepare-execution-marix @@ -278,4 +277,4 @@ jobs: - name: Remove builder cache if: always() run: | - docker system prune -a -f \ No newline at end of file + docker system prune -a -f diff --git a/js-packages/scripts/createHrmp.ts b/js-packages/scripts/createHrmp.ts index 7a131d0543..ec5ab725d5 100644 --- a/js-packages/scripts/createHrmp.ts +++ b/js-packages/scripts/createHrmp.ts @@ -12,22 +12,19 @@ await usingPlaygrounds(async (helper, privateKey) => { }; const alice = await privateKey('//Alice'); switch (profile) { - case 'opal': - await bidirOpen(1001, 1002); - break; case 'quartz': - await bidirOpen(1001, 1002); - await bidirOpen(1001, 1003); - await bidirOpen(1001, 1004); - await bidirOpen(1001, 1005); + await bidirOpen(2095, 1000); + await bidirOpen(2095, 2000); + await bidirOpen(2095, 2023); + await bidirOpen(2095, 2007); break; case 'unique': - await bidirOpen(1001, 1002); - await bidirOpen(1001, 1003); - await bidirOpen(1001, 1004); - await bidirOpen(1001, 1005); - await bidirOpen(1001, 1006); - await bidirOpen(1001, 1007); + await bidirOpen(2037, 1000); + await bidirOpen(2037, 2000); + await bidirOpen(2037, 2004); + await bidirOpen(2037, 2006); + await bidirOpen(2037, 2040); + await bidirOpen(2037, 2034); break; default: throw new Error(`unknown hrmp config profile: ${profile}`); diff --git a/js-packages/scripts/generateEnv.ts b/js-packages/scripts/generateEnv.ts index 51bc360687..367a8ae8f2 100644 --- a/js-packages/scripts/generateEnv.ts +++ b/js-packages/scripts/generateEnv.ts @@ -43,20 +43,18 @@ function fixupUnique(version: string): string { // Version from polkadot-fellows // ff('wss://rpc.polkadot.io/', /^(.)(...)(...)$/, 'v$1.$2.$3').then(v => env = setVar(env, 'POLKADOT_MAINNET_BRANCH', v)), ff('wss://rococo-rpc.polkadot.io/', /^(.)(...)(...)$/, 'v$1.$2.$3').then(v => env = setVar(env, 'POLKADOT_MAINNET_BRANCH', v)), - // ff('wss://statemint-rpc.polkadot.io/', /^(....)$/, 'release-parachains-v$1').then(v => env = setVar(env, 'STATEMINT_BUILD_BRANCH', v)), ff('wss://acala-rpc-0.aca-api.network/', /^(.)(..)(.)$/, '$1.$2.$3').then(v => env = setVar(env, 'ACALA_BUILD_BRANCH', v)), ff('wss://wss.api.moonbeam.network/', /^(....)$/, 'runtime-$1').then(v => env = setVar(env, 'MOONBEAM_BUILD_BRANCH', v)), ff('wss://ws.unique.network/', /^(........)$/, 'release-v$1').then(v => env = setVar(env, 'MAINNET_BRANCH', fixupUnique(v))), // ff('wss://kusama-rpc.polkadot.io/', /^(.)(...)(...)$/, 'v$1.$2.$3').then(v => env = setVar(env, 'KUSAMA_MAINNET_BRANCH', v)), ff('wss://rococo-rpc.polkadot.io/', /^(.)(...)(...)$/, 'v$1.$2.$3').then(v => env = setVar(env, 'KUSAMA_MAINNET_BRANCH', v)), - // ff('wss://statemine-rpc.polkadot.io/', /^(....)$/, 'release-parachains-v$1').then(v => env = setVar(env, 'STATEMINE_BUILD_BRANCH', v)), ff('wss://karura-rpc-0.aca-api.network/', /^(.)(..)(.)$/, 'release-karura-$1.$2.$3').then(v => env = setVar(env, 'KARURA_BUILD_BRANCH', v)), ff('wss://wss.api.moonriver.moonbeam.network/', /^(....)$/, 'runtime-$1').then(v => env = setVar(env, 'MOONRIVER_BUILD_BRANCH', v)), ff('wss://ws-quartz.unique.network/', /^(........)$/, 'release-v$1').then(v => env = setVar(env, 'MAINNET_BRANCH', fixupUnique(v))), ff('wss://eu-ws-westend.unique.network/', /^(.)(..)(.)$/, 'release-v$1.$2.$3').then(v => env = setVar(env, 'UNIQUEWEST_MAINNET_BRANCH', v)), - ff('wss://westmint-rpc.polkadot.io/', /^(.......)$/, 'bad-branch-v$1').then(v => env = setVar(env, 'WESTMINT_BUILD_BRANCH', v)), + ff('wss://westend-asset-hub-rpc.polkadot.io/', /^(.......)$/, 'bad-branch-v$1').then(v => env = setVar(env, 'WESTEND_ASSETHUB_BUILD_BRANCH', v)), ff('wss://ws-opal.unique.network/', /^(........)$/, 'release-v$1').then(v => env = setVar(env, 'MAINNET_BRANCH', fixupUnique(v))), ff('wss://rpc.astar.network/', /^(.+)$/, (_, r) => { diff --git a/js-packages/test-utils/index.ts b/js-packages/test-utils/index.ts index 5b4ba793ea..85f39e7193 100644 --- a/js-packages/test-utils/index.ts +++ b/js-packages/test-utils/index.ts @@ -17,7 +17,7 @@ import type {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransaction import type {FrameSystemEventRecord, XcmV2TraitsError, StagingXcmV4TraitsOutcome} from '@polkadot/types/lookup'; import type {SignerOptions, VoidFn} from '@polkadot/api/types'; import {spawnSync} from 'child_process'; -import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup, HydraDxHelper} from './xcm/index.js'; +import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestendAssetHubHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup, HydraDxHelper} from './xcm/index.js'; import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './governance.js'; import type {ICollectiveGroup, IFellowshipGroup} from './governance.js'; @@ -112,7 +112,7 @@ function EventHelper(section: string, method: string, wrapEvent: (data: any[]) = if(e) { return e; } else { - throw Error(`Expected event ${section}.${method}`); + throw new Error(`Expected event ${section}.${method}`); } } }; @@ -272,14 +272,12 @@ export class Event { static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({ messageHash: eventJsonData(data, 0), })); + }; - static Success = this.Method('Success', data => ({ - messageHash: eventJsonData(data, 0), - })); - - static Fail = this.Method('Fail', data => ({ + static MessageQueue = class extends EventSection('messageQueue') { + static Processed = this.Method('Processed', data => ({ messageHash: eventJsonData(data, 0), - outcome: eventData(data, 2), + success: eventJsonData(data, 3), })); }; @@ -600,20 +598,20 @@ export class DevRelayHelper extends RelayHelper { } } -export class DevWestmintHelper extends WestmintHelper { +export class DevWestendAssetHubHelper extends WestendAssetHubHelper { wait: WaitGroup; constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.helperBase = options.helperBase ?? DevWestmintHelper; + options.helperBase = options.helperBase ?? DevWestendAssetHubHelper; super(logger, options); this.wait = new WaitGroup(this); } } -export class DevStatemineHelper extends DevWestmintHelper {} +export class DevKusamaAssetHubHelper extends DevWestendAssetHubHelper {} -export class DevStatemintHelper extends DevWestmintHelper {} +export class DevPolkadotAssetHubHelper extends DevWestendAssetHubHelper {} export class DevMoonbeamHelper extends MoonbeamHelper { account: MoonbeamAccountGroup; @@ -931,7 +929,7 @@ export class ArrangeGroup { makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) { return { - V2: [ + V4: [ { WithdrawAsset: [ { @@ -958,16 +956,10 @@ export class ArrangeGroup { assets: { Wild: 'All', }, - maxAssets: 1, beneficiary: { parents: 0, interior: { - X1: { - AccountId32: { - network: 'Any', - id: beneficiary, - }, - }, + X1: [{AccountId32: {id: beneficiary}}], }, }, }, @@ -978,7 +970,7 @@ export class ArrangeGroup { makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) { return { - V2: [ + V4: [ { ReserveAssetDeposited: [ { @@ -1005,16 +997,10 @@ export class ArrangeGroup { assets: { Wild: 'All', }, - maxAssets: 1, beneficiary: { parents: 0, interior: { - X1: { - AccountId32: { - network: 'Any', - id: beneficiary, - }, - }, + X1: [{AccountId32: {id: beneficiary}}], }, }, }, @@ -1025,7 +1011,7 @@ export class ArrangeGroup { makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) { return { - V3: [ + V4: [ { UnpaidExecution: { weightLimit: 'Unlimited', diff --git a/js-packages/test-utils/util.ts b/js-packages/test-utils/util.ts index a64643a1df..a45e963252 100644 --- a/js-packages/test-utils/util.ts +++ b/js-packages/test-utils/util.ts @@ -11,7 +11,7 @@ import {Context} from 'mocha'; import config from '../tests/config.js'; import {ChainHelperBase} from '@unique-nft/playgrounds/unique.js'; import type {ILogger} from '@unique-nft/playgrounds/types.js'; -import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper, DevHydraDxHelper} from '@unique/test-utils'; +import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestendAssetHubHelper, DevKusamaAssetHubHelper, DevPolkadotAssetHubHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper, DevHydraDxHelper} from '@unique/test-utils'; import {dirname} from 'path'; import {fileURLToPath} from 'url'; @@ -69,29 +69,29 @@ async function usingPlaygroundsGeneral( export const usingPlaygrounds = (code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => Promise) => Promise, url: string = config.substrateUrl) => usingPlaygroundsGeneral(DevUniqueHelper, url, code); -export const usingWestmintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevWestmintHelper, url, code); +export const usingWestendAssetHubPlaygrounds = (code: (helper: DevWestendAssetHubHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.westendAssetHubUrl) => usingPlaygroundsGeneral(DevWestendAssetHubHelper, url, code); -export const usingStateminePlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevWestmintHelper, url, code); +export const usingKusamaAssetHubPlaygrounds = (code: (helper: DevWestendAssetHubHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.kusamaAssetHubUrl) => usingPlaygroundsGeneral(DevWestendAssetHubHelper, url, code); -export const usingStatemintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevWestmintHelper, url, code); +export const usingPolkadotAssetHubPlaygrounds = (code: (helper: DevWestendAssetHubHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.polkadotAssetHubUrl) => usingPlaygroundsGeneral(DevWestendAssetHubHelper, url, code); -export const usingRelayPlaygrounds = (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevRelayHelper, url, code); +export const usingRelayPlaygrounds = (code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.relayUrl) => usingPlaygroundsGeneral(DevRelayHelper, url, code); -export const usingAcalaPlaygrounds = (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevAcalaHelper, url, code); +export const usingAcalaPlaygrounds = (code: (helper: DevAcalaHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.acalaUrl) => usingPlaygroundsGeneral(DevAcalaHelper, url, code); -export const usingKaruraPlaygrounds = (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevAcalaHelper, url, code); +export const usingKaruraPlaygrounds = (code: (helper: DevKaruraHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.karuraUrl) => usingPlaygroundsGeneral(DevAcalaHelper, url, code); -export const usingMoonbeamPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevMoonbeamHelper, url, code); +export const usingMoonbeamPlaygrounds = (code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.moonbeamUrl) => usingPlaygroundsGeneral(DevMoonbeamHelper, url, code); -export const usingMoonriverPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevMoonriverHelper, url, code); +export const usingMoonriverPlaygrounds = (code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.moonriverUrl) => usingPlaygroundsGeneral(DevMoonriverHelper, url, code); -export const usingAstarPlaygrounds = (url: string, code: (helper: DevAstarHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevAstarHelper, url, code); +export const usingAstarPlaygrounds = (code: (helper: DevAstarHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.astarUrl) => usingPlaygroundsGeneral(DevAstarHelper, url, code); -export const usingShidenPlaygrounds = (url: string, code: (helper: DevShidenHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevShidenHelper, url, code); +export const usingShidenPlaygrounds = (code: (helper: DevShidenHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.shidenUrl) => usingPlaygroundsGeneral(DevShidenHelper, url, code); -export const usingPolkadexPlaygrounds = (url: string, code: (helper: DevPolkadexHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevPolkadexHelper, url, code); +export const usingPolkadexPlaygrounds = (code: (helper: DevPolkadexHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.polkadexUrl) => usingPlaygroundsGeneral(DevPolkadexHelper, url, code); -export const usingHydraDxPlaygrounds = (url: string, code: (helper: DevHydraDxHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevHydraDxHelper, url, code); +export const usingHydraDxPlaygrounds = (code: (helper: DevHydraDxHelper, privateKey: (seed: string) => Promise) => Promise, url: string = config.hydraDxUrl) => usingPlaygroundsGeneral(DevHydraDxHelper, url, code); export const MINIMUM_DONOR_FUND = 4_000_000n; export const DONOR_FUNDING = 4_000_000n; diff --git a/js-packages/test-utils/xcm/index.ts b/js-packages/test-utils/xcm/index.ts index a48e6b233f..05738a11c9 100644 --- a/js-packages/test-utils/xcm/index.ts +++ b/js-packages/test-utils/xcm/index.ts @@ -17,8 +17,8 @@ export class XcmChainHelper extends ChainHelperBase { } class AcalaAssetRegistryGroup extends HelperGroup { - async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) { - await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true); + async registerForeignAsset(signer: TSigner, location: any, metadata: AcalaAssetMetadata) { + await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [location, metadata], true); } } @@ -158,7 +158,7 @@ export class ForeignAssetsGroup extends HelperGroup { await this.helper.executeExtrinsic( signer, 'api.tx.foreignAssets.forceRegisterForeignAsset', - [{V3: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode], + [{V4: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode], true, ); } @@ -177,8 +177,12 @@ export class XcmGroup extends HelperGroup { this.palletName = palletName; } + async transferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) { + return await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.transferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true); + } + async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true); + return await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true); } async setSafeXcmVersion(signer: TSigner, version: number) { @@ -189,6 +193,7 @@ export class XcmGroup extends HelperGroup { await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true); } + // TODO async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) { const destinationContent = { parents: 0, @@ -270,6 +275,10 @@ export class XTokensGroup extends HelperGroup { await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true); } + async transferMultiassets(signer: TSigner, assets: any, feeItem: number, destination: any, destWeight: any) { + return await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiassets', [assets, feeItem, destination, destWeight], true); + } + async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) { await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true); } @@ -330,14 +339,14 @@ export class RelayHelper extends XcmChainHelper { } } -export class WestmintHelper extends XcmChainHelper { - balance: SubstrateBalanceGroup; - xcm: XcmGroup; - assets: AssetsGroup; - xTokens: XTokensGroup; +export class WestendAssetHubHelper extends XcmChainHelper { + balance: SubstrateBalanceGroup; + xcm: XcmGroup; + assets: AssetsGroup; + xTokens: XTokensGroup; constructor(logger?: ILogger, options: { [key: string]: any } = {}) { - super(logger, options.helperBase ?? WestmintHelper); + super(logger, options.helperBase ?? WestendAssetHubHelper); this.balance = new SubstrateBalanceGroup(this); this.xcm = new XcmGroup(this, 'polkadotXcm'); @@ -351,6 +360,7 @@ export class MoonbeamHelper extends XcmChainHelper { assetManager: MoonbeamAssetManagerGroup; assets: AssetsGroup; xTokens: XTokensGroup; + xcm: XcmGroup; democracy: MoonbeamDemocracyGroup; collective: { council: MoonbeamCollectiveGroup, @@ -364,6 +374,7 @@ export class MoonbeamHelper extends XcmChainHelper { this.assetManager = new MoonbeamAssetManagerGroup(this); this.assets = new AssetsGroup(this); this.xTokens = new XTokensGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); this.democracy = new MoonbeamDemocracyGroup(this, options); this.collective = { council: new MoonbeamCollectiveGroup(this, 'councilCollective'), @@ -376,12 +387,14 @@ export class AstarHelper extends XcmChainHelper { balance: SubstrateBalanceGroup; assets: AssetsGroup; xcm: XcmGroup; + xTokens: XTokensGroup; constructor(logger?: ILogger, options: { [key: string]: any } = {}) { super(logger, options.helperBase ?? AstarHelper); this.balance = new SubstrateBalanceGroup(this); this.assets = new AssetsGroup(this); + this.xTokens = new XTokensGroup(this); this.xcm = new XcmGroup(this, 'polkadotXcm'); } } diff --git a/js-packages/tests/config.ts b/js-packages/tests/config.ts index 13a6b837ac..08f356841f 100644 --- a/js-packages/tests/config.ts +++ b/js-packages/tests/config.ts @@ -25,9 +25,9 @@ const config = { moonriverUrl: process.env.RELAY_MOONRIVER_URL || 'ws://127.0.0.1:9947', astarUrl: process.env.RELAY_ASTAR_URL || 'ws://127.0.0.1:9949', shidenUrl: process.env.RELAY_SHIDEN_URL || 'ws://127.0.0.1:9949', - westmintUrl: process.env.RELAY_ASSETHUB_URL || 'ws://127.0.0.1:9948', - statemineUrl: process.env.RELAY_ASSETHUB_URL || 'ws://127.0.0.1:9948', - statemintUrl: process.env.RELAY_ASSETHUB_URL || 'ws://127.0.0.1:9948', + westendAssetHubUrl: process.env.RELAY_ASSETHUB_URL || 'ws://127.0.0.1:9948', + kusamaAssetHubUrl: process.env.RELAY_ASSETHUB_URL || 'ws://127.0.0.1:9948', + polkadotAssetHubUrl: process.env.RELAY_ASSETHUB_URL || 'ws://127.0.0.1:9948', polkadexUrl: process.env.RELAY_POLKADEX_URL || 'ws://127.0.0.1:9950', hydraDxUrl: process.env.RELAY_HYDRADX_URL || 'ws://127.0.0.1:9951', }; diff --git a/js-packages/tests/package.json b/js-packages/tests/package.json index 722de7bac9..c01f030552 100644 --- a/js-packages/tests/package.json +++ b/js-packages/tests/package.json @@ -24,9 +24,8 @@ "testEth": "yarn _test ./**/eth/*.*test.ts", "testGovernance": "RUN_GOV_TESTS=1 yarn _test ./**/sub/governance/*.*test.ts", "testCollators": "RUN_COLLATOR_TESTS=1 yarn _test ./**/sub/collator-selection/**.*test.ts --timeout 49999999", - "testFullXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Unique.test.ts", - "testFullXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Quartz.test.ts", - "testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts", + "testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/unique.test.ts", + "testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/quartz.test.ts", "load": "yarn _test './**/*.load.ts'" } } diff --git a/js-packages/tests/xcm/lowLevelXcmQuartz.test.ts b/js-packages/tests/xcm/quartz.test.ts similarity index 56% rename from js-packages/tests/xcm/lowLevelXcmQuartz.test.ts rename to js-packages/tests/xcm/quartz.test.ts index 2d7de1a593..4bb3e556d4 100644 --- a/js-packages/tests/xcm/lowLevelXcmQuartz.test.ts +++ b/js-packages/tests/xcm/quartz.test.ts @@ -15,13 +15,13 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds} from '@unique/test-utils/util.js'; -import {QUARTZ_CHAIN, QTZ_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, shidenUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT, SENDER_BUDGET, relayUrl} from './xcm.types.js'; +import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingShidenPlaygrounds, usingMoonriverPlaygrounds} from '@unique/test-utils/util.js'; +import {QUARTZ_CHAIN, SAFE_XCM_VERSION, XcmTestHelper, SENDER_BUDGET, SENDTO_AMOUNT, SENDBACK_AMOUNT, SHIDEN_DECIMALS, UNQ_DECIMALS} from './xcm.types.js'; import {hexToString} from '@polkadot/util'; -const testHelper = new XcmTestHelper('quartz'); +const testHelper = new XcmTestHelper; -describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => { +describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -34,14 +34,12 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => { await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); }); - await usingKaruraPlaygrounds(karuraUrl, async (helper) => { - const destination = { - V2: { + await usingKaruraPlaygrounds(async (helper) => { + const location = { + V4: { parents: 1, interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, + X1: [{Parachain: QUARTZ_CHAIN}], }, }, }; @@ -49,7 +47,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => { const metadata = { name: 'Quartz', symbol: 'QTZ', - decimals: 18, + decimals: Number(UNQ_DECIMALS), minimalBalance: 1000000000000000000n, }; @@ -57,11 +55,11 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => { hexToString(v.toJSON()['symbol'])) as string[]; if(!assets.includes('QTZ')) { - await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); + await helper.getSudo().assetRegistry.registerForeignAsset(alice, location, metadata); } else { console.log('QTZ token already registered on Karura assetRegistry pallet'); } - await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); + await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET); }); await usingPlaygrounds(async (helper) => { @@ -70,155 +68,109 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => { }); itSub('Should connect and send QTZ to Karura', async () => { - await testHelper.sendUnqTo('karura', randomAccount); + await testHelper.sendUnqFromTo( + 'quartz', + 'karura', + randomAccount, + randomAccount, + SENDTO_AMOUNT, + ); }); itSub('Should connect to Karura and send QTZ back', async () => { - await testHelper.sendUnqBack('karura', alice, randomAccount); + await testHelper.sendUnqFromTo( + 'karura', + 'quartz', + randomAccount, + randomAccount, + SENDBACK_AMOUNT, + ); }); itSub('Karura can send only up to its balance', async () => { - await testHelper.sendOnlyOwnedBalance('karura', alice); - }); -}); -// These tests are relevant only when -// the the corresponding foreign assets are not registered -describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => { - let alice: IKeyringPair; - - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - - - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - }); - - itSub('Quartz rejects KAR tokens from Karura', async () => { - await testHelper.rejectNativeTokensFrom('karura', alice); - }); - - itSub('Quartz rejects MOVR tokens from Moonriver', async () => { - await testHelper.rejectNativeTokensFrom('moonriver', alice); + await testHelper.sendOnlyOwnedBalance( + alice, + 'karura', + 'quartz', + ); }); - itSub('Quartz rejects SDN tokens from Shiden', async () => { - await testHelper.rejectNativeTokensFrom('shiden', alice); + itSub('Should not accept reserve transfer of QTZ from Karura', async () => { + await testHelper.rejectReserveTransferUNQfrom( + alice, + 'karura', + 'quartz', + ); }); }); -describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => { - // Quartz constants +describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => { let alice: IKeyringPair; - let quartzAssetLocation; let randomAccountQuartz: IKeyringPair; let randomAccountMoonriver: IKeyringPair; - // Moonriver constants - let assetId: string; - - const quartzAssetMetadata = { - name: 'xcQuartz', - symbol: 'xcQTZ', - decimals: 18, - isFrozen: false, - minimalBalance: 1n, - }; - - before(async () => { await usingPlaygrounds(async (helper, privateKey) => { alice = await privateKey('//Alice'); [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice); - // Set the default version to wrap the first message to other chains. await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); + + await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, SENDER_BUDGET); }); - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { + await usingMoonriverPlaygrounds(async (helper) => { const alithAccount = helper.account.alithAccount(); - const baltatharAccount = helper.account.baltatharAccount(); - const dorothyAccount = helper.account.dorothyAccount(); randomAccountMoonriver = helper.account.create(); - // >>> 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: { - parents: 1, - interior: {X1: {Parachain: QUARTZ_CHAIN}}, - }, - }; - const existentialDeposit = 1n; - const isSufficient = true; - const unitsPerSecond = 1n; - const numAssetsWeightHint = 0; - if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) { - console.log('Quartz asset already registered on Moonriver'); - } else { - const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ - location: quartzAssetLocation, - metadata: quartzAssetMetadata, - existentialDeposit, - isSufficient, - unitsPerSecond, - numAssetsWeightHint, - }); - - console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); - - await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal); - } - // >>> Acquire Quartz AssetId Info on Moonriver >>> - console.log('Acquire Quartz AssetId Info on Moonriver.......'); - - assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString(); - - console.log('QTZ asset ID is %s', assetId); - console.log('Acquire Quartz AssetId Info on Moonriver.......DONE'); - // >>> Acquire Quartz AssetId Info on Moonriver >>> - - // >>> 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 usingPlaygrounds(async (helper) => { - await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT); + await helper.balance.transferToEthereum(alithAccount, randomAccountMoonriver.address, SENDER_BUDGET); }); }); itSub('Should connect and send QTZ to Moonriver', async () => { - await testHelper.sendUnqTo('moonriver', randomAccountQuartz, randomAccountMoonriver); + await testHelper.sendUnqFromTo( + 'quartz', + 'moonriver', + randomAccountQuartz, + randomAccountMoonriver, + SENDTO_AMOUNT, + ); }); - itSub('Should connect to Moonriver and send QTZ back', async () => { - await testHelper.sendUnqBack('moonriver', alice, randomAccountQuartz); + // TODO Moonbeam uses OpenGov now, we need another way of producing Root Origin in tests. + // So we can't register our asset on Moonbeam. + // We just test if the message got to the destination in the previous test. + itSub.skip('Should connect to Moonriver and send QTZ back', async () => { + await testHelper.sendUnqFromTo( + 'quartz', + 'moonriver', + randomAccountMoonriver, + randomAccountQuartz, + SENDBACK_AMOUNT, + ); }); - itSub('Moonriver can send only up to its balance', async () => { - await testHelper.sendOnlyOwnedBalance('moonriver', alice); + itSub.skip('Moonriver can send only up to its balance', async () => { + await testHelper.sendOnlyOwnedBalance( + alice, + 'moonriver', + 'quartz', + ); }); - itSub('Should not accept reserve transfer of QTZ from Moonriver', async () => { - await testHelper.rejectReserveTransferUNQfrom('moonriver', alice); + itSub.skip('Should not accept reserve transfer of QTZ from Moonriver', async () => { + await testHelper.rejectReserveTransferUNQfrom( + alice, + 'moonriver', + 'quartz', + ); }); }); -describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { +describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -240,7 +192,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); }); - await usingShidenPlaygrounds(shidenUrl, async (helper) => { + await usingShidenPlaygrounds(async (helper) => { if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) { console.log('1. Create foreign asset and metadata'); await helper.getSudo().assets.forceCreate( @@ -255,17 +207,15 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { QTZ_ASSET_ID_ON_SHIDEN, 'Quartz', 'QTZ', - Number(QTZ_DECIMALS), + Number(UNQ_DECIMALS), ); console.log('2. Register asset location on Shiden'); const assetLocation = { - V2: { + V4: { parents: 1, interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, + X1: [{Parachain: QUARTZ_CHAIN}], }, }, }; @@ -283,18 +233,66 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { }); itSub('Should connect and send QTZ to Shiden', async () => { - await testHelper.sendUnqTo('shiden', randomAccount); + await testHelper.sendUnqFromTo( + 'quartz', + 'shiden', + randomAccount, + randomAccount, + SENDTO_AMOUNT, + ); }); itSub('Should connect to Shiden and send QTZ back', async () => { - await testHelper.sendUnqBack('shiden', alice, randomAccount); + await testHelper.sendUnqFromTo( + 'shiden', + 'quartz', + randomAccount, + randomAccount, + SENDBACK_AMOUNT, + ); }); itSub('Shiden can send only up to its balance', async () => { - await testHelper.sendOnlyOwnedBalance('shiden', alice); + await testHelper.sendOnlyOwnedBalance( + alice, + 'shiden', + 'quartz', + ); }); itSub('Should not accept reserve transfer of QTZ from Shiden', async () => { - await testHelper.rejectReserveTransferUNQfrom('shiden', alice); + await testHelper.rejectReserveTransferUNQfrom( + alice, + 'shiden', + 'quartz', + ); + }); +}); + +// These tests are relevant only when +// the the corresponding foreign assets are not registered +describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => { + let alice: IKeyringPair; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + + // Set the default version to wrap the first message to other chains. + await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); + }); + }); + + itSub('Quartz rejects KAR tokens from Karura', async () => { + await testHelper.rejectNativeTokensFrom(alice, 'karura', 'quartz'); + }); + + // TODO Moonbeam uses OpenGov now, we need another way of producing Root Origin in tests. + itSub.skip('Quartz rejects MOVR tokens from Moonriver', async () => { + await testHelper.rejectNativeTokensFrom(alice, 'moonriver', 'quartz'); + }); + + itSub('Quartz rejects SDN tokens from Shiden', async () => { + await testHelper.rejectNativeTokensFrom(alice, 'shiden', 'quartz'); }); }); diff --git a/js-packages/tests/xcm/lowLevelXcmUnique.test.ts b/js-packages/tests/xcm/unique.test.ts similarity index 62% rename from js-packages/tests/xcm/lowLevelXcmUnique.test.ts rename to js-packages/tests/xcm/unique.test.ts index 0ac532e4e9..80dda3384e 100644 --- a/js-packages/tests/xcm/lowLevelXcmUnique.test.ts +++ b/js-packages/tests/xcm/unique.test.ts @@ -19,14 +19,11 @@ import config from '../config.js'; import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingHydraDxPlaygrounds} from '@unique/test-utils/util.js'; import {nToBigInt} from '@polkadot/util'; import {hexToString} from '@polkadot/util'; -import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, hydraDxUrl, moonbeamUrl, polkadexUrl, uniqueAssetId} from './xcm.types.js'; +import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDBACK_AMOUNT, SENDER_BUDGET, SENDTO_AMOUNT, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper} from './xcm.types.js'; -const testHelper = new XcmTestHelper('unique'); +const testHelper = new XcmTestHelper; - - - -describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => { +describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -40,14 +37,12 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => { await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); }); - await usingAcalaPlaygrounds(acalaUrl, async (helper) => { + await usingAcalaPlaygrounds(async (helper) => { const destination = { - V2: { + V4: { parents: 1, interior: { - X1: { - Parachain: UNIQUE_CHAIN, - }, + X1: [{Parachain: UNIQUE_CHAIN}], }, }, }; @@ -55,7 +50,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => { const metadata = { name: 'Unique Network', symbol: 'UNQ', - decimals: 18, + decimals: Number(UNQ_DECIMALS), minimalBalance: 1250_000_000_000_000_000n, }; const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) => @@ -66,7 +61,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => { } else { console.log('UNQ token already registered on Acala assetRegistry pallet'); } - await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); + await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET); }); await usingPlaygrounds(async (helper) => { @@ -75,23 +70,43 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => { }); itSub('Should connect and send UNQ to Acala', async () => { - await testHelper.sendUnqTo('acala', randomAccount); + await testHelper.sendUnqFromTo( + 'unique', + 'acala', + randomAccount, + randomAccount, + SENDTO_AMOUNT, + ); }); itSub('Should connect to Acala and send UNQ back', async () => { - await testHelper.sendUnqBack('acala', alice, randomAccount); + await testHelper.sendUnqFromTo( + 'acala', + 'unique', + randomAccount, + randomAccount, + SENDBACK_AMOUNT, + ); }); itSub('Acala can send only up to its balance', async () => { - await testHelper.sendOnlyOwnedBalance('acala', alice); + await testHelper.sendOnlyOwnedBalance( + alice, + 'acala', + 'unique', + ); }); itSub('Should not accept reserve transfer of UNQ from Acala', async () => { - await testHelper.rejectReserveTransferUNQfrom('acala', alice); + await testHelper.rejectReserveTransferUNQfrom( + alice, + 'acala', + 'unique', + ); }); }); -describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => { +describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -104,7 +119,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => { await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); }); - await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => { + await usingPolkadexPlaygrounds(async (helper) => { const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', [])) .toJSON() as []) .map(nToBigInt).length != 0; @@ -117,10 +132,20 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => { if(isWhitelisted) { console.log('UNQ token is already whitelisted on Polkadex'); } else { - await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId); + await helper.getSudo().xcmHelper.whitelistToken( + alice, + { + Concrete: { + parents: 1, + interior: { + X1: {Parachain: UNIQUE_CHAIN}, + }, + }, + } + ); } - await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); + await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET); }); await usingPlaygrounds(async (helper) => { @@ -129,162 +154,103 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => { }); itSub('Should connect and send UNQ to Polkadex', async () => { - await testHelper.sendUnqTo('polkadex', randomAccount); + await testHelper.sendUnqFromTo( + 'unique', + 'polkadex', + randomAccount, + randomAccount, + SENDTO_AMOUNT, + ); }); - - itSub('Should connect to Polkadex and send UNQ back', async () => { - await testHelper.sendUnqBack('polkadex', alice, randomAccount); - }); + // Polkadex has on a solochain part. We don't model this. + // We just test if the message got to the destination in the previous test. + // The next tests verify that messages from Polkadex arrive and act nicely. itSub('Polkadex can send only up to its balance', async () => { - await testHelper.sendOnlyOwnedBalance('polkadex', alice); + await testHelper.sendOnlyOwnedBalance( + alice, + 'polkadex', + 'unique', + ); }); itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => { - await testHelper.rejectReserveTransferUNQfrom('polkadex', alice); + await testHelper.rejectReserveTransferUNQfrom( + alice, + 'polkadex', + 'unique', + ); }); }); -// These tests are relevant only when -// the the corresponding foreign assets are not registered -describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => { +describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => { let alice: IKeyringPair; - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - }); - - itSub('Unique rejects ACA tokens from Acala', async () => { - await testHelper.rejectNativeTokensFrom('acala', alice); - }); - - itSub('Unique rejects GLMR tokens from Moonbeam', async () => { - await testHelper.rejectNativeTokensFrom('moonbeam', alice); - }); - - itSub('Unique rejects ASTR tokens from Astar', async () => { - await testHelper.rejectNativeTokensFrom('astar', alice); - }); - - itSub('Unique rejects PDX tokens from Polkadex', async () => { - await testHelper.rejectNativeTokensFrom('polkadex', alice); - }); -}); - -describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => { - // Unique constants - let alice: IKeyringPair; - let uniqueAssetLocation; - let randomAccountUnique: IKeyringPair; let randomAccountMoonbeam: IKeyringPair; - // Moonbeam constants - let assetId: string; - - const uniqueAssetMetadata = { - name: 'xcUnique', - symbol: 'xcUNQ', - decimals: 18, - isFrozen: false, - minimalBalance: 1n, - }; - - before(async () => { await usingPlaygrounds(async (helper, privateKey) => { alice = await privateKey('//Alice'); [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice); - // Set the default version to wrap the first message to other chains. await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); + + await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET); }); - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { + await usingMoonbeamPlaygrounds(async (helper) => { const alithAccount = helper.account.alithAccount(); - const baltatharAccount = helper.account.baltatharAccount(); - const dorothyAccount = helper.account.dorothyAccount(); randomAccountMoonbeam = helper.account.create(); - // >>> 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: { - parents: 1, - interior: {X1: {Parachain: UNIQUE_CHAIN}}, - }, - }; - const existentialDeposit = 1n; - const isSufficient = true; - const unitsPerSecond = 1n; - const numAssetsWeightHint = 0; - - if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) { - console.log('Unique asset already registered on Moonbeam'); - } else { - const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ - location: uniqueAssetLocation, - metadata: uniqueAssetMetadata, - existentialDeposit, - isSufficient, - unitsPerSecond, - numAssetsWeightHint, - }); - - console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); - - await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal); - } - - // >>> Acquire Unique AssetId Info on Moonbeam >>> - console.log('Acquire Unique AssetId Info on Moonbeam.......'); - - assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString(); - - console.log('UNQ asset ID is %s', assetId); - console.log('Acquire Unique AssetId Info on Moonbeam.......DONE'); - - // >>> 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 usingPlaygrounds(async (helper) => { - await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET); + await helper.balance.transferToEthereum(alithAccount, randomAccountMoonbeam.address, SENDER_BUDGET); }); }); itSub('Should connect and send UNQ to Moonbeam', async () => { - await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam); + await testHelper.sendUnqFromTo( + 'unique', + 'moonbeam', + randomAccountUnique, + randomAccountMoonbeam, + SENDTO_AMOUNT, + ); }); - itSub('Should connect to Moonbeam and send UNQ back', async () => { - await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique); + // TODO Moonbeam uses OpenGov now, we need another way of producing Root Origin in tests. + // So we can't register our asset on Moonbeam. + // We just test if the message got to the destination in the previous test. + itSub.skip('Should connect to Moonbeam and send UNQ back', async () => { + await testHelper.sendUnqFromTo( + 'moonbeam', + 'unique', + randomAccountUnique, + randomAccountMoonbeam, + SENDBACK_AMOUNT, + ); }); - itSub('Moonbeam can send only up to its balance', async () => { - await testHelper.sendOnlyOwnedBalance('moonbeam', alice); + itSub.skip('Moonbeam can send only up to its balance', async () => { + await testHelper.sendOnlyOwnedBalance( + alice, + 'moonbeam', + 'unique', + ); }); - itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => { - await testHelper.rejectReserveTransferUNQfrom('moonbeam', alice); + itSub.skip('Should not accept reserve transfer of UNQ from Moonbeam', async () => { + await testHelper.rejectReserveTransferUNQfrom( + alice, + 'moonbeam', + 'unique', + ); }); }); -describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => { +describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -306,7 +272,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => { await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); }); - await usingAstarPlaygrounds(astarUrl, async (helper) => { + await usingAstarPlaygrounds(async (helper) => { if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) { console.log('1. Create foreign asset and metadata'); await helper.getSudo().assets.forceCreate( @@ -349,23 +315,43 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => { }); itSub('Should connect and send UNQ to Astar', async () => { - await testHelper.sendUnqTo('astar', randomAccount); + await testHelper.sendUnqFromTo( + 'unique', + 'astar', + randomAccount, + randomAccount, + SENDTO_AMOUNT, + ); }); itSub('Should connect to Astar and send UNQ back', async () => { - await testHelper.sendUnqBack('astar', alice, randomAccount); + await testHelper.sendUnqFromTo( + 'astar', + 'unique', + randomAccount, + randomAccount, + SENDBACK_AMOUNT, + ); }); itSub('Astar can send only up to its balance', async () => { - await testHelper.sendOnlyOwnedBalance('astar', alice); + await testHelper.sendOnlyOwnedBalance( + alice, + 'astar', + 'unique', + ); }); itSub('Should not accept reserve transfer of UNQ from Astar', async () => { - await testHelper.rejectReserveTransferUNQfrom('astar', alice); + await testHelper.rejectReserveTransferUNQfrom( + alice, + 'astar', + 'unique', + ); }); }); -describeXCM('[XCMLL] Integration test: Exchanging tokens with HydraDx', () => { +describeXCM('[XCM] Integration test: Exchanging tokens with HydraDx', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -380,24 +366,93 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with HydraDx', () => { await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET); }); - await usingHydraDxPlaygrounds(hydraDxUrl, async (helper) => { + await usingHydraDxPlaygrounds(async (helper) => { await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); }); }); itSub('Should connect and send UNQ to HydraDx', async () => { - await testHelper.sendUnqTo('hydraDx', randomAccount); + await testHelper.sendUnqFromTo( + 'unique', + 'hydraDx', + randomAccount, + randomAccount, + SENDTO_AMOUNT, + ); }); - itSub('Should connect to HydraDx and send UNQ back', async () => { - await testHelper.sendUnqBack('hydraDx', alice, randomAccount); + // TODO + itSub.skip('Should connect to HydraDx and send UNQ back', async () => { + await testHelper.sendUnqFromTo( + 'hydraDx', + 'unique', + randomAccount, + randomAccount, + SENDBACK_AMOUNT, + ); }); itSub('HydraDx can send only up to its balance', async () => { - await testHelper.sendOnlyOwnedBalance('hydraDx', alice); + await testHelper.sendOnlyOwnedBalance( + alice, + 'hydraDx', + 'unique', + ); }); itSub('Should not accept reserve transfer of UNQ from HydraDx', async () => { - await testHelper.rejectReserveTransferUNQfrom('hydraDx', alice); + await testHelper.rejectReserveTransferUNQfrom( + alice, + 'hydraDx', + 'unique', + ); + }); +}); + +// These tests are relevant only when +// the the corresponding foreign assets are not registered +describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => { + let alice: IKeyringPair; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + + // Set the default version to wrap the first message to other chains. + await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); + }); }); -}); \ No newline at end of file + + itSub('Unique rejects ACA tokens from Acala', async () => { + await testHelper.rejectNativeTokensFrom( + alice, + 'acala', + 'unique' + ); + }); + + // TODO Moonbeam uses OpenGov now, we need another way of producing Root Origin in tests. + itSub.skip('Unique rejects GLMR tokens from Moonbeam', async () => { + await testHelper.rejectNativeTokensFrom( + alice, + 'moonbeam', + 'unique', + ); + }); + + itSub('Unique rejects ASTR tokens from Astar', async () => { + await testHelper.rejectNativeTokensFrom( + alice, + 'astar', + 'unique', + ); + }); + + itSub('Unique rejects PDX tokens from Polkadex', async () => { + await testHelper.rejectNativeTokensFrom( + alice, + 'polkadex', + 'unique', + ); + }); +}); diff --git a/js-packages/tests/xcm/xcm.types.ts b/js-packages/tests/xcm/xcm.types.ts index e0a304f47c..9c92f5c473 100644 --- a/js-packages/tests/xcm/xcm.types.ts +++ b/js-packages/tests/xcm/xcm.types.ts @@ -1,10 +1,11 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingHydraDxPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '@unique/test-utils/util.js'; import {DevUniqueHelper, Event} from '@unique/test-utils'; -import config from '../config.js'; +import { AcalaHelper, AstarHelper } from '@unique/test-utils/xcm/index.js'; +import { IEvent } from '@unique-nft/playgrounds/types.js'; export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037); -export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000); +export const POLKADOT_ASSETHUB_CHAIN = +(process.env.RELAY_ASSETHUB_URL || 1000); export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000); export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004); export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006); @@ -12,63 +13,22 @@ export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040); export const HYDRADX_CHAIN = +(process.env.RELAY_HYDRADX_ID || 2034); export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095); -export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000); +export const KUSAMA_ASSETHUB_CHAIN = +(process.env.RELAY_ASSETHUB_URL || 1000); export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000); export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023); export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007); -export const relayUrl = config.relayUrl; -export const statemintUrl = config.statemintUrl; -export const statemineUrl = config.statemineUrl; - -export const acalaUrl = config.acalaUrl; -export const moonbeamUrl = config.moonbeamUrl; -export const astarUrl = config.astarUrl; -export const polkadexUrl = config.polkadexUrl; -export const hydraDxUrl = config.hydraDxUrl; - -export const karuraUrl = config.karuraUrl; -export const moonriverUrl = config.moonriverUrl; -export const shidenUrl = config.shidenUrl; - -export const SAFE_XCM_VERSION = 3; - +export const SAFE_XCM_VERSION = 4; export const RELAY_DECIMALS = 12; -export const STATEMINE_DECIMALS = 12; +export const KUSAMA_ASSETHUB_DECIMALS = 12; export const KARURA_DECIMALS = 12; export const SHIDEN_DECIMALS = 18n; -export const QTZ_DECIMALS = 18n; export const ASTAR_DECIMALS = 18n; export const UNQ_DECIMALS = 18n; -export const maxWaitBlocks = 6; - -export const uniqueMultilocation = { - parents: 1, - interior: { - X1: { - Parachain: UNIQUE_CHAIN, - }, - }, -}; -export const uniqueVersionedMultilocation = { - V3: uniqueMultilocation, -}; - -export const uniqueAssetId = { - Concrete: uniqueMultilocation, -}; - -export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => { - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash - && event.outcome.isFailedToTransactAsset); -}; -export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => { - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash - && event.outcome.isUntrustedReserveLocation); -}; +export const maxWaitBlocks = 50; export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => { // The correct messageHash for downward messages can't be reliably obtained @@ -81,6 +41,9 @@ export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => { }; export const NETWORKS = { + unique: usingPlaygrounds, + quartz: usingPlaygrounds, + relay: usingRelayPlaygrounds, acala: usingAcalaPlaygrounds, astar: usingAstarPlaygrounds, polkadex: usingPolkadexPlaygrounds, @@ -92,10 +55,16 @@ export const NETWORKS = { } as const; type NetworkNames = keyof typeof NETWORKS; -type NativeRuntime = 'opal' | 'quartz' | 'unique'; +type UniqueChain = 'quartz' | 'unique'; export function mapToChainId(networkName: keyof typeof NETWORKS): number { switch (networkName) { + case 'unique': + return UNIQUE_CHAIN; + case 'quartz': + return QUARTZ_CHAIN; + case 'relay': + throw new Error('Relay chain has no para ID'); case 'acala': return ACALA_CHAIN; case 'astar': @@ -115,60 +84,27 @@ export function mapToChainId(networkName: keyof typeof NETWORKS): number { } } -export function mapToChainUrl(networkName: NetworkNames): string { - switch (networkName) { - case 'acala': - return acalaUrl; - case 'astar': - return astarUrl; - case 'moonbeam': - return moonbeamUrl; - case 'polkadex': - return polkadexUrl; - case 'moonriver': - return moonriverUrl; - case 'karura': - return karuraUrl; - case 'shiden': - return shidenUrl; - case 'hydraDx': - return hydraDxUrl; - } +export function mapToChainLocation(networkName: keyof typeof NETWORKS) { + return { + parents: 1, + interior: networkName === 'relay' + ? 'here' + : {X1: [{Parachain: mapToChainId(networkName)}]}, + }; } export function getDevPlayground(name: NetworkNames) { return NETWORKS[name]; } -export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n; +export const TRANSFER_AMOUNT = 2000000n * 10n ** UNQ_DECIMALS; export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT; +export const SENDTO_AMOUNT = TRANSFER_AMOUNT; export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n; export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT; -export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n; +export const OTHER_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n; export class XcmTestHelper { - private _balanceUniqueTokenInit: bigint = 0n; - private _balanceUniqueTokenMiddle: bigint = 0n; - private _balanceUniqueTokenFinal: bigint = 0n; - private _unqFees: bigint = 0n; - private _nativeRuntime: NativeRuntime; - - constructor(runtime: NativeRuntime) { - this._nativeRuntime = runtime; - } - - private _getNativeId() { - switch (this._nativeRuntime) { - case 'opal': - // To-Do - return 1001; - case 'quartz': - return QUARTZ_CHAIN; - case 'unique': - return UNIQUE_CHAIN; - } - } - private _isAddress20FormatFor(network: NetworkNames) { switch (network) { case 'moonbeam': @@ -179,355 +115,534 @@ export class XcmTestHelper { } } - private _runtimeVersionedMultilocation() { - return { - V3: { - parents: 1, - interior: { - X1: { - Parachain: this._getNativeId(), - }, - }, - }, - }; - } + async #collectProcessedMsgsEvents( + helper: any, + whileCondition: () => boolean + ) { + const {unsubscribe, collectedEvents} = await helper.subscribeEvents([ + { + section: Event.MessageQueue.Processed.section(), + names: [Event.MessageQueue.Processed.method()], + } + ]); - private _uniqueChainMultilocationForRelay() { - return { - V3: { - parents: 0, - interior: { - X1: {Parachain: this._getNativeId()}, - }, - }, - }; + let blocksSkipped = 0; + + while (whileCondition()) { + await helper.wait.newBlocks(1); + + blocksSkipped += 1; + + if (blocksSkipped >= maxWaitBlocks) { + throw new Error( + `max number of blocks (${maxWaitBlocks}) were skipped while waiting for the message hash to find`, + ); + } + } + + // After the block with XCM transfer from the `from` network + // is finalized, we will receive the XCM message on the `to` network + // in some near block via the `setValidationData`. + // + // When we see that `messageHash` isn't null, we need to wait several more blocks + // to make sure we didn't skip the one where the XCM message arrived. + // + // Yet, it could also arrive immediately, that is why we are collecting events. + await helper.wait.newBlocks(10); + + unsubscribe(); + return (collectedEvents as IEvent[]).map(e => e.data); } - async sendUnqTo( - networkName: keyof typeof NETWORKS, - randomAccount: IKeyringPair, - randomAccountOnTargetChain = randomAccount, + async #sendXcmProgram( + sudoer: IKeyringPair, + sendFrom: keyof typeof NETWORKS, + sendTo: UniqueChain, + program: any, ) { - const networkUrl = mapToChainUrl(networkName); - const targetPlayground = getDevPlayground(networkName); + const otherChainPlayground = getDevPlayground(sendFrom); + + return await otherChainPlayground(async (helper) => { + const destination = {V4: mapToChainLocation(sendTo)}; + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [destination, program]); + + if('getSudo' in helper) { + // can't use `getSudo` here because of types issues. + // The `helper` has a union type, + // the signatures of `getSudo` aren't compatible with each other. + // + // But we do know here that the chain has the `sudo` pallet. + const sendResult = await helper.executeExtrinsic( + sudoer, + 'api.tx.sudo.sudo', + [xcmSend], + ); + const messageSent = Event.XcmpQueue.XcmpMessageSent.expect(sendResult); + return messageSent.messageHash; + } else if ('fastDemocracy' in helper) { + // Needed to bypass the call filter. + const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); + + const [, messageSent] = await Promise.all([ + helper.fastDemocracy.executeProposal(`sending ${sendFrom} -> ${sendTo} via XCM program`, batchCall), + helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent) + ]); + + return messageSent.messageHash; + } else { + throw new Error(`unknown governance in ${sendFrom}`) + } + }); + } + + async #awaitMaliciousProgramRejection(getMessageHash: () => any) { await usingPlaygrounds(async (helper) => { - this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address); - const destination = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: mapToChainId(networkName), - }, - }, - }, - }; + const collectedEventData = await this.#collectProcessedMsgsEvents( + helper, + () => getMessageHash() === null, + ); + + const processedMsgEvent = collectedEventData.find(data => { + const processedMessageHash = data[0]; + return processedMessageHash === getMessageHash(); + }); + + expect(processedMsgEvent).to.be.not.undefined; + + const msgProcResult = processedMsgEvent![3]; + expect(msgProcResult).to.be.false; + }); + } + + async #sendTokens( + from: keyof typeof NETWORKS, + to: keyof typeof NETWORKS, + fromAccount: IKeyringPair, + toAccount: IKeyringPair, + amount: bigint, + setMessageHash: (messageHash: any) => void, + ) { + let isFromUnique = from === 'unique' || from === 'quartz'; + let isToUnique = to === 'unique' || to === 'quartz'; + + const fromPlayground = getDevPlayground(from); - const beneficiary = { - V2: { + await fromPlayground(async (helper) => { + let assetId: any; + if (isFromUnique) { + assetId = { parents: 0, - interior: { - X1: ( - this._isAddress20FormatFor(networkName) ? - { - AccountKey20: { - network: 'Any', - key: randomAccountOnTargetChain.address, - }, - } - : - { - AccountId32: { - network: 'Any', - id: randomAccountOnTargetChain.addressRaw, - }, - } - ), - }, - }, + interior: 'here', + }; + } else if (isToUnique) { + assetId = mapToChainLocation(to); + } else { + throw new Error('sendUnqFromTo: either `from` or `to` MUST point to a Unique chain'); + } + + const getRandomAccountBalance = async (): Promise => { + if (!isFromUnique) { + return 0n; + } + + if ('getSubstrate' in helper.balance) { + return await helper.balance.getSubstrate(fromAccount.address); + } else { + return await helper.balance.getEthereum(fromAccount.address); + } }; + const unqBalanceBefore = await getRandomAccountBalance(); + + let beneficiaryAccount: any; + if (this._isAddress20FormatFor(to)) { + beneficiaryAccount = { + AccountKey20: { + key: toAccount.address, + }, + }; + } else { + beneficiaryAccount = { + AccountId32: { + id: toAccount.addressRaw, + }, + }; + } + const assets = { - V2: [ + V4: [ { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, + id: assetId, fun: { - Fungible: TRANSFER_AMOUNT, + Fungible: amount, }, }, ], }; const feeAssetItem = 0; - await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - - this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT; - console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees)); - expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true; - - await targetPlayground(networkUrl, async (helper) => { - /* - Since only the parachain part of the Polkadex - infrastructure is launched (without their - solochain validators), processing incoming - assets will lead to an error. - This error indicates that the Polkadex chain - received a message from the Unique network, - since the hash is being checked to ensure - it matches what was sent. - */ - if(networkName == 'polkadex' || networkName =='hydraDx') { - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash); - } else { - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash); - } - }); - - }); - } + let transferResult: any; - async sendUnqBack( - networkName: keyof typeof NETWORKS, - sudoer: IKeyringPair, - randomAccountOnUnq: IKeyringPair, - ) { - const networkUrl = mapToChainUrl(networkName); + if ( + from === 'acala' || from === 'karura' + || from === 'astar' || from === 'shiden' + ) { + // `polkadotXcm.transferAssets` is filtered on Acala chains. + // Astar chains have prohibitive weights for it. + // using xTokens instead - const targetPlayground = getDevPlayground(networkName); - await usingPlaygrounds(async (helper) => { + const acalaHelper = helper as AcalaHelper | AstarHelper; - const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - randomAccountOnUnq.addressRaw, - { - Concrete: { + const destination = { + V4: { parents: 1, interior: { - X1: {Parachain: this._getNativeId()}, + X2: [ + { + Parachain: mapToChainId(to), + }, + beneficiaryAccount, + ], }, }, - }, - SENDBACK_AMOUNT, - ); - - let xcmProgramSent: any; + }; + + transferResult = await acalaHelper.xTokens.transferMultiassets( + fromAccount, + assets, + feeAssetItem, + destination, + 'Unlimited', + ); + } else { + const destination = {V4: mapToChainLocation(to)}; + const beneficiary = { + V4: { + parents: 0, + interior: { + X1: [beneficiaryAccount], + }, + }, + }; + + transferResult = await helper.xcm.transferAssets( + fromAccount, + destination, + beneficiary, + assets, + feeAssetItem, + 'Unlimited', + ); + } - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), xcmProgram); - xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), xcmProgram]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall); - xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - }); + const messageSent = Event.XcmpQueue.XcmpMessageSent.expect(transferResult); - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash); + const unqBalanceAfter = await getRandomAccountBalance(); + if (isFromUnique) { + const unqBalanceDiff = unqBalanceBefore - unqBalanceAfter; + const unqFees = unqBalanceDiff - amount; + const unqMinFees = 0n; + const unqMaxFees = 2n * 10n ** UNQ_DECIMALS; - this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address); + console.log('[%s -> %s] transaction fees: %s UNQ/QTZ', from, to, helper.util.bigIntToDecimals(unqFees)); - expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN); + expect( + unqMinFees < unqFees && unqFees <= unqMaxFees, + `invalid UNQ/QTZ fees when transferring from Unique/Quartz: ${unqFees}` + ).to.be.true; + } + setMessageHash(messageSent.messageHash); }); } - async sendOnlyOwnedBalance( - networkName: keyof typeof NETWORKS, - sudoer: IKeyringPair, + async #awaitTokens( + from: keyof typeof NETWORKS, + to: keyof typeof NETWORKS, + getMessageHash: () => any, + account: string, + amount: bigint, ) { - const networkUrl = mapToChainUrl(networkName); - const targetPlayground = getDevPlayground(networkName); + const toPlayground = getDevPlayground(to); + let isToUnique = to === 'unique' || to === 'quartz'; - const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS); + await toPlayground(async (helper) => { + const getRandomAccountBalance = async (): Promise => { + if (!isToUnique) { + return 0n; + } - await usingPlaygrounds(async (helper) => { - const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName)); - await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance); - const moreThanTargetChainHas = 2n * targetChainBalance; + if ('getSubstrate' in helper.balance) { + return await helper.balance.getSubstrate(account); + } else { + return await helper.balance.getEthereum(account); + } + }; - const targetAccount = helper.arrange.createEmptyAccount(); + const unqBalanceBefore = await getRandomAccountBalance(); - const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - moreThanTargetChainHas, + const collectedEventData = await this.#collectProcessedMsgsEvents( + helper, + getMessageHash, ); - let maliciousXcmProgramSent: any; + const validEventIndex = collectedEventData.findIndex(data => { + const processedMessageHash = data[0]; + return processedMessageHash === getMessageHash(); + }); + expect( + validEventIndex >= 0, + `no 'MessageQueue.Processed' event was found on ${to}`, + ).to.be.true; - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgram); - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgram]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall); - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - }); + const unqBalanceAfter = await getRandomAccountBalance(); + + if (isToUnique) { + const unqBalanceDiff = unqBalanceAfter - unqBalanceBefore; + const unqFees = unqBalanceDiff - amount; - await expectFailedToTransact(helper, maliciousXcmProgramSent); + console.log('[%s -> %s] transaction fees: %s UNQ/QTZ', from, to, helper.util.bigIntToDecimals(unqFees)); - const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(0n); + expect( + unqFees === 0n, + `invalid UNQ/QTZ fees when receiving to ${to}: ${unqFees}` + ).to.be.true; + } }); } - async rejectReserveTransferUNQfrom(networkName: keyof typeof NETWORKS, sudoer: IKeyringPair) { - const networkUrl = mapToChainUrl(networkName); - const targetPlayground = getDevPlayground(networkName); + async sendUnqFromTo( + from: keyof typeof NETWORKS, + to: keyof typeof NETWORKS, + randomAccountOnFrom: IKeyringPair, + randomAccountOnTo: IKeyringPair, + amount: bigint, + ) { + let messageHash: any = null; + + await Promise.all([ + this.#sendTokens( + from, + to, + randomAccountOnFrom, + randomAccountOnTo, + amount, + (hash) => messageHash = hash, + ), + this.#awaitTokens( + from, + to, + () => messageHash, + randomAccountOnTo.address, + amount, + ), + ]); + } + + async sendOnlyOwnedBalance( + sudoer: IKeyringPair, + otherChain: keyof typeof NETWORKS, + uniqueChain: UniqueChain, + ) { + const otherChainBalance = 10000n * (10n ** UNQ_DECIMALS); + + let randomAccount: IKeyringPair; + let maliciousXcmProgram: any; + let messageHash: any = null; await usingPlaygrounds(async (helper) => { - const testAmount = 10_000n * (10n ** UNQ_DECIMALS); - const targetAccount = helper.arrange.createEmptyAccount(); + const otherChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(otherChain)); + await helper.getSudo().balance.setBalanceSubstrate(sudoer, otherChainSovereignAccount, otherChainBalance); - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 1, - interior: { - X1: { - Parachain: this._getNativeId(), - }, - }, - }, - }, - testAmount, - ); + randomAccount = helper.arrange.createEmptyAccount(); + }); + + const sendMaliciousProgram = async () => { + await usingPlaygrounds(async (helper) => { + const moreThanOtherChainHas = 2n * otherChainBalance; - const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { + maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( + randomAccount.addressRaw, + { parents: 0, interior: 'Here', }, - }, - testAmount, - ); - - let maliciousXcmProgramFullIdSent: any; - let maliciousXcmProgramHereIdSent: any; - const maxWaitBlocks = 3; - - // Try to trick Unique using full UNQ identification - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId); - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - // Moonbeam case - else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using path asset identification`,batchCall); - - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } + moreThanOtherChainHas, + ); }); + messageHash = await this.#sendXcmProgram( + sudoer, + otherChain, + uniqueChain, + maliciousXcmProgram, + ); + }; - await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent); + await Promise.all([ + sendMaliciousProgram(), + this.#awaitMaliciousProgramRejection(() => messageHash), + ]); - let accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); + await usingPlaygrounds(async (helper) => { + const randomAccountBalance = await helper.balance.getSubstrate(randomAccount.address); + expect(randomAccountBalance).to.be.equal(0n); + }); - // Try to trick Unique using shortened UNQ identification - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId); - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall); + messageHash = null; + const sendGoodProgram = async () => { + await usingPlaygrounds(async (helper) => { - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } + maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( + randomAccount.addressRaw, + { + parents: 0, + interior: 'Here', + }, + otherChainBalance, + ); }); - await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent); + messageHash = await this.#sendXcmProgram( + sudoer, + otherChain, + uniqueChain, + maliciousXcmProgram, + ); + }; + + await Promise.all([ + sendGoodProgram(), + this.#awaitTokens( + otherChain, + uniqueChain, + () => messageHash, + randomAccount!.address, + otherChainBalance, + ) + ]); - accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); + await usingPlaygrounds(async (helper) => { + const randomAccountBalance = await helper.balance.getSubstrate(randomAccount.address); + expect(randomAccountBalance).to.be.equal(otherChainBalance); }); } - async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) { - const networkUrl = mapToChainUrl(networkName); - const targetPlayground = getDevPlayground(networkName); - let messageSent: any; + async rejectReserveTransferUNQfrom( + sudoer: IKeyringPair, + otherChain: keyof typeof NETWORKS, + uniqueChain: UniqueChain, + ) { + const testAmount = 10_000n * (10n ** UNQ_DECIMALS); + + let randomAccount: IKeyringPair; + let messageHash: any = null; + + const sendMaliciousXcmProgramFullId = async () => { + await usingPlaygrounds(async (helper) => { + randomAccount = helper.arrange.createEmptyAccount(); + + const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( + randomAccount.addressRaw, + mapToChainLocation(uniqueChain), + testAmount, + ); + + // Try to trick Unique using full UNQ identification + messageHash = await this.#sendXcmProgram( + sudoer, + otherChain, + uniqueChain, + maliciousXcmProgramFullId, + ); + }); + }; + + await Promise.all([ + sendMaliciousXcmProgramFullId(), + this.#awaitMaliciousProgramRejection(() => messageHash), + ]); - await usingPlaygrounds(async (helper) => { - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - helper.arrange.createEmptyAccount().addressRaw, - { - Concrete: { - parents: 1, - interior: { - X1: { - Parachain: mapToChainId(networkName), - }, - }, + messageHash = null; + const sendMaliciousXcmProgramHereId = async () => { + await usingPlaygrounds(async (helper) => { + const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( + randomAccount.addressRaw, + { + parents: 0, + interior: 'Here', }, - }, - TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT, - ); - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId); - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall); - - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } + testAmount, + ); + + // Try to trick Unique using shortened UNQ identification + messageHash = await this.#sendXcmProgram( + sudoer, + otherChain, + uniqueChain, + maliciousXcmProgramHereId, + ); }); - await expectFailedToTransact(helper, messageSent); + }; + + await Promise.all([ + sendMaliciousXcmProgramHereId(), + this.#awaitMaliciousProgramRejection(() => messageHash), + ]); + + await usingPlaygrounds(async (helper) => { + const randomAccountBalance = await helper.balance.getSubstrate(randomAccount.address); + expect(randomAccountBalance).to.be.equal(0n); }); } + async rejectNativeTokensFrom( + sudoer: IKeyringPair, + otherChain: keyof typeof NETWORKS, + uniqueChain: UniqueChain, + ) { + let messageHash: any = null; + + const sendMaliciousProgram = async () => { + await usingPlaygrounds(async (helper) => { + const maliciousXcmProgram = helper.arrange.makeXcmProgramReserveAssetDeposited( + helper.arrange.createEmptyAccount().addressRaw, + mapToChainLocation(otherChain), + OTHER_CHAIN_TOKEN_TRANSFER_AMOUNT, + ); + + messageHash = await this.#sendXcmProgram( + sudoer, + otherChain, + uniqueChain, + maliciousXcmProgram, + ); + }); + }; + + await Promise.all([ + sendMaliciousProgram(), + this.#awaitMaliciousProgramRejection(() => messageHash), + ]); + } + async registerRelayNativeTokenOnUnique(alice: IKeyringPair) { return await usingPlaygrounds(async (helper) => { const relayLocation = { parents: 1, interior: 'Here', }; - const relayAssetId = {Concrete: relayLocation}; - const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayAssetId); + const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayLocation); if(relayCollectionId == null) { - const name = 'Relay Tokens'; - const tokenPrefix = 'xDOT'; + const name = 'DOT'; + const tokenPrefix = 'DOT'; const decimals = 10; - await helper.getSudo().foreignAssets.register(alice, relayAssetId, name, tokenPrefix, {Fungible: decimals}); + await helper.getSudo().foreignAssets.register(alice, relayLocation, name, tokenPrefix, {Fungible: decimals}); - return await helper.foreignAssets.foreignCollectionId(relayAssetId); + return await helper.foreignAssets.foreignCollectionId(relayLocation); } else { console.log('Relay foreign collection is already registered'); return relayCollectionId; diff --git a/js-packages/tests/xcm/xcmOpal.test.ts b/js-packages/tests/xcm/xcmOpal.test.ts deleted file mode 100644 index 28db4f2484..0000000000 --- a/js-packages/tests/xcm/xcmOpal.test.ts +++ /dev/null @@ -1,431 +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 type {IKeyringPair} from '@polkadot/types/types'; -import config from '../config.js'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '@unique/test-utils/util.js'; -import {XcmTestHelper} from './xcm.types.js'; - -const STATEMINE_CHAIN = +(process.env.RELAY_WESTMINT_ID || 1000); -const UNIQUE_CHAIN = +(process.env.RELAY_OPAL_ID || 2095); - -const relayUrl = config.relayUrl; -const westmintUrl = config.westmintUrl; - -const STATEMINE_PALLET_INSTANCE = 50; -const USDT_ASSET_ID = 100; -const USDT_ASSET_METADATA_DECIMALS = 18; -const USDT_ASSET_METADATA_NAME = 'USDT'; -const USDT_ASSET_METADATA_DESCRIPTION = 'USDT'; -const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n; - -const RELAY_DECIMALS = 12; -const WESTMINT_DECIMALS = 12; - -const TRANSFER_AMOUNT = 1_000_000_000_000_000_000n; - -// 10,000.00 (ten thousands) USDT -const USDT_ASSET_AMOUNT = 1_000_000_000_000_000_000_000n; - -const testHelper = new XcmTestHelper('opal'); - -describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - - let balanceStmnBefore: bigint; - let balanceStmnAfter: bigint; - - let balanceOpalBefore: bigint; - let balanceOpalAfter: bigint; - let balanceOpalFinal: bigint; - - let balanceBobBefore: bigint; - let balanceBobAfter: bigint; - let balanceBobFinal: bigint; - - let balanceBobRelayTokenBefore: bigint; - let balanceBobRelayTokenAfter: bigint; - - let usdtCollectionId: number; - let relayCollectionId: number; - - before(async () => { - await usingPlaygrounds(async (_helper, privateKey) => { - alice = await privateKey('//Alice'); - bob = await privateKey('//Bob'); // funds donor - - relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice); - }); - - await usingWestmintPlaygrounds(westmintUrl, async (helper) => { - const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID); - if(assetInfo == null) { - await helper.assets.create( - alice, - USDT_ASSET_ID, - alice.address, - USDT_ASSET_METADATA_MINIMAL_BALANCE, - ); - await helper.assets.setMetadata( - alice, - USDT_ASSET_ID, - USDT_ASSET_METADATA_NAME, - USDT_ASSET_METADATA_DESCRIPTION, - USDT_ASSET_METADATA_DECIMALS, - ); - } else { - console.log('The USDT asset is already registered on AssetHub'); - } - - await helper.assets.mint( - alice, - USDT_ASSET_ID, - alice.address, - USDT_ASSET_AMOUNT, - ); - - const sovereignFundingAmount = 3_500_000_000n; - - // funding parachain sovereing account (Parachain: 2095) - const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN); - await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount); - }); - - await usingPlaygrounds(async (helper) => { - const location = { - parents: 1, - interior: {X3: [ - { - Parachain: STATEMINE_CHAIN, - }, - { - PalletInstance: STATEMINE_PALLET_INSTANCE, - }, - { - GeneralIndex: USDT_ASSET_ID, - }, - ]}, - }; - const assetId = {Concrete: location}; - - if(await helper.foreignAssets.foreignCollectionId(assetId) == null) { - const tokenPrefix = USDT_ASSET_METADATA_NAME; - await helper.getSudo().foreignAssets.register( - alice, - assetId, - USDT_ASSET_METADATA_NAME, - tokenPrefix, - {Fungible: USDT_ASSET_METADATA_DECIMALS}, - ); - } else { - console.log('Foreign collection is already registered on Opal'); - } - - balanceOpalBefore = await helper.balance.getSubstrate(alice.address); - usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId); - }); - - // Providing the relay currency to the unique sender account - await usingRelayPlaygrounds(relayUrl, async (helper) => { - const destination = { - V2: { - parents: 0, - interior: {X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }}; - - const beneficiary = { - V2: { - parents: 0, - interior: {X1: { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }}, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: 50_000_000_000_000_000n, - }, - }, - ], - }; - - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - }); - - }); - - itSub('Should connect and send USDT from Westmint to Opal', async ({helper}) => { - await usingWestmintPlaygrounds(westmintUrl, async (helper) => { - const dest = { - V2: { - parents: 1, - interior: {X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }}; - - const beneficiary = { - V2: { - parents: 0, - interior: {X1: { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }}, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: { - X2: [ - { - PalletInstance: STATEMINE_PALLET_INSTANCE, - }, - { - GeneralIndex: USDT_ASSET_ID, - }, - ]}, - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - ], - }; - - const feeAssetItem = 0; - - balanceStmnBefore = await helper.balance.getSubstrate(alice.address); - await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited'); - - balanceStmnAfter = await helper.balance.getSubstrate(alice.address); - - // common good parachain take commission in it native token - console.log( - '[Westmint -> Opal] transaction fees on Westmint: %s WND', - helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, WESTMINT_DECIMALS), - ); - expect(balanceStmnBefore > balanceStmnAfter).to.be.true; - - }); - - // ensure that asset has been delivered - await helper.wait.newBlocks(3); - - const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address}); - - balanceOpalAfter = await helper.balance.getSubstrate(alice.address); - - console.log( - '[Westmint -> Opal] transaction fees on Opal: %s USDT', - helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS), - ); - console.log( - '[Westmint -> Opal] transaction fees on Opal: %s OPL', - helper.util.bigIntToDecimals(balanceOpalAfter - balanceOpalBefore), - ); - - // commission has not paid in USDT token - expect(free == TRANSFER_AMOUNT).to.be.true; - // ... and parachain native token - expect(balanceOpalAfter == balanceOpalBefore).to.be.true; - }); - - itSub('Should connect and send USDT from Unique to Statemine back', async ({helper}) => { - const destination = { - V2: { - parents: 1, - interior: {X2: [ - { - Parachain: STATEMINE_CHAIN, - }, - { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }, - ]}, - }, - }; - - const currencies: [any, bigint][] = [ - [ - usdtCollectionId, - TRANSFER_AMOUNT, - ], - [ - relayCollectionId, - 400_000_000_000_000n, - ], - ]; - - const feeItem = 1; - - await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited'); - - // 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 - expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true; - }); - }); - - itSub('Should connect and send Relay token to Unique', async ({helper}) => { - const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n; - - balanceBobBefore = await helper.balance.getSubstrate(bob.address); - balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address}); - - // Providing the relay currency to the unique sender account - await usingRelayPlaygrounds(relayUrl, async (helper) => { - const destination = { - V2: { - parents: 0, - interior: {X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }}; - - const beneficiary = { - V2: { - parents: 0, - interior: {X1: { - AccountId32: { - network: 'Any', - id: bob.addressRaw, - }, - }}, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT_RELAY, - }, - }, - ], - }; - - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - }); - - await helper.wait.newBlocks(3); - - balanceBobAfter = await helper.balance.getSubstrate(bob.address); - balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address}); - - 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; - }); - - itSub('Should connect and send Relay token back', async ({helper}) => { - let relayTokenBalanceBefore: bigint; - let relayTokenBalanceAfter: bigint; - await usingRelayPlaygrounds(relayUrl, async (helper) => { - relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address); - }); - - const destination = { - V2: { - parents: 1, - interior: { - X1:{ - AccountId32: { - network: 'Any', - id: bob.addressRaw, - }, - }, - }, - }, - }; - - const currencies: any = [ - [ - relayCollectionId, - 50_000_000_000_000_000n, - ], - ]; - - const feeItem = 0; - - await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited'); - - balanceBobFinal = await helper.balance.getSubstrate(bob.address); - console.log('[Opal -> Relay (Westend)] transaction fees: %s OPL', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal)); - - await usingRelayPlaygrounds(relayUrl, async (helper) => { - await helper.wait.newBlocks(10); - relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address); - - const diff = relayTokenBalanceAfter - relayTokenBalanceBefore; - console.log('[Opal -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS)); - expect(diff > 0, 'Relay tokens was not delivered back').to.be.true; - }); - }); -}); diff --git a/js-packages/tests/xcm/xcmQuartz.test.ts b/js-packages/tests/xcm/xcmQuartz.test.ts deleted file mode 100644 index 67dc5afaa8..0000000000 --- a/js-packages/tests/xcm/xcmQuartz.test.ts +++ /dev/null @@ -1,1637 +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 type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '@unique/test-utils/util.js'; -import {DevUniqueHelper, Event} from '@unique/test-utils'; -import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types.js'; -import {hexToString} from '@polkadot/util'; -import {XcmTestHelper} from './xcm.types.js'; - -const STATEMINE_PALLET_INSTANCE = 50; - -const TRANSFER_AMOUNT = 2000000000000000000000000n; - -const FUNDING_AMOUNT = 3_500_000_0000_000_000n; - -const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n; - -const USDT_ASSET_ID = 100; -const USDT_ASSET_METADATA_DECIMALS = 18; -const USDT_ASSET_METADATA_NAME = 'USDT'; -const USDT_ASSET_METADATA_DESCRIPTION = 'USDT'; -const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n; -const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n; - -const SAFE_XCM_VERSION = 2; - -const testHelper = new XcmTestHelper('quartz'); - -describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - - let balanceStmnBefore: bigint; - let balanceStmnAfter: bigint; - - let balanceQuartzBefore: bigint; - let balanceQuartzAfter: bigint; - let balanceQuartzFinal: bigint; - - let balanceBobBefore: bigint; - let balanceBobAfter: bigint; - let balanceBobFinal: bigint; - - let balanceBobRelayTokenBefore: bigint; - let balanceBobRelayTokenAfter: bigint; - - let usdtCollectionId: number; - let relayCollectionId: number; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - - relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice); - }); - - await usingRelayPlaygrounds(relayUrl, async (helper) => { - // Fund accounts on Statemine(t) - await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT); - await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT); - }); - - await usingStateminePlaygrounds(statemineUrl, async (helper) => { - const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID); - if(assetInfo == null) { - await helper.assets.create( - alice, - USDT_ASSET_ID, - alice.address, - USDT_ASSET_METADATA_MINIMAL_BALANCE, - ); - await helper.assets.setMetadata( - alice, - USDT_ASSET_ID, - USDT_ASSET_METADATA_NAME, - USDT_ASSET_METADATA_DESCRIPTION, - USDT_ASSET_METADATA_DECIMALS, - ); - } else { - console.log('The USDT asset is already registered on AssetHub'); - } - - await helper.assets.mint( - alice, - USDT_ASSET_ID, - alice.address, - USDT_ASSET_AMOUNT, - ); - - const sovereignFundingAmount = 3_500_000_000n; - - // funding parachain sovereing account on Statemine(t). - // The sovereign account should be created before any action - // (the assets pallet on Statemine(t) check if the sovereign account exists) - const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN); - await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount); - }); - - - await usingPlaygrounds(async (helper) => { - const location = { - parents: 1, - interior: {X3: [ - { - Parachain: STATEMINE_CHAIN, - }, - { - PalletInstance: STATEMINE_PALLET_INSTANCE, - }, - { - GeneralIndex: USDT_ASSET_ID, - }, - ]}, - }; - const assetId = {Concrete: location}; - - if(await helper.foreignAssets.foreignCollectionId(assetId) == null) { - const tokenPrefix = USDT_ASSET_METADATA_NAME; - await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS}); - } else { - console.log('Foreign collection is already registered on Quartz'); - } - - balanceQuartzBefore = await helper.balance.getSubstrate(alice.address); - usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId); - }); - - - // Providing the relay currency to the quartz sender account - // (fee for USDT XCM are paid in relay tokens) - await usingRelayPlaygrounds(relayUrl, async (helper) => { - const destination = { - V2: { - parents: 0, - interior: {X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }}; - - const beneficiary = { - V2: { - parents: 0, - interior: {X1: { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }}, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT_RELAY, - }, - }, - ], - }; - - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - }); - - }); - - itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => { - await usingStateminePlaygrounds(statemineUrl, async (helper) => { - const dest = { - V2: { - parents: 1, - interior: {X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }}; - - const beneficiary = { - V2: { - parents: 0, - interior: {X1: { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }}, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: { - X2: [ - { - PalletInstance: STATEMINE_PALLET_INSTANCE, - }, - { - GeneralIndex: USDT_ASSET_ID, - }, - ]}, - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - ], - }; - - const feeAssetItem = 0; - - balanceStmnBefore = await helper.balance.getSubstrate(alice.address); - await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited'); - - balanceStmnAfter = await helper.balance.getSubstrate(alice.address); - - // common good parachain take commission in it native token - console.log( - '[Statemine -> Quartz] transaction fees on Statemine: %s WND', - helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS), - ); - expect(balanceStmnBefore > balanceStmnAfter).to.be.true; - - }); - - - // ensure that asset has been delivered - await helper.wait.newBlocks(3); - - const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address}); - - balanceQuartzAfter = await helper.balance.getSubstrate(alice.address); - - console.log( - '[Statemine -> Quartz] transaction fees on Quartz: %s USDT', - helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS), - ); - console.log( - '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ', - helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore), - ); - // commission has not paid in USDT token - expect(free).to.be.equal(TRANSFER_AMOUNT); - // ... and parachain native token - expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true; - }); - - itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => { - const destination = { - V2: { - parents: 1, - interior: {X2: [ - { - Parachain: STATEMINE_CHAIN, - }, - { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }, - ]}, - }, - }; - - const relayFee = 400_000_000_000_000n; - const currencies: [any, bigint][] = [ - [ - usdtCollectionId, - TRANSFER_AMOUNT, - ], - [ - relayCollectionId, - relayFee, - ], - ]; - - const feeItem = 1; - - await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited'); - - // the commission has been paid in parachain native token - balanceQuartzFinal = await helper.balance.getSubstrate(alice.address); - console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal)); - expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true; - - await usingStateminePlaygrounds(statemineUrl, 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 - expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true; - }); - }); - - itSub('Should connect and send Relay token to Quartz', async ({helper}) => { - balanceBobBefore = await helper.balance.getSubstrate(bob.address); - balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address}); - - await usingRelayPlaygrounds(relayUrl, async (helper) => { - const destination = { - V2: { - parents: 0, - interior: {X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }}; - - const beneficiary = { - V2: { - parents: 0, - interior: {X1: { - AccountId32: { - network: 'Any', - id: bob.addressRaw, - }, - }}, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT_RELAY, - }, - }, - ], - }; - - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - }); - - await helper.wait.newBlocks(3); - - balanceBobAfter = await helper.balance.getSubstrate(bob.address); - balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address}); - - const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore; - const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore; - console.log( - '[Relay (Westend) -> Quartz] transaction fees: %s QTZ', - helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore), - ); - console.log( - '[Relay (Westend) -> Quartz] transaction fees: %s WND', - helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS), - ); - console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz); - expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true; - expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true; - }); - - itSub('Should connect and send Relay token back', async ({helper}) => { - let relayTokenBalanceBefore: bigint; - let relayTokenBalanceAfter: bigint; - await usingRelayPlaygrounds(relayUrl, async (helper) => { - relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address); - }); - - const destination = { - V2: { - parents: 1, - interior: { - X1:{ - AccountId32: { - network: 'Any', - id: bob.addressRaw, - }, - }, - }, - }, - }; - - const currencies: any = [ - [ - relayCollectionId, - TRANSFER_AMOUNT_RELAY, - ], - ]; - - const feeItem = 0; - - await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited'); - - balanceBobFinal = await helper.balance.getSubstrate(bob.address); - console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal)); - - await usingRelayPlaygrounds(relayUrl, async (helper) => { - await helper.wait.newBlocks(10); - relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address); - - const diff = relayTokenBalanceAfter - relayTokenBalanceBefore; - console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS)); - expect(diff > 0, 'Relay tokens was not delivered back').to.be.true; - }); - }); -}); - -describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => { - let alice: IKeyringPair; - let randomAccount: IKeyringPair; - - let balanceQuartzTokenInit: bigint; - let balanceQuartzTokenMiddle: bigint; - let balanceQuartzTokenFinal: bigint; - let balanceKaruraTokenInit: bigint; - let balanceKaruraTokenMiddle: bigint; - let balanceKaruraTokenFinal: bigint; - let balanceQuartzForeignTokenInit: bigint; - let balanceQuartzForeignTokenMiddle: bigint; - let balanceQuartzForeignTokenFinal: bigint; - - // computed by a test transfer from prod Quartz to prod Karura. - // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9 - // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block) - const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n; - const karuraEps = 8n * 10n ** 16n; - - let karuraBackwardTransferAmount: bigint; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - [randomAccount] = await helper.arrange.createAccounts([0n], alice); - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - - await usingKaruraPlaygrounds(karuraUrl, async (helper) => { - const destination = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }; - - const metadata = { - name: 'Quartz', - symbol: 'QTZ', - decimals: 18, - minimalBalance: 1000000000000000000n, - }; - - const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) => - hexToString(v.toJSON()['symbol'])) as string[]; - - if(!assets.includes('QTZ')) { - await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); - } else { - console.log('QTZ token already registered on Karura assetRegistry pallet'); - } - 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 = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: KARURA_CHAIN, - }, - }, - }, - }; - - const beneficiary = { - V2: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, - }, - }, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - ], - }; - - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - - const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT; - expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true; - console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees)); - - 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; - karuraBackwardTransferAmount = qtzIncomeTransfer; - - const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer; - - console.log( - '[Quartz -> Karura] transaction fees on Karura: %s KAR', - helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS), - ); - console.log( - '[Quartz -> Karura] transaction fees on Karura: %s QTZ', - helper.util.bigIntToDecimals(karUnqFees), - ); - console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer)); - expect(karFees == 0n).to.be.true; - - const bigintAbs = (n: bigint) => (n < 0n) ? -n : n; - - expect( - bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps, - 'Karura took different income fee, check the Karura foreign asset config', - ).to.be.true; - }); - }); - - itSub('Should connect to Karura and send QTZ back', async ({helper}) => { - await usingKaruraPlaygrounds(karuraUrl, async (helper) => { - const destination = { - V2: { - parents: 1, - interior: { - X2: [ - {Parachain: QUARTZ_CHAIN}, - { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, - }, - ], - }, - }, - }; - - const id = { - ForeignAsset: 0, - }; - - await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited'); - balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address); - balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id); - - const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal; - const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal; - - 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, 'Negative fees KAR, looks like nothing was transferred').to.be.true; - expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true; - }); - - await helper.wait.newBlocks(3); - - 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', helper.util.bigIntToDecimals(actuallyDelivered)); - - const qtzFees = karuraBackwardTransferAmount - actuallyDelivered; - console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees)); - expect(qtzFees == 0n).to.be.true; - }); - - itSub('Karura can send only up to its balance', async ({helper}) => { - // set Karura's sovereign account's balance - const karuraBalance = 10000n * (10n ** QTZ_DECIMALS); - const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN); - await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance); - - const moreThanKaruraHas = karuraBalance * 2n; - - let targetAccountBalance = 0n; - const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice); - - const quartzMultilocation = { - V2: { - parents: 1, - interior: { - X1: {Parachain: QUARTZ_CHAIN}, - }, - }, - }; - - const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - moreThanKaruraHas, - ); - - let maliciousXcmProgramSent: any; - const maxWaitBlocks = 5; - - // Try to trick Quartz - await usingKaruraPlaygrounds(karuraUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram); - - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash - && event.outcome.isFailedToTransactAsset); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(0n); - - // But Karura still can send the correct amount - const validTransferAmount = karuraBalance / 2n; - const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - validTransferAmount, - ); - - await usingKaruraPlaygrounds(karuraUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram); - }); - - await helper.wait.newBlocks(maxWaitBlocks); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(validTransferAmount); - }); - - itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => { - const testAmount = 10_000n * (10n ** QTZ_DECIMALS); - const [targetAccount] = await helper.arrange.createAccounts([0n], alice); - - const quartzMultilocation = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }; - - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }, - testAmount, - ); - - const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - testAmount, - ); - - let maliciousXcmProgramFullIdSent: any; - let maliciousXcmProgramHereIdSent: any; - const maxWaitBlocks = 3; - - // Try to trick Quartz using full QTZ identification - await usingKaruraPlaygrounds(karuraUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId); - - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - let accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - - // Try to trick Quartz using shortened QTZ identification - await usingKaruraPlaygrounds(karuraUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId); - - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - }); -}); - -// These tests are relevant only when -// the the corresponding foreign assets are not registered -describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => { - let alice: IKeyringPair; - let alith: IKeyringPair; - - const testAmount = 100_000_000_000n; - let quartzParachainJunction; - let quartzAccountJunction; - - let quartzParachainMultilocation: any; - let quartzAccountMultilocation: any; - let quartzCombinedMultilocation: any; - - let messageSent: any; - - const maxWaitBlocks = 3; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - - quartzParachainJunction = {Parachain: QUARTZ_CHAIN}; - quartzAccountJunction = { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }; - - quartzParachainMultilocation = { - V2: { - parents: 1, - interior: { - X1: quartzParachainJunction, - }, - }, - }; - - quartzAccountMultilocation = { - V2: { - parents: 0, - interior: { - X1: quartzAccountJunction, - }, - }, - }; - - quartzCombinedMultilocation = { - V2: { - parents: 1, - interior: { - X2: [quartzParachainJunction, quartzAccountJunction], - }, - }, - }; - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - - // eslint-disable-next-line require-await - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - alith = helper.account.alithAccount(); - }); - }); - - const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => { - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash - && event.outcome.isFailedToTransactAsset); - }; - - itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => { - await usingKaruraPlaygrounds(karuraUrl, async (helper) => { - const id = { - Token: 'KAR', - }; - const destination = quartzCombinedMultilocation; - await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited'); - - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectFailedToTransact(helper, messageSent); - }); - - itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => { - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - const id = 'SelfReserve'; - const destination = quartzCombinedMultilocation; - await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited'); - - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectFailedToTransact(helper, messageSent); - }); - - itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => { - await usingShidenPlaygrounds(shidenUrl, async (helper) => { - const destinationParachain = quartzParachainMultilocation; - const beneficiary = quartzAccountMultilocation; - const assets = { - V2: [{ - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: testAmount, - }, - }], - }; - const feeAssetItem = 0; - - await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [ - destinationParachain, - beneficiary, - assets, - feeAssetItem, - ]); - - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectFailedToTransact(helper, messageSent); - }); -}); - -describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => { - // Quartz constants - let alice: IKeyringPair; - let quartzAssetLocation; - - let randomAccountQuartz: IKeyringPair; - let randomAccountMoonriver: IKeyringPair; - - // Moonriver constants - let assetId: string; - - const quartzAssetMetadata = { - name: 'xcQuartz', - symbol: 'xcQTZ', - decimals: 18, - isFrozen: false, - minimalBalance: 1n, - }; - - let balanceQuartzTokenInit: bigint; - let balanceQuartzTokenMiddle: bigint; - let balanceQuartzTokenFinal: bigint; - let balanceForeignQtzTokenInit: bigint; - let balanceForeignQtzTokenMiddle: bigint; - let balanceForeignQtzTokenFinal: bigint; - let balanceMovrTokenInit: bigint; - let balanceMovrTokenMiddle: bigint; - let balanceMovrTokenFinal: bigint; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice); - - balanceForeignQtzTokenInit = 0n; - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - const alithAccount = helper.account.alithAccount(); - const baltatharAccount = helper.account.baltatharAccount(); - const dorothyAccount = helper.account.dorothyAccount(); - - randomAccountMoonriver = helper.account.create(); - - // >>> 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: { - parents: 1, - interior: {X1: {Parachain: QUARTZ_CHAIN}}, - }, - }; - const existentialDeposit = 1n; - const isSufficient = true; - const unitsPerSecond = 1n; - const numAssetsWeightHint = 0; - - if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) { - console.log('Quartz asset already registered on Moonriver'); - } else { - const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ - location: quartzAssetLocation, - metadata: quartzAssetMetadata, - existentialDeposit, - isSufficient, - unitsPerSecond, - numAssetsWeightHint, - }); - - console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); - - await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal); - } - // >>> Acquire Quartz AssetId Info on Moonriver >>> - console.log('Acquire Quartz AssetId Info on Moonriver.......'); - - assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString(); - - console.log('QTZ asset ID is %s', assetId); - console.log('Acquire Quartz AssetId Info on Moonriver.......DONE'); - // >>> Acquire Quartz AssetId Info on Moonriver >>> - - // >>> 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 <<< - - balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address); - }); - - await usingPlaygrounds(async (helper) => { - await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT); - balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address); - }); - }); - - itSub('Should connect and send QTZ to Moonriver', async ({helper}) => { - const currencyId = 0; - const dest = { - V2: { - parents: 1, - interior: { - X2: [ - {Parachain: MOONRIVER_CHAIN}, - {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}}, - ], - }, - }, - }; - const amount = TRANSFER_AMOUNT; - - await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited'); - - 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', helper.util.bigIntToDecimals(transactionFees)); - expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true; - - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - await helper.wait.newBlocks(3); - - balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address); - - const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle; - console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees)); - expect(movrFees == 0n).to.be.true; - - 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; - }); - }); - - itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => { - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - const asset = { - V2: { - id: { - Concrete: { - parents: 1, - interior: { - X1: {Parachain: QUARTZ_CHAIN}, - }, - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - }; - const destination = { - V2: { - parents: 1, - interior: { - X2: [ - {Parachain: QUARTZ_CHAIN}, - {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}}, - ], - }, - }, - }; - - await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited'); - - balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address); - - const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal; - console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees)); - expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true; - - const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address); - - expect(qtzRandomAccountAsset).to.be.null; - - balanceForeignQtzTokenFinal = 0n; - - const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal; - console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer)); - expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; - }); - - await helper.wait.newBlocks(3); - - 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', helper.util.bigIntToDecimals(actuallyDelivered)); - - 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; - }); - - itSub('Moonriver can send only up to its balance', async ({helper}) => { - // set Moonriver's sovereign account's balance - const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS); - const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN); - await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance); - - const moreThanMoonriverHas = moonriverBalance * 2n; - - let targetAccountBalance = 0n; - const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice); - - const quartzMultilocation = { - V2: { - parents: 1, - interior: { - X1: {Parachain: QUARTZ_CHAIN}, - }, - }, - }; - - const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - moreThanMoonriverHas, - ); - - let maliciousXcmProgramSent: any; - const maxWaitBlocks = 3; - - // Try to trick Quartz - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]); - - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall); - - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash - && event.outcome.isFailedToTransactAsset); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(0n); - - // But Moonriver still can send the correct amount - const validTransferAmount = moonriverBalance / 2n; - const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - validTransferAmount, - ); - - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]); - - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall); - }); - - await helper.wait.newBlocks(maxWaitBlocks); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(validTransferAmount); - }); - - itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => { - const testAmount = 10_000n * (10n ** QTZ_DECIMALS); - const [targetAccount] = await helper.arrange.createAccounts([0n], alice); - - const quartzMultilocation = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }; - - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }, - testAmount, - ); - - const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - testAmount, - ); - - let maliciousXcmProgramFullIdSent: any; - let maliciousXcmProgramHereIdSent: any; - const maxWaitBlocks = 3; - - // Try to trick Quartz using full QTZ identification - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]); - - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall); - - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - let accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - - // Try to trick Quartz using shortened QTZ identification - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]); - - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall); - - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - }); -}); - -describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => { - let alice: IKeyringPair; - let sender: IKeyringPair; - - const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden - const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden - - // Quartz -> Shiden - const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden - const unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden - const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ - const qtzToShidenArrived = 7_998_196_000_000_000_000n; // 7.99 ... QTZ, Shiden takes a commision in foreign tokens - - // Shiden -> Quartz - const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ - const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 2.99 ... QTZ - - let balanceAfterQuartzToShidenXCM: bigint; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - [sender] = await helper.arrange.createAccounts([100n], alice); - console.log('sender', sender.address); - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - - await usingShidenPlaygrounds(shidenUrl, async (helper) => { - if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) { - console.log('1. Create foreign asset and metadata'); - await helper.getSudo().assets.forceCreate( - alice, - QTZ_ASSET_ID_ON_SHIDEN, - alice.address, - QTZ_MINIMAL_BALANCE_ON_SHIDEN, - ); - - await helper.assets.setMetadata( - alice, - QTZ_ASSET_ID_ON_SHIDEN, - 'Quartz', - 'QTZ', - Number(QTZ_DECIMALS), - ); - - console.log('2. Register asset location on Shiden'); - const assetLocation = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }; - - await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]); - - console.log('3. Set QTZ payment for XCM execution on Shiden'); - await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]); - } else { - console.log('QTZ is already registered on Shiden'); - } - console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)'); - await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance); - }); - }); - - itSub('Should connect and send QTZ to Shiden', async ({helper}) => { - const destination = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: SHIDEN_CHAIN, - }, - }, - }, - }; - - const beneficiary = { - V2: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: sender.addressRaw, - }, - }, - }, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: qtzToShidenTransferred, - }, - }, - ], - }; - - // Initial balance is 100 QTZ - const balanceBefore = await helper.balance.getSubstrate(sender.address); - console.log(`Initial balance is: ${balanceBefore}`); - - const feeAssetItem = 0; - await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - - // Balance after reserve transfer is less than 90 - balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address); - console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`); - console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`); - expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true; - - await usingShidenPlaygrounds(shidenUrl, async (helper) => { - await helper.wait.newBlocks(3); - const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address); - const shidenBalance = await helper.balance.getSubstrate(sender.address); - - console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`); - console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`); - - expect(xcQTZbalance).to.eq(qtzToShidenArrived); - // SHD balance does not changed: - expect(shidenBalance).to.eq(shidenInitialBalance); - }); - }); - - itSub('Should connect to Shiden and send QTZ back', async ({helper}) => { - await usingShidenPlaygrounds(shidenUrl, async (helper) => { - const destination = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }; - - const beneficiary = { - V2: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: sender.addressRaw, - }, - }, - }, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }, - fun: { - Fungible: qtzFromShidenTransfered, - }, - }, - ], - }; - - // Initial balance is 1 SDN - const balanceSDNbefore = await helper.balance.getSubstrate(sender.address); - console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`); - expect(balanceSDNbefore).to.eq(shidenInitialBalance); - - const feeAssetItem = 0; - // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw - await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]); - - // Balance after reserve transfer is less than 1 SDN - const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address); - const balanceSDN = await helper.balance.getSubstrate(sender.address); - console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`); - - // Assert: xcQTZ balance correctly decreased - expect(xcQTZbalance).to.eq(qtzOnShidenLeft); - // Assert: SDN balance is 0.996... - expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n); - }); - - await helper.wait.newBlocks(3); - const balanceQTZ = await helper.balance.getSubstrate(sender.address); - console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`); - expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered); - }); - - itSub('Shiden can send only up to its balance', async ({helper}) => { - // set Shiden's sovereign account's balance - const shidenBalance = 10000n * (10n ** QTZ_DECIMALS); - const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN); - await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance); - - const moreThanShidenHas = shidenBalance * 2n; - - let targetAccountBalance = 0n; - const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice); - - const quartzMultilocation = { - V2: { - parents: 1, - interior: { - X1: {Parachain: QUARTZ_CHAIN}, - }, - }, - }; - - const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - moreThanShidenHas, - ); - - let maliciousXcmProgramSent: any; - const maxWaitBlocks = 3; - - // Try to trick Quartz - await usingShidenPlaygrounds(shidenUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram); - - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash - && event.outcome.isFailedToTransactAsset); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(0n); - - // But Shiden still can send the correct amount - const validTransferAmount = shidenBalance / 2n; - const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - validTransferAmount, - ); - - await usingShidenPlaygrounds(shidenUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram); - }); - - await helper.wait.newBlocks(maxWaitBlocks); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(validTransferAmount); - }); - - itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => { - const testAmount = 10_000n * (10n ** QTZ_DECIMALS); - const [targetAccount] = await helper.arrange.createAccounts([0n], alice); - - const quartzMultilocation = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }; - - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, - }, - }, - }, - }, - testAmount, - ); - - const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - testAmount, - ); - - let maliciousXcmProgramFullIdSent: any; - let maliciousXcmProgramHereIdSent: any; - const maxWaitBlocks = 3; - - // Try to trick Quartz using full QTZ identification - await usingShidenPlaygrounds(shidenUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId); - - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - let accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - - // Try to trick Quartz using shortened QTZ identification - await usingShidenPlaygrounds(shidenUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId); - - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - }); -}); diff --git a/js-packages/tests/xcm/xcmUnique.test.ts b/js-packages/tests/xcm/xcmUnique.test.ts deleted file mode 100644 index 1df2091d6d..0000000000 --- a/js-packages/tests/xcm/xcmUnique.test.ts +++ /dev/null @@ -1,1836 +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 type {IKeyringPair} from '@polkadot/types/types'; -import config from '../config.js'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '@unique/test-utils/util.js'; -import {Event} from '@unique/test-utils'; -import {hexToString, nToBigInt} from '@polkadot/util'; -import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js'; -import {XcmTestHelper} from './xcm.types.js'; - -const STATEMINT_PALLET_INSTANCE = 50; - -const relayUrl = config.relayUrl; -const statemintUrl = config.statemintUrl; -const acalaUrl = config.acalaUrl; -const moonbeamUrl = config.moonbeamUrl; -const astarUrl = config.astarUrl; -const polkadexUrl = config.polkadexUrl; - -const RELAY_DECIMALS = 12; -const STATEMINT_DECIMALS = 12; -const ACALA_DECIMALS = 12; -const ASTAR_DECIMALS = 18n; -const UNQ_DECIMALS = 18n; - -const TRANSFER_AMOUNT = 2000000000000000000000000n; - -const FUNDING_AMOUNT = 3_500_000_0000_000_000n; - -const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n; - -const USDT_ASSET_ID = 100; -const USDT_ASSET_METADATA_DECIMALS = 18; -const USDT_ASSET_METADATA_NAME = 'USDT'; -const USDT_ASSET_METADATA_DESCRIPTION = 'USDT'; -const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n; -const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n; - -const testHelper = new XcmTestHelper('unique'); - -describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - - let balanceStmnBefore: bigint; - let balanceStmnAfter: bigint; - - let balanceUniqueBefore: bigint; - let balanceUniqueAfter: bigint; - let balanceUniqueFinal: bigint; - - let balanceBobBefore: bigint; - let balanceBobAfter: bigint; - let balanceBobFinal: bigint; - - let balanceBobRelayTokenBefore: bigint; - let balanceBobRelayTokenAfter: bigint; - - let usdtCollectionId: number; - let relayCollectionId: number; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - - relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice); - }); - - await usingRelayPlaygrounds(relayUrl, async (helper) => { - // Fund accounts on Statemint - await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT); - await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT); - }); - - await usingStatemintPlaygrounds(statemintUrl, async (helper) => { - const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID); - if(assetInfo == null) { - await helper.assets.create( - alice, - USDT_ASSET_ID, - alice.address, - USDT_ASSET_METADATA_MINIMAL_BALANCE, - ); - await helper.assets.setMetadata( - alice, - USDT_ASSET_ID, - USDT_ASSET_METADATA_NAME, - USDT_ASSET_METADATA_DESCRIPTION, - USDT_ASSET_METADATA_DECIMALS, - ); - } else { - console.log('The USDT asset is already registered on AssetHub'); - } - - await helper.assets.mint( - alice, - USDT_ASSET_ID, - alice.address, - USDT_ASSET_AMOUNT, - ); - - const sovereignFundingAmount = 3_500_000_000n; - - // funding parachain sovereing account on Statemint. - // The sovereign account should be created before any action - // (the assets pallet on Statemint check if the sovereign account exists) - const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN); - await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount); - }); - - - await usingPlaygrounds(async (helper) => { - const location = { - parents: 1, - interior: {X3: [ - { - Parachain: STATEMINT_CHAIN, - }, - { - PalletInstance: STATEMINT_PALLET_INSTANCE, - }, - { - GeneralIndex: USDT_ASSET_ID, - }, - ]}, - }; - const assetId = {Concrete: location}; - - if(await helper.foreignAssets.foreignCollectionId(assetId) == null) { - const tokenPrefix = USDT_ASSET_METADATA_NAME; - await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS}); - } else { - console.log('Foreign collection is already registered on Unique'); - } - - balanceUniqueBefore = await helper.balance.getSubstrate(alice.address); - usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId); - }); - - - // Providing the relay currency to the unique sender account - // (fee for USDT XCM are paid in relay tokens) - await usingRelayPlaygrounds(relayUrl, async (helper) => { - const destination = { - V2: { - parents: 0, - interior: {X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }}; - - const beneficiary = { - V2: { - parents: 0, - interior: {X1: { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }}, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT_RELAY, - }, - }, - ], - }; - - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - }); - - }); - - itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => { - await usingStatemintPlaygrounds(statemintUrl, async (helper) => { - const dest = { - V2: { - parents: 1, - interior: {X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }}; - - const beneficiary = { - V2: { - parents: 0, - interior: {X1: { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }}, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: { - X2: [ - { - PalletInstance: STATEMINT_PALLET_INSTANCE, - }, - { - GeneralIndex: USDT_ASSET_ID, - }, - ]}, - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - ], - }; - - const feeAssetItem = 0; - - balanceStmnBefore = await helper.balance.getSubstrate(alice.address); - await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited'); - - balanceStmnAfter = await helper.balance.getSubstrate(alice.address); - - // common good parachain take commission in it native token - console.log( - '[Statemint -> Unique] transaction fees on Statemint: %s WND', - helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS), - ); - expect(balanceStmnBefore > balanceStmnAfter).to.be.true; - - }); - - - // ensure that asset has been delivered - await helper.wait.newBlocks(3); - - const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address}); - - balanceUniqueAfter = await helper.balance.getSubstrate(alice.address); - - console.log( - '[Statemint -> Unique] transaction fees on Unique: %s USDT', - helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS), - ); - console.log( - '[Statemint -> Unique] transaction fees on Unique: %s UNQ', - helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore), - ); - // commission has not paid in USDT token - expect(free).to.be.equal(TRANSFER_AMOUNT); - // ... and parachain native token - expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true; - }); - - itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => { - const destination = { - V2: { - parents: 1, - interior: {X2: [ - { - Parachain: STATEMINT_CHAIN, - }, - { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }, - ]}, - }, - }; - - const relayFee = 400_000_000_000_000n; - const currencies: [any, bigint][] = [ - [ - usdtCollectionId, - TRANSFER_AMOUNT, - ], - [ - relayCollectionId, - relayFee, - ], - ]; - - const feeItem = 1; - - await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited'); - - // the commission has been paid in parachain native token - balanceUniqueFinal = await helper.balance.getSubstrate(alice.address); - console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal)); - expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true; - - await usingStatemintPlaygrounds(statemintUrl, 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 - expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true; - }); - }); - - itSub('Should connect and send Relay token to Unique', async ({helper}) => { - balanceBobBefore = await helper.balance.getSubstrate(bob.address); - balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address}); - - await usingRelayPlaygrounds(relayUrl, async (helper) => { - const destination = { - V2: { - parents: 0, - interior: {X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }}; - - const beneficiary = { - V2: { - parents: 0, - interior: {X1: { - AccountId32: { - network: 'Any', - id: bob.addressRaw, - }, - }}, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT_RELAY, - }, - }, - ], - }; - - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - }); - - await helper.wait.newBlocks(3); - - balanceBobAfter = await helper.balance.getSubstrate(bob.address); - balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address}); - - const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore; - const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore; - console.log( - '[Relay (Westend) -> Unique] transaction fees: %s UNQ', - helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore), - ); - console.log( - '[Relay (Westend) -> Unique] transaction fees: %s WND', - helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS), - ); - console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique); - expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true; - expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true; - }); - - itSub('Should connect and send Relay token back', async ({helper}) => { - let relayTokenBalanceBefore: bigint; - let relayTokenBalanceAfter: bigint; - await usingRelayPlaygrounds(relayUrl, async (helper) => { - relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address); - }); - - const destination = { - V2: { - parents: 1, - interior: { - X1:{ - AccountId32: { - network: 'Any', - id: bob.addressRaw, - }, - }, - }, - }, - }; - - const currencies: any = [ - [ - relayCollectionId, - TRANSFER_AMOUNT_RELAY, - ], - ]; - - const feeItem = 0; - - await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited'); - - balanceBobFinal = await helper.balance.getSubstrate(bob.address); - console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal)); - - await usingRelayPlaygrounds(relayUrl, async (helper) => { - await helper.wait.newBlocks(10); - relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address); - - const diff = relayTokenBalanceAfter - relayTokenBalanceBefore; - console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS)); - expect(diff > 0, 'Relay tokens was not delivered back').to.be.true; - }); - }); -}); - -describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => { - let alice: IKeyringPair; - let randomAccount: IKeyringPair; - - let balanceUniqueTokenInit: bigint; - let balanceUniqueTokenMiddle: bigint; - let balanceUniqueTokenFinal: bigint; - let balanceAcalaTokenInit: bigint; - let balanceAcalaTokenMiddle: bigint; - let balanceAcalaTokenFinal: bigint; - let balanceUniqueForeignTokenInit: bigint; - let balanceUniqueForeignTokenMiddle: bigint; - let balanceUniqueForeignTokenFinal: bigint; - - // computed by a test transfer from prod Unique to prod Acala. - // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48 - // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block) - const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n; - const acalaEps = 8n * 10n ** 16n; - - let acalaBackwardTransferAmount: bigint; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - [randomAccount] = await helper.arrange.createAccounts([0n], alice); - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - - await usingAcalaPlaygrounds(acalaUrl, async (helper) => { - const destination = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }, - }; - - const metadata = { - name: 'Unique Network', - symbol: 'UNQ', - decimals: 18, - minimalBalance: 1250000000000000000n, - }; - const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) => - hexToString(v.toJSON()['symbol'])) as string[]; - - if(!assets.includes('UNQ')) { - await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); - } else { - console.log('UNQ token already registered on Acala assetRegistry pallet'); - } - 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 = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: ACALA_CHAIN, - }, - }, - }, - }; - - const beneficiary = { - V2: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, - }, - }, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - ], - }; - - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - - const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT; - console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees)); - expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true; - - await usingAcalaPlaygrounds(acalaUrl, async (helper) => { - await helper.wait.newBlocks(3); - - balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0}); - balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - - const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle; - const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit; - acalaBackwardTransferAmount = unqIncomeTransfer; - - const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer; - - console.log( - '[Unique -> Acala] transaction fees on Acala: %s ACA', - helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS), - ); - console.log( - '[Unique -> Acala] transaction fees on Acala: %s UNQ', - helper.util.bigIntToDecimals(acaUnqFees), - ); - console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer)); - expect(acaFees == 0n).to.be.true; - - const bigintAbs = (n: bigint) => (n < 0n) ? -n : n; - - expect( - bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps, - 'Acala took different income fee, check the Acala foreign asset config', - ).to.be.true; - }); - }); - - itSub('Should connect to Acala and send UNQ back', async ({helper}) => { - await usingAcalaPlaygrounds(acalaUrl, async (helper) => { - const destination = { - V2: { - parents: 1, - interior: { - X2: [ - {Parachain: UNIQUE_CHAIN}, - { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, - }, - ], - }, - }, - }; - - const id = { - ForeignAsset: 0, - }; - - await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited'); - balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address); - balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id); - - const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal; - const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal; - - 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, 'Negative fees ACA, looks like nothing was transferred').to.be.true; - expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true; - }); - - await helper.wait.newBlocks(3); - - 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', helper.util.bigIntToDecimals(actuallyDelivered)); - - const unqFees = acalaBackwardTransferAmount - actuallyDelivered; - console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees)); - expect(unqFees == 0n).to.be.true; - }); - - itSub('Acala can send only up to its balance', async ({helper}) => { - // set Acala's sovereign account's balance - const acalaBalance = 10000n * (10n ** UNQ_DECIMALS); - const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN); - await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance); - - const moreThanAcalaHas = acalaBalance * 2n; - - let targetAccountBalance = 0n; - const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice); - - const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - moreThanAcalaHas, - ); - - let maliciousXcmProgramSent: any; - const maxWaitBlocks = 3; - - // Try to trick Unique - await usingAcalaPlaygrounds(acalaUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram); - - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash - && event.outcome.isFailedToTransactAsset); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(0n); - - // But Acala still can send the correct amount - const validTransferAmount = acalaBalance / 2n; - const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - validTransferAmount, - ); - - await usingAcalaPlaygrounds(acalaUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram); - }); - - await helper.wait.newBlocks(maxWaitBlocks); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(validTransferAmount); - }); - - itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => { - const testAmount = 10_000n * (10n ** UNQ_DECIMALS); - const [targetAccount] = await helper.arrange.createAccounts([0n], alice); - - const uniqueMultilocation = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }, - }; - - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - uniqueAssetId, - testAmount, - ); - - const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - testAmount, - ); - - let maliciousXcmProgramFullIdSent: any; - let maliciousXcmProgramHereIdSent: any; - const maxWaitBlocks = 3; - - // Try to trick Unique using full UNQ identification - await usingAcalaPlaygrounds(acalaUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId); - - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - let accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - - // Try to trick Unique using shortened UNQ identification - await usingAcalaPlaygrounds(acalaUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId); - - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - }); -}); - -describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => { - let alice: IKeyringPair; - let randomAccount: IKeyringPair; - let unqFees: bigint; - let balanceUniqueTokenInit: bigint; - let balanceUniqueTokenMiddle: bigint; - let balanceUniqueTokenFinal: bigint; - const maxWaitBlocks = 6; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - [randomAccount] = await helper.arrange.createAccounts([0n], alice); - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - - await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => { - const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', [])) - .toJSON() as []) - .map(nToBigInt).length != 0; - /* - Check whether the Unique token has been added - to the whitelist, since an error will occur - if it is added again. Needed for debugging - when this test is run multiple times. - */ - if(isWhitelisted) { - console.log('UNQ token is already whitelisted on Polkadex'); - } else { - await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId); - } - - await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); - }); - - 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 Polkadex', async ({helper}) => { - - const destination = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: POLKADEX_CHAIN, - }, - }, - }, - }; - - const beneficiary = { - V2: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, - }, - }, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - ], - }; - - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - - unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT; - console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees)); - expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true; - - await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => { - /* - Since only the parachain part of the Polkadex - infrastructure is launched (without their - solochain validators), processing incoming - assets will lead to an error. - This error indicates that the Polkadex chain - received a message from the Unique network, - since the hash is being checked to ensure - it matches what was sent. - */ - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash); - }); - }); - - - itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => { - - const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - randomAccount.addressRaw, - uniqueAssetId, - TRANSFER_AMOUNT, - ); - - let xcmProgramSent: any; - - - await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram); - - xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash); - - balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address); - - expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees); - }); - - itSub('Polkadex can send only up to its balance', async ({helper}) => { - const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS); - const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN); - await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance); - const moreThanPolkadexHas = 2n * polkadexBalance; - - const targetAccount = helper.arrange.createEmptyAccount(); - - const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - moreThanPolkadexHas, - ); - - let maliciousXcmProgramSent: any; - - - await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram); - - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectFailedToTransact(helper, maliciousXcmProgramSent); - - const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(0n); - }); - - itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => { - const testAmount = 10_000n * (10n ** UNQ_DECIMALS); - const targetAccount = helper.arrange.createEmptyAccount(); - - const uniqueMultilocation = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }, - }; - - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - uniqueAssetId, - testAmount, - ); - - const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - testAmount, - ); - - let maliciousXcmProgramFullIdSent: any; - let maliciousXcmProgramHereIdSent: any; - const maxWaitBlocks = 3; - - // Try to trick Unique using full UNQ identification - await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId); - - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent); - - let accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - - // Try to trick Unique using shortened UNQ identification - await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId); - - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent); - - accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - }); -}); - -// These tests are relevant only when -// the the corresponding foreign assets are not registered -describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => { - let alice: IKeyringPair; - let alith: IKeyringPair; - - const testAmount = 100_000_000_000n; - let uniqueParachainJunction; - let uniqueAccountJunction; - - let uniqueParachainMultilocation: any; - let uniqueAccountMultilocation: any; - let uniqueCombinedMultilocation: any; - let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V2 - - let messageSent: any; - - const maxWaitBlocks = 3; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - - uniqueParachainJunction = {Parachain: UNIQUE_CHAIN}; - uniqueAccountJunction = { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }; - - uniqueParachainMultilocation = { - V2: { - parents: 1, - interior: { - X1: uniqueParachainJunction, - }, - }, - }; - - uniqueAccountMultilocation = { - V2: { - parents: 0, - interior: { - X1: uniqueAccountJunction, - }, - }, - }; - - uniqueCombinedMultilocation = { - V2: { - parents: 1, - interior: { - X2: [uniqueParachainJunction, uniqueAccountJunction], - }, - }, - }; - - uniqueCombinedMultilocationAcala = { - V2: { - parents: 1, - interior: { - X2: [uniqueParachainJunction, uniqueAccountJunction], - }, - }, - }; - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - - // eslint-disable-next-line require-await - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { - alith = helper.account.alithAccount(); - }); - }); - - - - itSub('Unique rejects ACA tokens from Acala', async ({helper}) => { - await usingAcalaPlaygrounds(acalaUrl, async (helper) => { - const id = { - Token: 'ACA', - }; - const destination = uniqueCombinedMultilocationAcala; - await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited'); - - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectFailedToTransact(helper, messageSent); - }); - - itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => { - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { - const id = 'SelfReserve'; - const destination = uniqueCombinedMultilocation; - await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited'); - - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectFailedToTransact(helper, messageSent); - }); - - itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => { - await usingAstarPlaygrounds(astarUrl, async (helper) => { - const destinationParachain = uniqueParachainMultilocation; - const beneficiary = uniqueAccountMultilocation; - const assets = { - V2: [{ - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: testAmount, - }, - }], - }; - const feeAssetItem = 0; - - await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [ - destinationParachain, - beneficiary, - assets, - feeAssetItem, - ]); - - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectFailedToTransact(helper, messageSent); - }); - - itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => { - - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - helper.arrange.createEmptyAccount().addressRaw, - { - Concrete: { - parents: 1, - interior: { - X1: { - Parachain: POLKADEX_CHAIN, - }, - }, - }, - }, - testAmount, - ); - - await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId); - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await expectFailedToTransact(helper, messageSent); - }); -}); - -describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => { - // Unique constants - let alice: IKeyringPair; - let uniqueAssetLocation; - - let randomAccountUnique: IKeyringPair; - let randomAccountMoonbeam: IKeyringPair; - - // Moonbeam constants - let assetId: string; - - const uniqueAssetMetadata = { - name: 'xcUnique', - symbol: 'xcUNQ', - decimals: 18, - isFrozen: false, - minimalBalance: 1n, - }; - - let balanceUniqueTokenInit: bigint; - let balanceUniqueTokenMiddle: bigint; - let balanceUniqueTokenFinal: bigint; - let balanceForeignUnqTokenInit: bigint; - let balanceForeignUnqTokenMiddle: bigint; - let balanceForeignUnqTokenFinal: bigint; - let balanceGlmrTokenInit: bigint; - let balanceGlmrTokenMiddle: bigint; - let balanceGlmrTokenFinal: bigint; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice); - - balanceForeignUnqTokenInit = 0n; - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { - const alithAccount = helper.account.alithAccount(); - const baltatharAccount = helper.account.baltatharAccount(); - const dorothyAccount = helper.account.dorothyAccount(); - - randomAccountMoonbeam = helper.account.create(); - - // >>> 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: { - parents: 1, - interior: {X1: {Parachain: UNIQUE_CHAIN}}, - }, - }; - const existentialDeposit = 1n; - const isSufficient = true; - const unitsPerSecond = 1n; - const numAssetsWeightHint = 0; - - if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) { - console.log('Unique asset is already registered on MoonBeam'); - } else { - const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ - location: uniqueAssetLocation, - metadata: uniqueAssetMetadata, - existentialDeposit, - isSufficient, - unitsPerSecond, - numAssetsWeightHint, - }); - - console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); - - await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal); - } - - // >>> Acquire Unique AssetId Info on Moonbeam >>> - console.log('Acquire Unique AssetId Info on Moonbeam.......'); - - assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString(); - console.log('UNQ asset ID is %s', assetId); - console.log('Acquire Unique AssetId Info on Moonbeam.......DONE'); - // >>> Acquire Unique AssetId Info on Moonbeam >>> - - // >>> 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 <<< - - balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address); - }); - - await usingPlaygrounds(async (helper) => { - await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT); - balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address); - }); - }); - - itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => { - const currencyId = 0; - const dest = { - V2: { - parents: 1, - interior: { - X2: [ - {Parachain: MOONBEAM_CHAIN}, - {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}}, - ], - }, - }, - }; - const amount = TRANSFER_AMOUNT; - - await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited'); - - 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', helper.util.bigIntToDecimals(transactionFees)); - expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true; - - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { - await helper.wait.newBlocks(3); - balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address); - - const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle; - console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees)); - expect(glmrFees == 0n).to.be.true; - - balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!; - - const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit; - console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer)); - expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true; - }); - }); - - itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => { - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { - const asset = { - V2: { - id: { - Concrete: { - parents: 1, - interior: { - X1: {Parachain: UNIQUE_CHAIN}, - }, - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - }; - const destination = { - V2: { - parents: 1, - interior: { - X2: [ - {Parachain: UNIQUE_CHAIN}, - {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}}, - ], - }, - }, - }; - - await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited'); - - balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address); - - const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal; - console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees)); - expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true; - - const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address); - - expect(unqRandomAccountAsset).to.be.null; - - balanceForeignUnqTokenFinal = 0n; - - const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal; - console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer)); - expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; - }); - - await helper.wait.newBlocks(3); - - 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', helper.util.bigIntToDecimals(actuallyDelivered)); - - 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; - }); - - itSub('Moonbeam can send only up to its balance', async ({helper}) => { - // set Moonbeam's sovereign account's balance - const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS); - const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN); - await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance); - - const moreThanMoonbeamHas = moonbeamBalance * 2n; - - let targetAccountBalance = 0n; - const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice); - - const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - moreThanMoonbeamHas, - ); - - let maliciousXcmProgramSent: any; - const maxWaitBlocks = 3; - - // Try to trick Unique - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]); - - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall); - - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash - && event.outcome.isFailedToTransactAsset); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(0n); - - // But Moonbeam still can send the correct amount - const validTransferAmount = moonbeamBalance / 2n; - const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - validTransferAmount, - ); - - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]); - - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall); - }); - - await helper.wait.newBlocks(maxWaitBlocks); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(validTransferAmount); - }); - - itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => { - const testAmount = 10_000n * (10n ** UNQ_DECIMALS); - const [targetAccount] = await helper.arrange.createAccounts([0n], alice); - - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - uniqueAssetId, - testAmount, - ); - - const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - testAmount, - ); - - let maliciousXcmProgramFullIdSent: any; - let maliciousXcmProgramHereIdSent: any; - const maxWaitBlocks = 3; - - // Try to trick Unique using full UNQ identification - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]); - - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall); - - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - let accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - - // Try to trick Unique using shortened UNQ identification - await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]); - - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall); - - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - }); -}); - -describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => { - let alice: IKeyringPair; - let randomAccount: IKeyringPair; - - const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar - const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar - - // Unique -> Astar - const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar. - const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar - const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ - const unqToAstarArrived = 9_962_196_000_000_000_000n; // 9.962 ... UNQ, Astar takes a commision in foreign tokens - - // Astar -> Unique - const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ - const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.962_219_600_000_000_000n UNQ - - let balanceAfterUniqueToAstarXCM: bigint; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - [randomAccount] = await helper.arrange.createAccounts([100n], alice); - console.log('randomAccount', randomAccount.address); - - // Set the default version to wrap the first message to other chains. - await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); - }); - - await usingAstarPlaygrounds(astarUrl, async (helper) => { - if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) { - console.log('1. Create foreign asset and metadata'); - await helper.getSudo().assets.forceCreate( - alice, - UNQ_ASSET_ID_ON_ASTAR, - alice.address, - UNQ_MINIMAL_BALANCE_ON_ASTAR, - ); - - await helper.assets.setMetadata( - alice, - UNQ_ASSET_ID_ON_ASTAR, - 'Unique Network', - 'UNQ', - Number(UNQ_DECIMALS), - ); - - console.log('2. Register asset location on Astar'); - const assetLocation = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }, - }; - - await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]); - - console.log('3. Set UNQ payment for XCM execution on Astar'); - await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]); - } else { - console.log('UNQ is already registered on Astar'); - } - console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)'); - await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance); - }); - }); - - itSub('Should connect and send UNQ to Astar', async ({helper}) => { - const destination = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: ASTAR_CHAIN, - }, - }, - }, - }; - - const beneficiary = { - V2: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, - }, - }, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: unqToAstarTransferred, - }, - }, - ], - }; - - // Initial balance is 100 UNQ - const balanceBefore = await helper.balance.getSubstrate(randomAccount.address); - console.log(`Initial balance is: ${balanceBefore}`); - - const feeAssetItem = 0; - await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - - // Balance after reserve transfer is less than 90 - balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address); - console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`); - console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`); - expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true; - - await usingAstarPlaygrounds(astarUrl, async (helper) => { - await helper.wait.newBlocks(3); - const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address); - const astarBalance = await helper.balance.getSubstrate(randomAccount.address); - - console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`); - console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`); - - expect(xcUNQbalance).to.eq(unqToAstarArrived); - // Astar balance does not changed - expect(astarBalance).to.eq(astarInitialBalance); - }); - }); - - itSub('Should connect to Astar and send UNQ back', async ({helper}) => { - await usingAstarPlaygrounds(astarUrl, async (helper) => { - const destination = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }, - }; - - const beneficiary = { - V2: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: randomAccount.addressRaw, - }, - }, - }, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 1, - interior: { - X1: { - Parachain: UNIQUE_CHAIN, - }, - }, - }, - }, - fun: { - Fungible: unqFromAstarTransfered, - }, - }, - ], - }; - - // Initial balance is 1 ASTR - const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address); - console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`); - expect(balanceASTRbefore).to.eq(astarInitialBalance); - - const feeAssetItem = 0; - // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw - await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]); - - const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address); - const balanceAstar = await helper.balance.getSubstrate(randomAccount.address); - console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`); - - // Assert: xcUNQ balance correctly decreased - expect(xcUNQbalance).to.eq(unqOnAstarLeft); - // Assert: ASTR balance is 0.996... - expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n); - }); - - await helper.wait.newBlocks(3); - const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address); - console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`); - expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered); - }); - - itSub('Astar can send only up to its balance', async ({helper}) => { - // set Astar's sovereign account's balance - const astarBalance = 10000n * (10n ** UNQ_DECIMALS); - const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN); - await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance); - - const moreThanAstarHas = astarBalance * 2n; - - let targetAccountBalance = 0n; - const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice); - - const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - moreThanAstarHas, - ); - - let maliciousXcmProgramSent: any; - const maxWaitBlocks = 3; - - // Try to trick Unique - await usingAstarPlaygrounds(astarUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram); - - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash - && event.outcome.isFailedToTransactAsset); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(0n); - - // But Astar still can send the correct amount - const validTransferAmount = astarBalance / 2n; - const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - validTransferAmount, - ); - - await usingAstarPlaygrounds(astarUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram); - }); - - await helper.wait.newBlocks(maxWaitBlocks); - - targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(validTransferAmount); - }); - - itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => { - const testAmount = 10_000n * (10n ** UNQ_DECIMALS); - const [targetAccount] = await helper.arrange.createAccounts([0n], alice); - - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - uniqueAssetId, - testAmount, - ); - - const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - testAmount, - ); - - let maliciousXcmProgramFullIdSent: any; - let maliciousXcmProgramHereIdSent: any; - const maxWaitBlocks = 3; - - // Try to trick Unique using full UNQ identification - await usingAstarPlaygrounds(astarUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId); - - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - let accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - - // Try to trick Unique using shortened UNQ identification - await usingAstarPlaygrounds(astarUrl, async (helper) => { - await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId); - - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash - && event.outcome.isUntrustedReserveLocation); - - accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - }); -});