difftreelog
feat(ci) xcm tests for hydraDx
in: master
14 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
@@ -25,8 +25,11 @@
[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]
},
}
.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
.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,105 @@
}
}
+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,
+ );
+
+ // const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;
+ // await this.helper.collective.council.vote(aliceAccount, motionHash, councilProposalIdx, true);
+
+
+ // await this.helper.collective.council.close(
+ // aliceAccount,
+ // motionHash,
+ // councilProposalIdx,
+ // {
+ // refTime: 1_000_000_000,
+ // proofSize: 1_000_000,
+ // },
+ // 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, usingHydraDxPlaygrounds, 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);13export const HYDRADX_CHAIN = +(process.env.RELAY_HYDRADX_ID || 2034);1415export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);16export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);17export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);18export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);19export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);2021export const relayUrl = config.relayUrl;22export const statemintUrl = config.statemintUrl;23export const statemineUrl = config.statemineUrl;2425export const acalaUrl = config.acalaUrl;26export const moonbeamUrl = config.moonbeamUrl;27export const astarUrl = config.astarUrl;28export const polkadexUrl = config.polkadexUrl;29export const hydraDxUrl = config.hydraDxUrl;3031export const karuraUrl = config.karuraUrl;32export const moonriverUrl = config.moonriverUrl;33export const shidenUrl = config.shidenUrl;3435export const SAFE_XCM_VERSION = 3;363738export const RELAY_DECIMALS = 12;39export const STATEMINE_DECIMALS = 12;40export const KARURA_DECIMALS = 12;41export const SHIDEN_DECIMALS = 18n;42export const QTZ_DECIMALS = 18n;4344export const ASTAR_DECIMALS = 18n;45export const UNQ_DECIMALS = 18n;4647export const maxWaitBlocks = 6;4849export const uniqueMultilocation = {50 parents: 1,51 interior: {52 X1: {53 Parachain: UNIQUE_CHAIN,54 },55 },56};57export const uniqueVersionedMultilocation = {58 V3: uniqueMultilocation,59};6061export const uniqueAssetId = {62 Concrete: uniqueMultilocation,63};6465export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {66 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash67 && event.outcome.isFailedToTransactAsset);68};69export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {70 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash71 && event.outcome.isUntrustedReserveLocation);72};7374export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => {75 // The correct messageHash for downward messages can't be reliably obtained76 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission);77};7879export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => {80 // The correct messageHash for downward messages can't be reliably obtained81 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete);82};8384export const NETWORKS = {85 acala: usingAcalaPlaygrounds,86 astar: usingAstarPlaygrounds,87 polkadex: usingPolkadexPlaygrounds,88 moonbeam: usingMoonbeamPlaygrounds,89 moonriver: usingMoonriverPlaygrounds,90 karura: usingKaruraPlaygrounds,91 shiden: usingShidenPlaygrounds,92 hydraDx: usingHydraDxPlaygrounds,93} as const;94type NetworkNames = keyof typeof NETWORKS;9596type NativeRuntime = 'opal' | 'quartz' | 'unique';9798export function mapToChainId(networkName: keyof typeof NETWORKS): number {99 switch (networkName) {100 case 'acala':101 return ACALA_CHAIN;102 case 'astar':103 return ASTAR_CHAIN;104 case 'moonbeam':105 return MOONBEAM_CHAIN;106 case 'polkadex':107 return POLKADEX_CHAIN;108 case 'moonriver':109 return MOONRIVER_CHAIN;110 case 'karura':111 return KARURA_CHAIN;112 case 'shiden':113 return SHIDEN_CHAIN;114 case 'hydraDx':115 return HYDRADX_CHAIN;116 }117}118119export function mapToChainUrl(networkName: NetworkNames): string {120 switch (networkName) {121 case 'acala':122 return acalaUrl;123 case 'astar':124 return astarUrl;125 case 'moonbeam':126 return moonbeamUrl;127 case 'polkadex':128 return polkadexUrl;129 case 'moonriver':130 return moonriverUrl;131 case 'karura':132 return karuraUrl;133 case 'shiden':134 return shidenUrl;135 case 'hydraDx':136 return hydraDxUrl;137 }138}139140export function getDevPlayground(name: NetworkNames) {141 return NETWORKS[name];142}143144export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;145export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;146export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;147export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;148export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;149150export class XcmTestHelper {151 private _balanceUniqueTokenInit: bigint = 0n;152 private _balanceUniqueTokenMiddle: bigint = 0n;153 private _balanceUniqueTokenFinal: bigint = 0n;154 private _unqFees: bigint = 0n;155 private _nativeRuntime: NativeRuntime;156157 constructor(runtime: NativeRuntime) {158 this._nativeRuntime = runtime;159 }160161 private _getNativeId() {162 switch (this._nativeRuntime) {163 case 'opal':164 // To-Do165 return 1001;166 case 'quartz':167 return QUARTZ_CHAIN;168 case 'unique':169 return UNIQUE_CHAIN;170 }171 }172173 private _isAddress20FormatFor(network: NetworkNames) {174 switch (network) {175 case 'moonbeam':176 case 'moonriver':177 return true;178 default:179 return false;180 }181 }182183 private _runtimeVersionedMultilocation() {184 return {185 V3: {186 parents: 1,187 interior: {188 X1: {189 Parachain: this._getNativeId(),190 },191 },192 },193 };194 }195196 private _uniqueChainMultilocationForRelay() {197 return {198 V3: {199 parents: 0,200 interior: {201 X1: {Parachain: this._getNativeId()},202 },203 },204 };205 }206207 async sendUnqTo(208 networkName: keyof typeof NETWORKS,209 randomAccount: IKeyringPair,210 randomAccountOnTargetChain = randomAccount,211 ) {212 const networkUrl = mapToChainUrl(networkName);213 const targetPlayground = getDevPlayground(networkName);214 await usingPlaygrounds(async (helper) => {215 this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);216 const destination = {217 V2: {218 parents: 1,219 interior: {220 X1: {221 Parachain: mapToChainId(networkName),222 },223 },224 },225 };226227 const beneficiary = {228 V2: {229 parents: 0,230 interior: {231 X1: (232 this._isAddress20FormatFor(networkName) ?233 {234 AccountKey20: {235 network: 'Any',236 key: randomAccountOnTargetChain.address,237 },238 }239 :240 {241 AccountId32: {242 network: 'Any',243 id: randomAccountOnTargetChain.addressRaw,244 },245 }246 ),247 },248 },249 };250251 const assets = {252 V2: [253 {254 id: {255 Concrete: {256 parents: 0,257 interior: 'Here',258 },259 },260 fun: {261 Fungible: TRANSFER_AMOUNT,262 },263 },264 ],265 };266 const feeAssetItem = 0;267268 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');269 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);270 this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);271272 this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;273 console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees));274 expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true;275276 await targetPlayground(networkUrl, async (helper) => {277 /*278 Since only the parachain part of the Polkadex279 infrastructure is launched (without their280 solochain validators), processing incoming281 assets will lead to an error.282 This error indicates that the Polkadex chain283 received a message from the Unique network,284 since the hash is being checked to ensure285 it matches what was sent.286 */287 if(networkName == 'polkadex' || networkName =='hydraDx') {288 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);289 } else {290 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);291 }292 });293294 });295 }296297 async sendUnqBack(298 networkName: keyof typeof NETWORKS,299 sudoer: IKeyringPair,300 randomAccountOnUnq: IKeyringPair,301 ) {302 const networkUrl = mapToChainUrl(networkName);303304 const targetPlayground = getDevPlayground(networkName);305 await usingPlaygrounds(async (helper) => {306307 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(308 randomAccountOnUnq.addressRaw,309 {310 Concrete: {311 parents: 1,312 interior: {313 X1: {Parachain: this._getNativeId()},314 },315 },316 },317 SENDBACK_AMOUNT,318 );319320 let xcmProgramSent: any;321322323 await targetPlayground(networkUrl, async (helper) => {324 if('getSudo' in helper) {325 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), xcmProgram);326 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);327 } else if('fastDemocracy' in helper) {328 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), xcmProgram]);329 // Needed to bypass the call filter.330 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);331 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);332 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);333 }334 });335336 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);337338 this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);339340 expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);341342 });343 }344345 async sendOnlyOwnedBalance(346 networkName: keyof typeof NETWORKS,347 sudoer: IKeyringPair,348 ) {349 const networkUrl = mapToChainUrl(networkName);350 const targetPlayground = getDevPlayground(networkName);351352 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);353354 await usingPlaygrounds(async (helper) => {355 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));356 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);357 const moreThanTargetChainHas = 2n * targetChainBalance;358359 const targetAccount = helper.arrange.createEmptyAccount();360361 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(362 targetAccount.addressRaw,363 {364 Concrete: {365 parents: 0,366 interior: 'Here',367 },368 },369 moreThanTargetChainHas,370 );371372 let maliciousXcmProgramSent: any;373374375 await targetPlayground(networkUrl, async (helper) => {376 if('getSudo' in helper) {377 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgram);378 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);379 } else if('fastDemocracy' in helper) {380 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgram]);381 // Needed to bypass the call filter.382 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);383 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);384 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);385 }386 });387388 await expectFailedToTransact(helper, maliciousXcmProgramSent);389390 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);391 expect(targetAccountBalance).to.be.equal(0n);392 });393 }394395 async rejectReserveTransferUNQfrom(networkName: keyof typeof NETWORKS, sudoer: IKeyringPair) {396 const networkUrl = mapToChainUrl(networkName);397 const targetPlayground = getDevPlayground(networkName);398399 await usingPlaygrounds(async (helper) => {400 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);401 const targetAccount = helper.arrange.createEmptyAccount();402403 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(404 targetAccount.addressRaw,405 {406 Concrete: {407 parents: 1,408 interior: {409 X1: {410 Parachain: this._getNativeId(),411 },412 },413 },414 },415 testAmount,416 );417418 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(419 targetAccount.addressRaw,420 {421 Concrete: {422 parents: 0,423 interior: 'Here',424 },425 },426 testAmount,427 );428429 let maliciousXcmProgramFullIdSent: any;430 let maliciousXcmProgramHereIdSent: any;431 const maxWaitBlocks = 3;432433 // Try to trick Unique using full UNQ identification434 await targetPlayground(networkUrl, async (helper) => {435 if('getSudo' in helper) {436 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);437 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);438 }439 // Moonbeam case440 else if('fastDemocracy' in helper) {441 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);442 // Needed to bypass the call filter.443 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);444 await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using path asset identification`,batchCall);445446 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);447 }448 });449450451 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);452453 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);454 expect(accountBalance).to.be.equal(0n);455456 // Try to trick Unique using shortened UNQ identification457 await targetPlayground(networkUrl, async (helper) => {458 if('getSudo' in helper) {459 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId);460 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);461 }462 else if('fastDemocracy' in helper) {463 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]);464 // Needed to bypass the call filter.465 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);466 await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);467468 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);469 }470 });471472 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);473474 accountBalance = await helper.balance.getSubstrate(targetAccount.address);475 expect(accountBalance).to.be.equal(0n);476 });477 }478479 async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {480 const networkUrl = mapToChainUrl(networkName);481 const targetPlayground = getDevPlayground(networkName);482 let messageSent: any;483484 await usingPlaygrounds(async (helper) => {485 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(486 helper.arrange.createEmptyAccount().addressRaw,487 {488 Concrete: {489 parents: 1,490 interior: {491 X1: {492 Parachain: mapToChainId(networkName),493 },494 },495 },496 },497 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,498 );499 await targetPlayground(networkUrl, async (helper) => {500 if('getSudo' in helper) {501 await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);502 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);503 } else if('fastDemocracy' in helper) {504 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);505 // Needed to bypass the call filter.506 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);507 await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);508509 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);510 }511 });512 await expectFailedToTransact(helper, messageSent);513 });514 }515516 async registerRelayNativeTokenOnUnique(alice: IKeyringPair) {517 return await usingPlaygrounds(async (helper) => {518 const relayLocation = {519 parents: 1,520 interior: 'Here',521 };522 const relayAssetId = {Concrete: relayLocation};523524 const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayAssetId);525 if(relayCollectionId == null) {526 const name = 'Relay Tokens';527 const tokenPrefix = 'xDOT';528 const decimals = 10;529 await helper.getSudo().foreignAssets.register(alice, relayAssetId, name, tokenPrefix, {Fungible: decimals});530531 return await helper.foreignAssets.foreignCollectionId(relayAssetId);532 } else {533 console.log('Relay foreign collection is already registered');534 return relayCollectionId;535 }536 });537 }538}