difftreelog
Merge pull request #1046 from UniqueNetwork/ci/hydra-xcm-integration
in: master
hydra xcm integration
16 files changed
.baedeker/rewrites.example.jsonnetdiffbeforeafterboth--- a/.baedeker/rewrites.example.jsonnet
+++ b/.baedeker/rewrites.example.jsonnet
@@ -1,17 +1,18 @@
local dotenv = {
- [std.splitLimit(line, "=", 2)[0]]: std.splitLimit(line, "=", 2)[1]
- for line in std.split(importstr "../.env", "\n")
- if line != ""
- if std.member(line, "=")
+ [std.splitLimit(line, '=', 2)[0]]: std.splitLimit(line, '=', 2)[1]
+ for line in std.split(importstr '../.env', '\n')
+ if line != ''
+ if std.member(line, '=')
};
function(prev, repoDir)
-(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({
- 'bin/unique':'%s/target/release/unique-collator' % repoDir,
- 'bin/polkadot':{dockerImage:'uniquenetwork/builder-polkadot:%s' % dotenv.POLKADOT_MAINNET_BRANCH},
- 'bin/acala':{dockerImage:'uniquenetwork/builder-acala:%s' % dotenv.ACALA_BUILD_BRANCH},
- 'bin/moonbeam':{dockerImage:'uniquenetwork/builder-moonbeam:%s' % dotenv.MOONBEAM_BUILD_BRANCH},
- 'bin/cumulus':{dockerImage:'uniquenetwork/builder-cumulus:%s' % dotenv.STATEMINE_BUILD_BRANCH},
- 'bin/astar':{dockerImage:'uniquenetwork/builder-astar:%s' % dotenv.ASTAR_BUILD_BRANCH},
- 'bin/polkadex':{dockerImage:'uniquenetwork/builder-polkadex:%s' % dotenv.POLKADEX_BUILD_BRANCH},
-})(prev)
+ (import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({
+ 'bin/unique': '%s/target/release/unique-collator' % repoDir,
+ 'bin/polkadot': { dockerImage: 'uniquenetwork/builder-polkadot:%s' % dotenv.POLKADOT_MAINNET_BRANCH },
+ 'bin/acala': { dockerImage: 'uniquenetwork/builder-acala:%s' % dotenv.ACALA_BUILD_BRANCH },
+ 'bin/moonbeam': { dockerImage: 'uniquenetwork/builder-moonbeam:%s' % dotenv.MOONBEAM_BUILD_BRANCH },
+ 'bin/cumulus': { dockerImage: 'uniquenetwork/builder-cumulus:%s' % dotenv.STATEMINE_BUILD_BRANCH },
+ 'bin/astar': { dockerImage: 'uniquenetwork/builder-astar:%s' % dotenv.ASTAR_BUILD_BRANCH },
+ 'bin/polkadex': { dockerImage: 'uniquenetwork/builder-polkadex:%s' % dotenv.POLKADEX_BUILD_BRANCH },
+ 'bin/hydradx': { dockerImage: 'uniquenetwork/builder-hydraDx:%s' % dotenv.HYDRADX_BUILD_BRANCH },
+ })(prev)
.baedeker/xcm-opal.jsonnetdiffbeforeafterboth--- a/.baedeker/xcm-opal.jsonnet
+++ b/.baedeker/xcm-opal.jsonnet
@@ -19,6 +19,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'relay',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob', 'charlie', 'dave', 'eve']
},
@@ -35,6 +38,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
@@ -52,6 +58,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
.baedeker/xcm-quartz.jsonnetdiffbeforeafterboth--- a/.baedeker/xcm-quartz.jsonnet
+++ b/.baedeker/xcm-quartz.jsonnet
@@ -24,6 +24,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'relay',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob', 'charlie', 'dave', 'eve', 'ferdie']
},
@@ -40,6 +43,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
@@ -60,6 +66,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
@@ -79,6 +88,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para-nimbus',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alith', 'baltathar']
},
@@ -96,6 +108,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
@@ -113,6 +128,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
.baedeker/xcm-unique.jsonnetdiffbeforeafterboth--- a/.baedeker/xcm-unique.jsonnet
+++ b/.baedeker/xcm-unique.jsonnet
@@ -11,22 +11,25 @@
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 [
+ // [[$.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',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
- for name in ['alice', 'bob', 'charlie', 'dave', 'eve', 'ferdie', 'gregory']
+ for name in ['alice', 'bob', 'charlie', 'dave', 'eve', 'ferdie', 'gregory', 'holly']
},
};
@@ -41,6 +44,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
@@ -61,6 +67,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
@@ -80,6 +89,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para-nimbus',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alith', 'baltathar']
},
@@ -97,6 +109,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para-ed',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
@@ -114,6 +129,9 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
@@ -131,14 +149,39 @@
[name]: {
bin: $.bin,
wantedKeys: 'para',
+ extraArgs: [
+ "-lxcm=trace"
+ ],
+ },
+ for name in ['alice', 'bob']
+ },
+};
+
+local hydraDx = {
+ name: 'hydraDx',
+ bin: 'bin/hydradx',
+ paraId: 1007,
+ spec: {Genesis:{
+ chain: 'local',
+ modify:: m.genericPara($),
+ }},
+ nodes: {
+ [name]: {
+ bin: $.bin,
+ wantedKeys: 'para',
+ legacyRpc: true,
+ extraArgs: [
+ "-lxcm=trace"
+ ],
},
for name in ['alice', 'bob']
},
};
+
relay + {
parachains: {
[para.name]: para,
- for para in [unique, acala, moonbeam, statemint, astar, polkadex]
+ for para in [unique, acala, moonbeam, statemint, astar, polkadex, hydraDx]
},
}
.docker/Dockerfile-hydradx.j2diffbeforeafterboth--- /dev/null
+++ b/.docker/Dockerfile-hydradx.j2
@@ -0,0 +1,46 @@
+# ===== Rust builder =====
+FROM ubuntu:22.04 as rust-builder
+LABEL maintainer="Unique.Network"
+
+ENV CARGO_HOME="/cargo-home"
+ENV PATH="/cargo-home/bin:$PATH"
+ENV TZ=UTC
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN apt-get update && \
+ apt-get install -y curl cmake pkg-config libssl-dev git clang llvm libudev-dev protobuf-compiler && \
+ apt-get clean && \
+ rm -r /var/lib/apt/lists/*
+
+RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none
+
+RUN rustup toolchain install {{ RUST_TOOLCHAIN }} && \
+ rustup target add wasm32-unknown-unknown --toolchain {{ RUST_TOOLCHAIN }} && \
+ rustup default {{ RUST_TOOLCHAIN }} && \
+ rustup target list --installed && \
+ rustup show
+
+RUN mkdir /unique_parachain
+WORKDIR /unique_parachain
+
+# ===== BUILD ACALA =====
+FROM rust-builder as builder-hydradx-bin
+
+WORKDIR /unique_parachain
+
+RUN git clone --depth 1 -b {{ HYDRADX_BUILD_BRANCH }} https://github.com/galacticcouncil/HydraDX-node.git
+
+RUN --mount=type=cache,target=/cargo-home/registry \
+ --mount=type=cache,target=/cargo-home/git \
+ --mount=type=cache,target=/unique_parachain/parachain/target \
+ cd HydraDX-node && \
+ CARGO_INCREMENTAL=0 cargo build --release --locked && \
+ mkdir /unique_parachain/Hydradx && \
+ mv ./target/release/hydradx /unique_parachain/Hydradx/hydradx
+
+# ===== BIN ======
+
+FROM ubuntu:22.04 as builder-hydradx
+
+COPY --from=builder-hydradx-bin /unique_parachain/Hydradx/hydradx /bin/hydradx
+ENTRYPOINT ["/bin/hydradx"]
.envdiffbeforeafterboth--- a/.env
+++ b/.env
@@ -12,7 +12,7 @@
UNIQUE_MAINNET_BRANCH=release-v10010063
UNIQUE_REPLICA_FROM=wss://ws.unique.network:443
POLKADEX_BUILD_BRANCH=v1.1.0
-
+HYDRADX_BUILD_BRANCH=v20.1.1
KUSAMA_MAINNET_BRANCH=release-v1.0.0
STATEMINE_BUILD_BRANCH=release-parachains-v9430
.github/workflows/xcm.ymldiffbeforeafterboth--- a/.github/workflows/xcm.yml
+++ b/.github/workflows/xcm.yml
@@ -37,9 +37,9 @@
id: create_matrix
with:
matrix: |
- network {opal}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.WESTMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmOpal}, runtime_features {opal-runtime}
- network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmQuartz}, runtime_features {quartz-runtime}
- network {unique}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime}
+ network {opal}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.WESTMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, hydradx_version {${{ env.HYDRADX_BUILD_BRANCH }}}, runtest {testXcmOpal}, runtime_features {opal-runtime}
+ network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, hydradx_version {${{ env.HYDRADX_BUILD_BRANCH }}}, runtest {testFullXcmQuartz}, runtime_features {quartz-runtime}
+ network {unique}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, hydradx_version {${{ env.HYDRADX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime}
xcm:
needs: prepare-execution-marix
@@ -280,6 +280,40 @@
fi
shell: bash
+ # Check Hydradx version and build it if it doesn't exist in repository
+ - name: Generate ENV related extend Dockerfile file for Hydradx
+ uses: cuchi/jinja2-action@v1.2.0
+ with:
+ template: .docker/Dockerfile-hydradx.j2
+ output_file: .docker/Dockerfile-hydradx.${{ matrix.hydradx_version }}.yml
+ variables: |
+ RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+ HYDRADX_BUILD_BRANCH=${{ matrix.hydradx_version }}
+
+ - name: Check if the dockerhub repository contains the needed HYDRADX version
+ run: |
+ # aquire token
+ TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${{ secrets.CORE_DOCKERHUB_USERNAME }}'", "password": "'${{ secrets.CORE_DOCKERHUB_TOKEN }}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token)
+ export TOKEN=$TOKEN
+
+ # Get TAGS from DOCKERHUB repository
+ HYDRADX_TAGS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/uniquenetwork/builder-hydradx/tags/?page_size=100 | jq -r '."results"[]["name"]')
+ # Show TAGS
+ echo "HYDRADX TAGS:"
+ echo $HYDRADX_TAGS
+ # Check correct version HYDRADX and build it if it doesn't exist in HYDRADX TAGS
+ if [[ ${HYDRADX_TAGS[*]} =~ (^|[[:space:]])"${{ matrix.hydradx_version }}"($|[[:space:]]) ]]; then
+ echo "Repository has needed HYDRADX version";
+ docker pull uniquenetwork/builder-hydradx:${{ matrix.hydradx_version }}
+ else
+ echo "Repository has not needed HYDRADX version, so build it";
+ cd .docker/ && docker build --file ./Dockerfile-hydradx.${{ matrix.hydradx_version }}.yml --tag uniquenetwork/builder-hydradx:${{ matrix.hydradx_version }} .
+ echo "Push needed HYDRADX version to the repository";
+ docker push uniquenetwork/builder-hydradx:${{ matrix.hydradx_version }}
+ fi
+ shell: bash
+
+
- name: Build unique-chain
run: |
docker build --file .docker/Dockerfile-unique \
@@ -318,6 +352,7 @@
snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/cumulus':{dockerImage:'uniquenetwork/builder-cumulus:${{ matrix.cumulus_version }}'}})
snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/astar':{dockerImage:'uniquenetwork/builder-astar:${{ matrix.astar_version }}'}})
snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/polkadex':{dockerImage:'uniquenetwork/builder-polkadex:${{ matrix.polkadex_version }}'}})
+ snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/hydradx':{dockerImage:'uniquenetwork/builder-hydradx:${{ matrix.hydradx_version }}'}})
- name: Upload network config
uses: actions/upload-artifact@v3
.vscode/settings.jsondiffbeforeafterboth--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -10,5 +10,5 @@
"SKIP_WASM_BUILD": "1"
},
"rust-analyzer.cargo.features": ["runtime-benchmarks", "try-runtime"],
- "solidity.packageDefaultDependenciesDirectory": "tests/node_modules"
+ "solidity.packageDefaultDependenciesDirectory": "js-packages/node_modules"
}
js-packages/.vscode/settings.jsondiffbeforeafterboth--- a/js-packages/.vscode/settings.json
+++ b/js-packages/.vscode/settings.json
@@ -3,7 +3,7 @@
"RUN_GOV_TESTS": "1",
"RUN_XCM_TESTS": "1"
},
- "mochaExplorer.files": "src/**/*.test.ts",
+ "mochaExplorer.files": "tests/**/*.test.ts",
"mochaExplorer.require": "ts-node/register",
"eslint.format.enable": true,
"[javascript]": {
js-packages/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/js-packages/playgrounds/unique.dev.ts
+++ b/js-packages/playgrounds/unique.dev.ts
@@ -17,7 +17,7 @@
import type {FrameSystemEventRecord, StagingXcmV2TraitsError, StagingXcmV3TraitsOutcome} 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} from './unique.xcm.js';
+import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup, HydraDxHelper} from './unique.xcm.js';
import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance.js';
import type {ICollectiveGroup, IFellowshipGroup} from './unique.governance.js';
@@ -667,6 +667,20 @@
}
}
+export class DevHydraDxHelper extends HydraDxHelper {
+ wait: WaitGroup;
+ fastDemocracy: HydraFastDemocracyGroup;
+
+ constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+ options.helperBase = options.helperBase ?? DevHydraDxHelper;
+
+ super(logger, options);
+
+ this.wait = new WaitGroup(this);
+ this.fastDemocracy = new HydraFastDemocracyGroup(this);
+ }
+}
+
export class DevKaruraHelper extends DevAcalaHelper {}
export class ArrangeGroup {
@@ -1150,6 +1164,91 @@
}
}
+class HydraFastDemocracyGroup {
+ helper: DevHydraDxHelper;
+
+ constructor(helper: DevHydraDxHelper) {
+ this.helper = helper;
+ }
+
+ async executeProposal(proposalDesciption: string, encodedProposal: string) {
+ const proposalHash = blake2AsHex(encodedProposal);
+ const aliceAccount = this.helper.util.fromSeed('//Alice');
+ const bobAccount = this.helper.util.fromSeed('//Bob');
+ const eveAccount = this.helper.util.fromSeed('//Eve');
+
+ const councilVotingThreshold = 1;
+ const technicalCommitteeThreshold = 3;
+ const fastTrackVotingPeriod = 3;
+ const fastTrackDelayPeriod = 0;
+
+ console.log(`[democracy] executing '${proposalDesciption}' proposal`);
+
+ // >>> Propose external motion through council >>>
+ console.log('\t* Propose external motion through council.......');
+ const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});
+ const encodedMotion = externalMotion?.method.toHex() || '';
+ const motionHash = blake2AsHex(encodedMotion);
+ console.log('\t* Motion hash is %s', motionHash);
+
+ await this.helper.collective.council.propose(
+ aliceAccount,
+ councilVotingThreshold,
+ externalMotion,
+ externalMotion.encodedLength,
+ );
+
+ console.log('\t* Propose external motion through council.......DONE');
+ // <<< Propose external motion through council <<<
+
+ // >>> Fast track proposal through technical committee >>>
+ console.log('\t* Fast track proposal through technical committee.......');
+ const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);
+ const encodedFastTrack = fastTrack?.method.toHex() || '';
+ const fastTrackHash = blake2AsHex(encodedFastTrack);
+ console.log('\t* FastTrack hash is %s', fastTrackHash);
+
+ await this.helper.collective.techCommittee.propose(aliceAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
+
+ const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;
+ await this.helper.collective.techCommittee.vote(aliceAccount, fastTrackHash, techProposalIdx, true);
+ await this.helper.collective.techCommittee.vote(bobAccount, fastTrackHash, techProposalIdx, true);
+ await this.helper.collective.techCommittee.vote(eveAccount, fastTrackHash, techProposalIdx, true);
+
+ await this.helper.collective.techCommittee.close(
+ bobAccount,
+ fastTrackHash,
+ techProposalIdx,
+ {
+ refTime: 1_000_000_000,
+ proofSize: 1_000_000,
+ },
+ fastTrack.encodedLength,
+ );
+ console.log('\t* Fast track proposal through technical committee.......DONE');
+ // <<< Fast track proposal through technical committee <<<
+
+ const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);
+ const referendumIndex = democracyStarted.referendumIndex;
+
+ // >>> Referendum voting >>>
+ console.log(`\t* Referendum #${referendumIndex} voting.......`);
+ await this.helper.democracy.referendumVote(eveAccount, referendumIndex, {
+ balance: 10_000_000_000_000_000_000n,
+ vote: {aye: true, conviction: 1},
+ });
+ console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);
+ // <<< Referendum voting <<<
+
+ // Wait the proposal to pass
+ await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);
+
+ await this.helper.wait.newBlocks(1);
+
+ console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);
+ }
+}
+
class WaitGroup {
helper: ChainHelperBase;
js-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -71,6 +71,31 @@
}
}
+class DemocracyGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ notePreimagePallet: string;
+
+ constructor(helper: T, options: { [key: string]: any } = {}) {
+ super(helper);
+ this.notePreimagePallet = options.notePreimagePallet;
+ }
+
+ async notePreimage(signer: TSigner, encodedProposal: string) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true);
+ }
+
+ externalProposeMajority(proposal: any) {
+ return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]);
+ }
+
+ fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {
+ return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
+ }
+
+ async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);
+ }
+}
+
class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {
collective: string;
@@ -97,6 +122,31 @@
}
}
+class CollectiveGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ collective: string;
+
+ constructor(helper: T, palletName: string) {
+ super(helper);
+
+ this.collective = palletName;
+ }
+
+ async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);
+ }
+
+ async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);
+ }
+
+ async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);
+ }
+
+ async proposalCount() {
+ return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));
+ }
+}
class PolkadexXcmHelperGroup<T extends ChainHelperBase> extends HelperGroup<T> {
async whitelistToken(signer: TSigner, assetId: any) {
await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);
@@ -372,3 +422,28 @@
}
}
+export class HydraDxHelper extends XcmChainHelper {
+ balance: SubstrateBalanceGroup<HydraDxHelper>;
+ xcm: XcmGroup<HydraDxHelper>;
+ xTokens: XTokensGroup<HydraDxHelper>;
+ democracy: DemocracyGroup<HydraDxHelper>;
+ collective: {
+ council: CollectiveGroup<HydraDxHelper>,
+ techCommittee: CollectiveGroup<HydraDxHelper>,
+ };
+
+ constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+ super(logger, options.helperBase ?? HydraDxHelper);
+ options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';
+
+ this.balance = new SubstrateBalanceGroup(this);
+ this.xcm = new XcmGroup(this, 'polkadotXcm');
+ this.xTokens = new XTokensGroup(this);
+ this.democracy = new DemocracyGroup(this, options);
+ this.collective = {
+ council: new CollectiveGroup(this, 'council'),
+ techCommittee: new CollectiveGroup(this, 'technicalCommittee'),
+ };
+ }
+}
+
js-packages/tests/config.tsdiffbeforeafterboth--- a/js-packages/tests/config.ts
+++ b/js-packages/tests/config.ts
@@ -29,6 +29,7 @@
statemineUrl: process.env.RELAY_STATEMINE_URL || 'ws://127.0.0.1:9948',
statemintUrl: process.env.RELAY_STATEMINT_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',
};
export default config;
js-packages/tests/util/createHrmp.tsdiffbeforeafterboth--- a/js-packages/tests/util/createHrmp.ts
+++ b/js-packages/tests/util/createHrmp.ts
@@ -27,6 +27,7 @@
await bidirOpen(1001, 1004);
await bidirOpen(1001, 1005);
await bidirOpen(1001, 1006);
+ await bidirOpen(1001, 1007);
break;
default:
throw new Error(`unknown hrmp config profile: ${profile}`);
js-packages/tests/util/index.tsdiffbeforeafterboth--- a/js-packages/tests/util/index.ts
+++ b/js-packages/tests/util/index.ts
@@ -11,7 +11,7 @@
import config from '../config.js';
import {ChainHelperBase} from '@unique/playgrounds/unique.js';
import type {ILogger} from '@unique/playgrounds/types.js';
-import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper} from '@unique/playgrounds/unique.dev.js';
+import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper, DevHydraDxHelper} from '@unique/playgrounds/unique.dev.js';
import {dirname} from 'path';
import {fileURLToPath} from 'url';
@@ -91,6 +91,8 @@
export const usingPolkadexPlaygrounds = (url: string, code: (helper: DevPolkadexHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevPolkadexHelper>(DevPolkadexHelper, url, code);
+export const usingHydraDxPlaygrounds = (url: string, code: (helper: DevHydraDxHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevHydraDxHelper>(DevHydraDxHelper, url, code);
+
export const MINIMUM_DONOR_FUND = 4_000_000n;
export const DONOR_FUNDING = 4_000_000n;
js-packages/tests/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth--- a/js-packages/tests/xcm/lowLevelXcmUnique.test.ts
+++ b/js-packages/tests/xcm/lowLevelXcmUnique.test.ts
@@ -16,10 +16,10 @@
import type {IKeyringPair} from '@polkadot/types/types';
import config from '../config.js';
-import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util/index.js';
+import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingHydraDxPlaygrounds} from '../util/index.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, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types.js';
+import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, hydraDxUrl, moonbeamUrl, polkadexUrl, uniqueAssetId} from './xcm.types.js';
const testHelper = new XcmTestHelper('unique');
@@ -364,3 +364,40 @@
await testHelper.rejectReserveTransferUNQfrom('astar', alice);
});
});
+
+describeXCM('[XCMLL] Integration test: Exchanging tokens with HydraDx', () => {
+ let alice: IKeyringPair;
+ let randomAccount: IKeyringPair;
+
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ alice = await privateKey('//Alice');
+ console.log(config.acalaUrl);
+ randomAccount = helper.arrange.createEmptyAccount();
+
+ // 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, randomAccount.address, SENDER_BUDGET);
+ });
+
+ await usingHydraDxPlaygrounds(hydraDxUrl, async (helper) => {
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
+ });
+ });
+
+ itSub('Should connect and send UNQ to HydraDx', async () => {
+ await testHelper.sendUnqTo('hydraDx', randomAccount);
+ });
+
+ itSub('Should connect to HydraDx and send UNQ back', async () => {
+ await testHelper.sendUnqBack('hydraDx', alice, randomAccount);
+ });
+
+ itSub('HydraDx can send only up to its balance', async () => {
+ await testHelper.sendOnlyOwnedBalance('hydraDx', alice);
+ });
+
+ itSub('Should not accept reserve transfer of UNQ from HydraDx', async () => {
+ await testHelper.rejectReserveTransferUNQfrom('hydraDx', alice);
+ });
+});
\ No newline at end of file
js-packages/tests/xcm/xcm.types.tsdiffbeforeafterboth1import type {IKeyringPair} from '@polkadot/types/types';2import {hexToString} from '@polkadot/util';3import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util/index.js';4import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';5import config from '../config.js';67export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);8export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);9export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);10export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);11export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);12export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);1314export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);15export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);16export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);17export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);18export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);1920export const relayUrl = config.relayUrl;21export const statemintUrl = config.statemintUrl;22export const statemineUrl = config.statemineUrl;2324export const acalaUrl = config.acalaUrl;25export const moonbeamUrl = config.moonbeamUrl;26export const astarUrl = config.astarUrl;27export const polkadexUrl = config.polkadexUrl;2829export const karuraUrl = config.karuraUrl;30export const moonriverUrl = config.moonriverUrl;31export const shidenUrl = config.shidenUrl;3233export const SAFE_XCM_VERSION = 3;343536export const RELAY_DECIMALS = 12;37export const STATEMINE_DECIMALS = 12;38export const KARURA_DECIMALS = 12;39export const SHIDEN_DECIMALS = 18n;40export const QTZ_DECIMALS = 18n;4142export const ASTAR_DECIMALS = 18n;43export const UNQ_DECIMALS = 18n;4445export const maxWaitBlocks = 6;4647export const uniqueMultilocation = {48 parents: 1,49 interior: {50 X1: {51 Parachain: UNIQUE_CHAIN,52 },53 },54};55export const uniqueVersionedMultilocation = {56 V3: uniqueMultilocation,57};5859export const uniqueAssetId = {60 Concrete: uniqueMultilocation,61};6263export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {64 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash65 && event.outcome.isFailedToTransactAsset);66};67export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {68 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash69 && event.outcome.isUntrustedReserveLocation);70};7172export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => {73 // The correct messageHash for downward messages can't be reliably obtained74 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission);75};7677export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => {78 // The correct messageHash for downward messages can't be reliably obtained79 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete);80};8182export const NETWORKS = {83 acala: usingAcalaPlaygrounds,84 astar: usingAstarPlaygrounds,85 polkadex: usingPolkadexPlaygrounds,86 moonbeam: usingMoonbeamPlaygrounds,87 moonriver: usingMoonriverPlaygrounds,88 karura: usingKaruraPlaygrounds,89 shiden: usingShidenPlaygrounds,90} as const;91type NetworkNames = keyof typeof NETWORKS;9293type NativeRuntime = 'opal' | 'quartz' | 'unique';9495export function mapToChainId(networkName: keyof typeof NETWORKS): number {96 switch (networkName) {97 case 'acala':98 return ACALA_CHAIN;99 case 'astar':100 return ASTAR_CHAIN;101 case 'moonbeam':102 return MOONBEAM_CHAIN;103 case 'polkadex':104 return POLKADEX_CHAIN;105 case 'moonriver':106 return MOONRIVER_CHAIN;107 case 'karura':108 return KARURA_CHAIN;109 case 'shiden':110 return SHIDEN_CHAIN;111 }112}113114export function mapToChainUrl(networkName: NetworkNames): string {115 switch (networkName) {116 case 'acala':117 return acalaUrl;118 case 'astar':119 return astarUrl;120 case 'moonbeam':121 return moonbeamUrl;122 case 'polkadex':123 return polkadexUrl;124 case 'moonriver':125 return moonriverUrl;126 case 'karura':127 return karuraUrl;128 case 'shiden':129 return shidenUrl;130 }131}132133export function getDevPlayground(name: NetworkNames) {134 return NETWORKS[name];135}136137export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;138export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;139export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;140export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;141export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;142143export class XcmTestHelper {144 private _balanceUniqueTokenInit: bigint = 0n;145 private _balanceUniqueTokenMiddle: bigint = 0n;146 private _balanceUniqueTokenFinal: bigint = 0n;147 private _unqFees: bigint = 0n;148 private _nativeRuntime: NativeRuntime;149150 constructor(runtime: NativeRuntime) {151 this._nativeRuntime = runtime;152 }153154 private _getNativeId() {155 switch (this._nativeRuntime) {156 case 'opal':157 // To-Do158 return 1001;159 case 'quartz':160 return QUARTZ_CHAIN;161 case 'unique':162 return UNIQUE_CHAIN;163 }164 }165166 private _isAddress20FormatFor(network: NetworkNames) {167 switch (network) {168 case 'moonbeam':169 case 'moonriver':170 return true;171 default:172 return false;173 }174 }175176 private _runtimeVersionedMultilocation() {177 return {178 V3: {179 parents: 1,180 interior: {181 X1: {182 Parachain: this._getNativeId(),183 },184 },185 },186 };187 }188189 private _uniqueChainMultilocationForRelay() {190 return {191 V3: {192 parents: 0,193 interior: {194 X1: {Parachain: this._getNativeId()},195 },196 },197 };198 }199200 async sendUnqTo(201 networkName: keyof typeof NETWORKS,202 randomAccount: IKeyringPair,203 randomAccountOnTargetChain = randomAccount,204 ) {205 const networkUrl = mapToChainUrl(networkName);206 const targetPlayground = getDevPlayground(networkName);207 await usingPlaygrounds(async (helper) => {208 this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);209 const destination = {210 V2: {211 parents: 1,212 interior: {213 X1: {214 Parachain: mapToChainId(networkName),215 },216 },217 },218 };219220 const beneficiary = {221 V2: {222 parents: 0,223 interior: {224 X1: (225 this._isAddress20FormatFor(networkName) ?226 {227 AccountKey20: {228 network: 'Any',229 key: randomAccountOnTargetChain.address,230 },231 }232 :233 {234 AccountId32: {235 network: 'Any',236 id: randomAccountOnTargetChain.addressRaw,237 },238 }239 ),240 },241 },242 };243244 const assets = {245 V2: [246 {247 id: {248 Concrete: {249 parents: 0,250 interior: 'Here',251 },252 },253 fun: {254 Fungible: TRANSFER_AMOUNT,255 },256 },257 ],258 };259 const feeAssetItem = 0;260261 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');262 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);263 this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);264265 this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;266 console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees));267 expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true;268269 await targetPlayground(networkUrl, async (helper) => {270 /*271 Since only the parachain part of the Polkadex272 infrastructure is launched (without their273 solochain validators), processing incoming274 assets will lead to an error.275 This error indicates that the Polkadex chain276 received a message from the Unique network,277 since the hash is being checked to ensure278 it matches what was sent.279 */280 if(networkName == 'polkadex') {281 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);282 } else {283 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);284 }285 });286287 });288 }289290 async sendUnqBack(291 networkName: keyof typeof NETWORKS,292 sudoer: IKeyringPair,293 randomAccountOnUnq: IKeyringPair,294 ) {295 const networkUrl = mapToChainUrl(networkName);296297 const targetPlayground = getDevPlayground(networkName);298 await usingPlaygrounds(async (helper) => {299300 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(301 randomAccountOnUnq.addressRaw,302 {303 Concrete: {304 parents: 1,305 interior: {306 X1: {Parachain: this._getNativeId()},307 },308 },309 },310 SENDBACK_AMOUNT,311 );312313 let xcmProgramSent: any;314315316 await targetPlayground(networkUrl, async (helper) => {317 if('getSudo' in helper) {318 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), xcmProgram);319 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);320 } else if('fastDemocracy' in helper) {321 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), xcmProgram]);322 // Needed to bypass the call filter.323 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);324 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);325 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);326 }327 });328329 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);330331 this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);332333 expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);334335 });336 }337338 async sendOnlyOwnedBalance(339 networkName: keyof typeof NETWORKS,340 sudoer: IKeyringPair,341 ) {342 const networkUrl = mapToChainUrl(networkName);343 const targetPlayground = getDevPlayground(networkName);344345 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);346347 await usingPlaygrounds(async (helper) => {348 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));349 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);350 const moreThanTargetChainHas = 2n * targetChainBalance;351352 const targetAccount = helper.arrange.createEmptyAccount();353354 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(355 targetAccount.addressRaw,356 {357 Concrete: {358 parents: 0,359 interior: 'Here',360 },361 },362 moreThanTargetChainHas,363 );364365 let maliciousXcmProgramSent: any;366367368 await targetPlayground(networkUrl, async (helper) => {369 if('getSudo' in helper) {370 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgram);371 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);372 } else if('fastDemocracy' in helper) {373 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgram]);374 // Needed to bypass the call filter.375 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);376 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);377 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);378 }379 });380381 await expectFailedToTransact(helper, maliciousXcmProgramSent);382383 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);384 expect(targetAccountBalance).to.be.equal(0n);385 });386 }387388 async rejectReserveTransferUNQfrom(networkName: keyof typeof NETWORKS, sudoer: IKeyringPair) {389 const networkUrl = mapToChainUrl(networkName);390 const targetPlayground = getDevPlayground(networkName);391392 await usingPlaygrounds(async (helper) => {393 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);394 const targetAccount = helper.arrange.createEmptyAccount();395396 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(397 targetAccount.addressRaw,398 {399 Concrete: {400 parents: 1,401 interior: {402 X1: {403 Parachain: this._getNativeId(),404 },405 },406 },407 },408 testAmount,409 );410411 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(412 targetAccount.addressRaw,413 {414 Concrete: {415 parents: 0,416 interior: 'Here',417 },418 },419 testAmount,420 );421422 let maliciousXcmProgramFullIdSent: any;423 let maliciousXcmProgramHereIdSent: any;424 const maxWaitBlocks = 3;425426 // Try to trick Unique using full UNQ identification427 await targetPlayground(networkUrl, async (helper) => {428 if('getSudo' in helper) {429 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);430 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);431 }432 // Moonbeam case433 else if('fastDemocracy' in helper) {434 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);435 // Needed to bypass the call filter.436 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);437 await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using path asset identification`,batchCall);438439 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);440 }441 });442443444 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);445446 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);447 expect(accountBalance).to.be.equal(0n);448449 // Try to trick Unique using shortened UNQ identification450 await targetPlayground(networkUrl, async (helper) => {451 if('getSudo' in helper) {452 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId);453 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);454 }455 else if('fastDemocracy' in helper) {456 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]);457 // Needed to bypass the call filter.458 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);459 await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);460461 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);462 }463 });464465 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);466467 accountBalance = await helper.balance.getSubstrate(targetAccount.address);468 expect(accountBalance).to.be.equal(0n);469 });470 }471472 async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {473 const networkUrl = mapToChainUrl(networkName);474 const targetPlayground = getDevPlayground(networkName);475 let messageSent: any;476477 await usingPlaygrounds(async (helper) => {478 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(479 helper.arrange.createEmptyAccount().addressRaw,480 {481 Concrete: {482 parents: 1,483 interior: {484 X1: {485 Parachain: mapToChainId(networkName),486 },487 },488 },489 },490 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,491 );492 await targetPlayground(networkUrl, async (helper) => {493 if('getSudo' in helper) {494 await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);495 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);496 } else if('fastDemocracy' in helper) {497 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);498 // Needed to bypass the call filter.499 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);500 await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);501502 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);503 }504 });505 await expectFailedToTransact(helper, messageSent);506 });507 }508509 async registerRelayNativeTokenOnUnique(alice: IKeyringPair) {510 return await usingPlaygrounds(async (helper) => {511 const relayLocation = {512 parents: 1,513 interior: 'Here',514 };515 const relayAssetId = {Concrete: relayLocation};516517 const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayAssetId);518 if(relayCollectionId == null) {519 const name = 'Relay Tokens';520 const tokenPrefix = 'xDOT';521 const decimals = 10;522 await helper.getSudo().foreignAssets.register(alice, relayAssetId, name, tokenPrefix, {Fungible: decimals});523524 return await helper.foreignAssets.foreignCollectionId(relayAssetId);525 } else {526 console.log('Relay foreign collection is already registered');527 return relayCollectionId;528 }529 });530 }531}