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.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import '@unique/opal-types/augment-api.js';5import '@unique/opal-types/augment-types.js';6import '@unique/opal-types/types-lookup.js';78import {stringToU8a} from '@polkadot/util';9import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';10import type {ChainHelperBaseConstructor, UniqueHelperConstructor} from './unique.js';11import {UniqueHelper, ChainHelperBase, HelperGroup} from './unique.js';12import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';13import * as defs from '@unique/opal-types/definitions.js';14import type {IKeyringPair} from '@polkadot/types/types';15import type {EventRecord} from '@polkadot/types/interfaces';16import type {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types.js';17import type {FrameSystemEventRecord, StagingXcmV2TraitsError, StagingXcmV3TraitsOutcome} from '@polkadot/types/lookup';18import type {SignerOptions, VoidFn} from '@polkadot/api/types';19import {spawnSync} from 'child_process';20import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup} from './unique.xcm.js';21import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance.js';22import type {ICollectiveGroup, IFellowshipGroup} from './unique.governance.js';2324export class SilentLogger {25 log(_msg: any, _level: any): void { }26 level = {27 ERROR: 'ERROR' as const,28 WARNING: 'WARNING' as const,29 INFO: 'INFO' as const,30 };31}3233export class SilentConsole {34 // TODO: Remove, this is temporary: Filter unneeded API output35 // (Jaco promised it will be removed in the next version)36 consoleErr: any;37 consoleLog: any;38 consoleWarn: any;3940 constructor() {41 this.consoleErr = console.error;42 this.consoleLog = console.log;43 this.consoleWarn = console.warn;44 }4546 enable() {47 const outFn = (printer: any) => (...args: any[]) => {48 for(const arg of args) {49 if(typeof arg !== 'string')50 continue;51 const skippedWarnings = ['1000:: Normal connection closure', 'Not decorating unknown runtime apis:', 'RPC methods not decorated:', 'Not decorating runtime apis', 'Bad input data provided to validate_transaction', 'account balance too low', '1006:: Abnormal Closure'];52 const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false);53 if(needToSkip || arg === 'Normal connection closure')54 return;55 }56 printer(...args);57 };5859 console.error = outFn(this.consoleErr.bind(console));60 console.log = outFn(this.consoleLog.bind(console));61 console.warn = outFn(this.consoleWarn.bind(console));62 }6364 disable() {65 console.error = this.consoleErr;66 console.log = this.consoleLog;67 console.warn = this.consoleWarn;68 }69}7071export interface IEventHelper {72 section(): string;7374 method(): string;7576 wrapEvent(data: any[]): any;77}7879// eslint-disable-next-line @typescript-eslint/naming-convention80function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {81 const helperClass = class implements IEventHelper {82 wrapEvent: (data: any[]) => any;83 _section: string;84 _method: string;8586 constructor() {87 this.wrapEvent = wrapEvent;88 this._section = section;89 this._method = method;90 }9192 section(): string {93 return this._section;94 }9596 method(): string {97 return this._method;98 }99100 filter(txres: ITransactionResult) {101 return txres.result.events.filter(e => e.event.section === section && e.event.method === method)102 .map(e => this.wrapEvent(e.event.data));103 }104105 find(txres: ITransactionResult) {106 const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);107 return e ? this.wrapEvent(e.event.data) : null;108 }109110 expect(txres: ITransactionResult) {111 const e = this.find(txres);112 if(e) {113 return e;114 } else {115 throw Error(`Expected event ${section}.${method}`);116 }117 }118 };119120 return helperClass;121}122123function eventJsonData<T = any>(data: any[], index: number) {124 return data[index].toJSON() as T;125}126127function eventHumanData(data: any[], index: number) {128 return data[index].toHuman();129}130131function eventData<T = any>(data: any[], index: number) {132 return data[index] as T;133}134135// eslint-disable-next-line @typescript-eslint/naming-convention136function EventSection(section: string) {137 return class Section {138 static section = section;139140 static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {141 const helperClass = EventHelper(Section.section, name, wrapEvent);142 return new helperClass();143 }144 };145}146147function schedulerSection(schedulerInstance: string) {148 return class extends EventSection(schedulerInstance) {149 static Dispatched = this.Method('Dispatched', data => ({150 task: eventJsonData(data, 0),151 id: eventHumanData(data, 1),152 result: data[2],153 }));154155 static PriorityChanged = this.Method('PriorityChanged', data => ({156 task: eventJsonData(data, 0),157 priority: eventJsonData(data, 1),158 }));159 };160}161162export class Event {163 static Democracy = class extends EventSection('democracy') {164 static Proposed = this.Method('Proposed', data => ({165 proposalIndex: eventJsonData<number>(data, 0),166 }));167168 static ExternalTabled = this.Method('ExternalTabled');169170 static Started = this.Method('Started', data => ({171 referendumIndex: eventJsonData<number>(data, 0),172 threshold: eventHumanData(data, 1),173 }));174175 static Voted = this.Method('Voted', data => ({176 voter: eventJsonData(data, 0),177 referendumIndex: eventJsonData<number>(data, 1),178 vote: eventJsonData(data, 2),179 }));180181 static Passed = this.Method('Passed', data => ({182 referendumIndex: eventJsonData<number>(data, 0),183 }));184185 static ProposalCanceled = this.Method('ProposalCanceled', data => ({186 propIndex: eventJsonData<number>(data, 0),187 }));188189 static Cancelled = this.Method('Cancelled', data => ({190 propIndex: eventJsonData<number>(data, 0),191 }));192193 static Vetoed = this.Method('Vetoed', data => ({194 who: eventHumanData(data, 0),195 proposalHash: eventHumanData(data, 1),196 until: eventJsonData<number>(data, 1),197 }));198 };199200 static Council = class extends EventSection('council') {201 static Proposed = this.Method('Proposed', data => ({202 account: eventHumanData(data, 0),203 proposalIndex: eventJsonData<number>(data, 1),204 proposalHash: eventHumanData(data, 2),205 threshold: eventJsonData<number>(data, 3),206 }));207 static Closed = this.Method('Closed', data => ({208 proposalHash: eventHumanData(data, 0),209 yes: eventJsonData<number>(data, 1),210 no: eventJsonData<number>(data, 2),211 }));212 static Executed = this.Method('Executed', data => ({213 proposalHash: eventHumanData(data, 0),214 }));215 };216217 static TechnicalCommittee = class extends EventSection('technicalCommittee') {218 static Proposed = this.Method('Proposed', data => ({219 account: eventHumanData(data, 0),220 proposalIndex: eventJsonData<number>(data, 1),221 proposalHash: eventHumanData(data, 2),222 threshold: eventJsonData<number>(data, 3),223 }));224 static Closed = this.Method('Closed', data => ({225 proposalHash: eventHumanData(data, 0),226 yes: eventJsonData<number>(data, 1),227 no: eventJsonData<number>(data, 2),228 }));229 static Approved = this.Method('Approved', data => ({230 proposalHash: eventHumanData(data, 0),231 }));232 static Executed = this.Method('Executed', data => ({233 proposalHash: eventHumanData(data, 0),234 result: eventHumanData(data, 1),235 }));236 };237238 static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {239 static Submitted = this.Method('Submitted', data => ({240 referendumIndex: eventJsonData<number>(data, 0),241 trackId: eventJsonData<number>(data, 1),242 proposal: eventJsonData(data, 2),243 }));244245 static Cancelled = this.Method('Cancelled', data => ({246 index: eventJsonData<number>(data, 0),247 tally: eventJsonData(data, 1),248 }));249 };250251 static UniqueScheduler = schedulerSection('uniqueScheduler');252 static Scheduler = schedulerSection('scheduler');253254 static XcmpQueue = class extends EventSection('xcmpQueue') {255 static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({256 messageHash: eventJsonData(data, 0),257 }));258259 static Success = this.Method('Success', data => ({260 messageHash: eventJsonData(data, 0),261 }));262263 static Fail = this.Method('Fail', data => ({264 messageHash: eventJsonData(data, 0),265 outcome: eventData<StagingXcmV2TraitsError>(data, 2),266 }));267 };268269 static DmpQueue = class extends EventSection('dmpQueue') {270 static ExecutedDownward = this.Method('ExecutedDownward', data => ({271 outcome: eventData<StagingXcmV3TraitsOutcome>(data, 2),272 }));273 };274}275276// eslint-disable-next-line @typescript-eslint/naming-convention277export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {278 return class extends Base {279 constructor(...args: any[]) {280 super(...args);281 }282283 override async executeExtrinsic(284 sender: IKeyringPair,285 extrinsic: string,286 params: any[],287 expectSuccess?: boolean,288 options: Partial<SignerOptions> | null = null,289 ): Promise<ITransactionResult> {290 const call = this.constructApiCall(extrinsic, params);291 const result = await super.executeExtrinsic(292 sender,293 'api.tx.sudo.sudo',294 [call],295 expectSuccess,296 options,297 );298299 if(result.status === 'Fail') return result;300301 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;302 if(data.isErr) {303 if(data.asErr.isModule) {304 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;305 const metaError = super.getApi()?.registry.findMetaError(error);306 throw new Error(`${metaError.section}.${metaError.name}`);307 } else if(data.asErr.isToken) {308 throw new Error(`Token: ${data.asErr.asToken}`);309 }310 // May be [object Object] in case of unhandled non-unit enum311 throw new Error(`Misc: ${data.asErr.toHuman()}`);312 }313 return result;314 }315 override async executeExtrinsicUncheckedWeight(316 sender: IKeyringPair,317 extrinsic: string,318 params: any[],319 expectSuccess?: boolean,320 options: Partial<SignerOptions> | null = null,321 ): Promise<ITransactionResult> {322 const call = this.constructApiCall(extrinsic, params);323 const result = await super.executeExtrinsic(324 sender,325 'api.tx.sudo.sudoUncheckedWeight',326 [call, {refTime: 0, proofSize: 0}],327 expectSuccess,328 options,329 );330331 if(result.status === 'Fail') return result;332333 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;334 if(data.isErr) {335 if(data.asErr.isModule) {336 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;337 const metaError = super.getApi()?.registry.findMetaError(error);338 throw new Error(`${metaError.section}.${metaError.name}`);339 } else if(data.asErr.isToken) {340 throw new Error(`Token: ${data.asErr.asToken}`);341 }342 // May be [object Object] in case of unhandled non-unit enum343 throw new Error(`Misc: ${data.asErr.toHuman()}`);344 }345 return result;346 }347 };348}349350class SchedulerGroup extends HelperGroup<UniqueHelper> {351 constructor(helper: UniqueHelper) {352 super(helper);353 }354355 cancelScheduled(signer: TSigner, scheduledId: string) {356 return this.helper.executeExtrinsic(357 signer,358 'api.tx.scheduler.cancelNamed',359 [scheduledId],360 true,361 );362 }363364 changePriority(signer: TSigner, scheduledId: string, priority: number) {365 return this.helper.executeExtrinsic(366 signer,367 'api.tx.scheduler.changeNamedPriority',368 [scheduledId, priority],369 true,370 );371 }372373 scheduleAt<T extends DevUniqueHelper>(374 executionBlockNumber: number,375 options: ISchedulerOptions = {},376 ) {377 return this.schedule<T>('schedule', executionBlockNumber, options);378 }379380 scheduleAfter<T extends DevUniqueHelper>(381 blocksBeforeExecution: number,382 options: ISchedulerOptions = {},383 ) {384 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);385 }386387 schedule<T extends UniqueHelper>(388 scheduleFn: 'schedule' | 'scheduleAfter',389 blocksNum: number,390 options: ISchedulerOptions = {},391 ) {392 // eslint-disable-next-line @typescript-eslint/naming-convention393 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);394 return this.helper.clone(ScheduledHelperType, {395 scheduleFn,396 blocksNum,397 options,398 }) as T;399 }400}401402class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {403 //todo:collator documentation404 addInvulnerable(signer: TSigner, address: string) {405 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);406 }407408 removeInvulnerable(signer: TSigner, address: string) {409 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);410 }411412 async getInvulnerables(): Promise<string[]> {413 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());414 }415416 /** and also total max invulnerables */417 maxCollators(): number {418 return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);419 }420421 async getDesiredCollators(): Promise<number> {422 return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();423 }424425 setLicenseBond(signer: TSigner, amount: bigint) {426 return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);427 }428429 async getLicenseBond(): Promise<bigint> {430 return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();431 }432433 obtainLicense(signer: TSigner) {434 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);435 }436437 releaseLicense(signer: TSigner) {438 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);439 }440441 forceReleaseLicense(signer: TSigner, released: string) {442 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);443 }444445 async hasLicense(address: string): Promise<bigint> {446 return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();447 }448449 onboard(signer: TSigner) {450 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);451 }452453 offboard(signer: TSigner) {454 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);455 }456457 async getCandidates(): Promise<string[]> {458 return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());459 }460}461462export class DevUniqueHelper extends UniqueHelper {463 /**464 * Arrange methods for tests465 */466 arrange: ArrangeGroup;467 wait: WaitGroup;468 admin: AdminGroup;469 session: SessionGroup;470 testUtils: TestUtilGroup;471 foreignAssets: ForeignAssetsGroup;472 xcm: XcmGroup<DevUniqueHelper>;473 xTokens: XTokensGroup<DevUniqueHelper>;474 tokens: TokensGroup<DevUniqueHelper>;475 scheduler: SchedulerGroup;476 collatorSelection: CollatorSelectionGroup;477 council: ICollectiveGroup;478 technicalCommittee: ICollectiveGroup;479 fellowship: IFellowshipGroup;480 democracy: DemocracyGroup;481482 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {483 options.helperBase = options.helperBase ?? DevUniqueHelper;484485 super(logger, options);486 this.arrange = new ArrangeGroup(this);487 this.wait = new WaitGroup(this);488 this.admin = new AdminGroup(this);489 this.testUtils = new TestUtilGroup(this);490 this.session = new SessionGroup(this);491 this.foreignAssets = new ForeignAssetsGroup(this);492 this.xcm = new XcmGroup(this, 'polkadotXcm');493 this.xTokens = new XTokensGroup(this);494 this.tokens = new TokensGroup(this);495 this.scheduler = new SchedulerGroup(this);496 this.collatorSelection = new CollatorSelectionGroup(this);497 this.council = {498 collective: new CollectiveGroup(this, 'council'),499 membership: new CollectiveMembershipGroup(this, 'councilMembership'),500 };501 this.technicalCommittee = {502 collective: new CollectiveGroup(this, 'technicalCommittee'),503 membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),504 };505 this.fellowship = {506 collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),507 referenda: new ReferendaGroup(this, 'fellowshipReferenda'),508 };509 this.democracy = new DemocracyGroup(this);510 }511512 override async connect(wsEndpoint: string, _listeners?: any): Promise<void> {513 if(!wsEndpoint) throw new Error('wsEndpoint was not set');514 const wsProvider = new WsProvider(wsEndpoint);515 this.api = new ApiPromise({516 provider: wsProvider,517 signedExtensions: {518 ContractHelpers: {519 extrinsic: {},520 payload: {},521 },522 CheckMaintenance: {523 extrinsic: {},524 payload: {},525 },526 DisableIdentityCalls: {527 extrinsic: {},528 payload: {},529 },530 FakeTransactionFinalizer: {531 extrinsic: {},532 payload: {},533 },534 },535 rpc: {536 unique: defs.unique.rpc,537 appPromotion: defs.appPromotion.rpc,538 povinfo: defs.povinfo.rpc,539 eth: {540 feeHistory: {541 description: 'Dummy',542 params: [],543 type: 'u8',544 },545 maxPriorityFeePerGas: {546 description: 'Dummy',547 params: [],548 type: 'u8',549 },550 },551 },552 });553 await this.api.isReadyOrError;554 this.network = await UniqueHelper.detectNetwork(this.api);555 this.wsEndpoint = wsEndpoint;556 }557 getSudo<T extends DevUniqueHelper>() {558 // eslint-disable-next-line @typescript-eslint/naming-convention559 const SudoHelperType = SudoHelper(this.helperBase);560 return this.clone(SudoHelperType) as T;561 }562}563564export class DevRelayHelper extends RelayHelper {565 wait: WaitGroup;566567 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {568 options.helperBase = options.helperBase ?? DevRelayHelper;569570 super(logger, options);571 this.wait = new WaitGroup(this);572 }573574 getSudo() {575 // eslint-disable-next-line @typescript-eslint/naming-convention576 const SudoHelperType = SudoHelper(this.helperBase);577 return this.clone(SudoHelperType) as DevRelayHelper;578 }579}580581export class DevWestmintHelper extends WestmintHelper {582 wait: WaitGroup;583584 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {585 options.helperBase = options.helperBase ?? DevWestmintHelper;586587 super(logger, options);588 this.wait = new WaitGroup(this);589 }590}591592export class DevStatemineHelper extends DevWestmintHelper {}593594export class DevStatemintHelper extends DevWestmintHelper {}595596export class DevMoonbeamHelper extends MoonbeamHelper {597 account: MoonbeamAccountGroup;598 wait: WaitGroup;599 fastDemocracy: MoonbeamFastDemocracyGroup;600601 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {602 options.helperBase = options.helperBase ?? DevMoonbeamHelper;603 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';604605 super(logger, options);606 this.account = new MoonbeamAccountGroup(this);607 this.wait = new WaitGroup(this);608 this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);609 }610}611612export class DevMoonriverHelper extends DevMoonbeamHelper {613 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {614 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';615 super(logger, options);616 }617}618619export class DevAstarHelper extends AstarHelper {620 wait: WaitGroup;621622 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {623 options.helperBase = options.helperBase ?? DevAstarHelper;624625 super(logger, options);626 this.wait = new WaitGroup(this);627 }628629 getSudo<T extends AstarHelper>() {630 // eslint-disable-next-line @typescript-eslint/naming-convention631 const SudoHelperType = SudoHelper(this.helperBase);632 return this.clone(SudoHelperType) as T;633 }634}635636export class DevShidenHelper extends DevAstarHelper { }637638export class DevAcalaHelper extends AcalaHelper {639 wait: WaitGroup;640641 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {642 options.helperBase = options.helperBase ?? DevAcalaHelper;643644 super(logger, options);645 this.wait = new WaitGroup(this);646 }647 getSudo() {648 // eslint-disable-next-line @typescript-eslint/naming-convention649 const SudoHelperType = SudoHelper(this.helperBase);650 return this.clone(SudoHelperType) as DevAcalaHelper;651 }652}653654export class DevPolkadexHelper extends PolkadexHelper {655 wait: WaitGroup;656 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {657 options.helperBase = options.helperBase ?? PolkadexHelper;658659 super(logger, options);660 this.wait = new WaitGroup(this);661 }662663 getSudo() {664 // eslint-disable-next-line @typescript-eslint/naming-convention665 const SudoHelperType = SudoHelper(this.helperBase);666 return this.clone(SudoHelperType) as DevPolkadexHelper;667 }668}669670export class DevKaruraHelper extends DevAcalaHelper {}671672export class ArrangeGroup {673 helper: DevUniqueHelper;674675 scheduledIdSlider = 0;676677 constructor(helper: DevUniqueHelper) {678 this.helper = helper;679 }680681 /**682 * Generates accounts with the specified UNQ token balance683 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.684 * @param donor donor account for balances685 * @returns array of newly created accounts686 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);687 */688 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {689 let nonce = await this.helper.chain.getNonce(donor.address);690 const wait = new WaitGroup(this.helper);691 const ss58Format = this.helper.chain.getChainProperties().ss58Format;692 const tokenNominal = this.helper.balance.getOneTokenNominal();693 const transactions = [];694 const accounts: IKeyringPair[] = [];695 for(const balance of balances) {696 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);697 accounts.push(recipient);698 if(balance !== 0n) {699 const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, balance * tokenNominal]);700 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));701 nonce++;702 }703 }704705 await Promise.all(transactions).catch(_e => {});706707 //#region TODO remove this region, when nonce problem will be solved708 const checkBalances = async () => {709 let isSuccess = true;710 for(let i = 0; i < balances.length; i++) {711 const balance = await this.helper.balance.getSubstrate(accounts[i].address);712 if(balance !== balances[i] * tokenNominal) {713 isSuccess = false;714 break;715 }716 }717 return isSuccess;718 };719720 let accountsCreated = false;721 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;722 // checkBalances retry up to 5-50 blocks723 for(let index = 0; index < maxBlocksChecked; index++) {724 accountsCreated = await checkBalances();725 if(accountsCreated) break;726 await wait.newBlocks(1);727 }728729 if(!accountsCreated) throw Error('Accounts generation failed');730 //#endregion731732 return accounts;733 };734735 // TODO combine this method and createAccounts into one736 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {737 const createAsManyAsCan = async () => {738 let transactions: any = [];739 const accounts: IKeyringPair[] = [];740 let nonce = await this.helper.chain.getNonce(donor.address);741 const tokenNominal = this.helper.balance.getOneTokenNominal();742 const ss58Format = this.helper.chain.getChainProperties().ss58Format;743 for(let i = 0; i < accountsToCreate; i++) {744 if(i === 500) { // if there are too many accounts to create745 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled746 transactions = []; //747 nonce = await this.helper.chain.getNonce(donor.address); // update nonce748 }749 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);750 accounts.push(recipient);751 if(withBalance !== 0n) {752 const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, withBalance * tokenNominal]);753 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));754 nonce++;755 }756 }757758 const fullfilledAccounts = [];759 await Promise.allSettled(transactions);760 for(const account of accounts) {761 const accountBalance = await this.helper.balance.getSubstrate(account.address);762 if(accountBalance === withBalance * tokenNominal) {763 fullfilledAccounts.push(account);764 }765 }766 return fullfilledAccounts;767 };768769770 const crowd: IKeyringPair[] = [];771 // do up to 5 retries772 for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {773 const asManyAsCan = await createAsManyAsCan();774 crowd.push(...asManyAsCan);775 accountsToCreate -= asManyAsCan.length;776 }777778 if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);779780 return crowd;781 };782783 /**784 * Generates one account with zero balance785 * @returns the newly generated account786 * @example const account = await helper.arrange.createEmptyAccount();787 */788 createEmptyAccount = (): IKeyringPair => {789 const ss58Format = this.helper.chain.getChainProperties().ss58Format;790 return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);791 };792793 isDevNode = async () => {794 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();795 if(blockNumber == 0) {796 await this.helper.wait.newBlocks(1);797 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();798 }799 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);800 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);801 const findCreationDate = (block: any) => {802 const humanBlock = block.toHuman();803 let date;804 humanBlock.block.extrinsics.forEach((ext: any) => {805 if(ext.method.section === 'timestamp') {806 date = Number(ext.method.args.now.replaceAll(',', ''));807 }808 });809 return date;810 };811 const block1date = await findCreationDate(block1);812 const block2date = await findCreationDate(block2);813 if(block2date! - block1date! < 9000) return true;814 return false;815 };816817 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {818 const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);819 let balance = await this.helper.balance.getSubstrate(address);820821 await promise();822823 balance -= await this.helper.balance.getSubstrate(address);824825 return balance;826 }827828 async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {829 const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);830831 const kvJson: {[key: string]: string} = {};832833 for(const kv of rawPovInfo.keyValues) {834 kvJson[kv.key.toHex()] = kv.value.toHex();835 }836837 const kvStr = JSON.stringify(kvJson);838839 const chainql = spawnSync(840 'chainql',841 [842 `--tla-code=data=${kvStr}`,843 '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,844 ],845 );846847 if(!chainql.stdout) {848 throw Error('unable to get an output from the `chainql`');849 }850851 return {852 proofSize: rawPovInfo.proofSize.toNumber(),853 compactProofSize: rawPovInfo.compactProofSize.toNumber(),854 compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),855 results: rawPovInfo.results,856 kv: JSON.parse(chainql.stdout.toString()),857 };858 }859860 calculatePalletAddress(palletId: any) {861 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));862 return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);863 }864865 makeScheduledIds(num: number): string[] {866 function makeId(slider: number) {867 const scheduledIdSize = 64;868 const hexId = slider.toString(16);869 const prefixSize = scheduledIdSize - hexId.length;870871 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;872873 return scheduledId;874 }875876 const ids = [];877 for(let i = 0; i < num; i++) {878 ids.push(makeId(this.scheduledIdSlider));879 this.scheduledIdSlider += 1;880 }881882 return ids;883 }884885 makeScheduledId(): string {886 return (this.makeScheduledIds(1))[0];887 }888889 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {890 const capture = new EventCapture(this.helper, eventSection, eventMethod);891 await capture.startCapture();892893 return capture;894 }895896 makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {897 return {898 V2: [899 {900 WithdrawAsset: [901 {902 id,903 fun: {904 Fungible: amount,905 },906 },907 ],908 },909 {910 BuyExecution: {911 fees: {912 id,913 fun: {914 Fungible: amount,915 },916 },917 weightLimit: 'Unlimited',918 },919 },920 {921 DepositAsset: {922 assets: {923 Wild: 'All',924 },925 maxAssets: 1,926 beneficiary: {927 parents: 0,928 interior: {929 X1: {930 AccountId32: {931 network: 'Any',932 id: beneficiary,933 },934 },935 },936 },937 },938 },939 ],940 };941 }942943 makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {944 return {945 V2: [946 {947 ReserveAssetDeposited: [948 {949 id,950 fun: {951 Fungible: amount,952 },953 },954 ],955 },956 {957 BuyExecution: {958 fees: {959 id,960 fun: {961 Fungible: amount,962 },963 },964 weightLimit: 'Unlimited',965 },966 },967 {968 DepositAsset: {969 assets: {970 Wild: 'All',971 },972 maxAssets: 1,973 beneficiary: {974 parents: 0,975 interior: {976 X1: {977 AccountId32: {978 network: 'Any',979 id: beneficiary,980 },981 },982 },983 },984 },985 },986 ],987 };988 }989990 makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) {991 return {992 V3: [993 {994 UnpaidExecution: {995 weightLimit: 'Unlimited',996 checkOrigin: null,997 },998 },999 {1000 Transact: {1001 originKind: 'Superuser',1002 requireWeightAtMost: {1003 refTime: info.weightMultiplier * 200000000,1004 proofSize: info.weightMultiplier * 3000,1005 },1006 call: {1007 encoded: info.call,1008 },1009 },1010 },1011 ],1012 };1013 }1014}10151016class MoonbeamAccountGroup {1017 helper: MoonbeamHelper;10181019 keyring: Keyring;1020 _alithAccount: IKeyringPair;1021 _baltatharAccount: IKeyringPair;1022 _dorothyAccount: IKeyringPair;10231024 constructor(helper: MoonbeamHelper) {1025 this.helper = helper;10261027 this.keyring = new Keyring({type: 'ethereum'});1028 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';1029 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';1030 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';10311032 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');1033 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');1034 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');1035 }10361037 alithAccount() {1038 return this._alithAccount;1039 }10401041 baltatharAccount() {1042 return this._baltatharAccount;1043 }10441045 dorothyAccount() {1046 return this._dorothyAccount;1047 }10481049 create() {1050 return this.keyring.addFromUri(mnemonicGenerate());1051 }1052}10531054class MoonbeamFastDemocracyGroup {1055 helper: DevMoonbeamHelper;10561057 constructor(helper: DevMoonbeamHelper) {1058 this.helper = helper;1059 }10601061 async executeProposal(proposalDesciption: string, encodedProposal: string) {1062 const proposalHash = blake2AsHex(encodedProposal);10631064 const alithAccount = this.helper.account.alithAccount();1065 const baltatharAccount = this.helper.account.baltatharAccount();1066 const dorothyAccount = this.helper.account.dorothyAccount();10671068 const councilVotingThreshold = 2;1069 const technicalCommitteeThreshold = 2;1070 const fastTrackVotingPeriod = 3;1071 const fastTrackDelayPeriod = 0;10721073 console.log(`[democracy] executing '${proposalDesciption}' proposal`);10741075 // >>> Propose external motion through council >>>1076 console.log('\t* Propose external motion through council.......');1077 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});1078 const encodedMotion = externalMotion?.method.toHex() || '';1079 const motionHash = blake2AsHex(encodedMotion);1080 console.log('\t* Motion hash is %s', motionHash);10811082 await this.helper.collective.council.propose(1083 baltatharAccount,1084 councilVotingThreshold,1085 externalMotion,1086 externalMotion.encodedLength,1087 );10881089 const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;1090 await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);1091 await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);10921093 await this.helper.collective.council.close(1094 dorothyAccount,1095 motionHash,1096 councilProposalIdx,1097 {1098 refTime: 1_000_000_000,1099 proofSize: 1_000_000,1100 },1101 externalMotion.encodedLength,1102 );1103 console.log('\t* Propose external motion through council.......DONE');1104 // <<< Propose external motion through council <<<11051106 // >>> Fast track proposal through technical committee >>>1107 console.log('\t* Fast track proposal through technical committee.......');1108 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);1109 const encodedFastTrack = fastTrack?.method.toHex() || '';1110 const fastTrackHash = blake2AsHex(encodedFastTrack);1111 console.log('\t* FastTrack hash is %s', fastTrackHash);11121113 await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);11141115 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;1116 await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);1117 await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);11181119 await this.helper.collective.techCommittee.close(1120 baltatharAccount,1121 fastTrackHash,1122 techProposalIdx,1123 {1124 refTime: 1_000_000_000,1125 proofSize: 1_000_000,1126 },1127 fastTrack.encodedLength,1128 );1129 console.log('\t* Fast track proposal through technical committee.......DONE');1130 // <<< Fast track proposal through technical committee <<<11311132 const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);1133 const referendumIndex = democracyStarted.referendumIndex;11341135 // >>> Referendum voting >>>1136 console.log(`\t* Referendum #${referendumIndex} voting.......`);1137 await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {1138 balance: 10_000_000_000_000_000_000n,1139 vote: {aye: true, conviction: 1},1140 });1141 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);1142 // <<< Referendum voting <<<11431144 // Wait the proposal to pass1145 await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);11461147 await this.helper.wait.newBlocks(1);11481149 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);1150 }1151}11521153class WaitGroup {1154 helper: ChainHelperBase;11551156 constructor(helper: ChainHelperBase) {1157 this.helper = helper;1158 }11591160 sleep(milliseconds: number) {1161 return new Promise((resolve) => setTimeout(resolve, milliseconds));1162 }11631164 private async waitWithTimeout(promise: Promise<any>, timeout: number) {1165 let isBlock = false;1166 promise.then(() => isBlock = true).catch(() => isBlock = true);1167 let totalTime = 0;1168 const step = 100;1169 while(!isBlock) {1170 await this.sleep(step);1171 totalTime += step;1172 if(totalTime >= timeout) throw Error('Blocks production failed');1173 }1174 return promise;1175 }11761177 /**1178 * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.1179 * @param promise async operation to race against the timeout1180 * @param timeoutMS time after which to time out1181 * @param timeoutError error message to throw1182 * @returns promise of the same type the operation had1183 */1184 withTimeout<T>(1185 promise: Promise<T>,1186 timeoutMS = 30000,1187 timeoutError = 'The operation has timed out!',1188 ): Promise<T> {1189 const timeout = new Promise<never>((_, reject) => {1190 setTimeout(() => {1191 reject(new Error(timeoutError));1192 }, timeoutMS);1193 });11941195 return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});1196 }11971198 /**1199 * Wait for specified number of blocks1200 * @param blocksCount number of blocks to wait1201 * @returns1202 */1203 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {1204 timeout = timeout ?? blocksCount * 60_000;1205 // eslint-disable-next-line no-async-promise-executor1206 const promise = new Promise<void>(async (resolve) => {1207 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {1208 if(blocksCount > 0) {1209 blocksCount--;1210 } else {1211 unsubscribe();1212 resolve();1213 }1214 });1215 });1216 await this.waitWithTimeout(promise, timeout);1217 return promise;1218 }12191220 /**1221 * Wait for the specified number of sessions to pass.1222 * Only applicable if the Session pallet is turned on.1223 * @param sessionCount number of sessions to wait1224 * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks1225 * @returns1226 */1227 async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {1228 console.log(`Waiting for ${sessionCount} new session${sessionCount>1's'''}.`1229 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');12301231 const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;1232 let currentSessionIndex = -1;12331234 while(currentSessionIndex < expectedSessionIndex) {1235 // eslint-disable-next-line no-async-promise-executor1236 currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {1237 await this.newBlocks(1);1238 const res = await (this.helper as DevUniqueHelper).session.getIndex();1239 resolve(res);1240 }), blockTimeout, 'The chain has stopped producing blocks!');1241 }1242 }12431244 async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {1245 timeout = timeout ?? 30 * 60 * 1000;1246 // eslint-disable-next-line no-async-promise-executor1247 const promise = new Promise<void>(async (resolve) => {1248 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1249 if(data.number.toNumber() >= blockNumber) {1250 unsubscribe();1251 resolve();1252 }1253 });1254 });1255 await this.waitWithTimeout(promise, timeout);1256 return promise;1257 }12581259 async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {1260 timeout = timeout ?? 30 * 60 * 1000;1261 // eslint-disable-next-line no-async-promise-executor1262 const promise = new Promise<void>(async (resolve) => {1263 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {1264 if(data.value.relayParentNumber.toNumber() >= blockNumber) {1265 // @ts-ignore1266 unsubscribe();1267 resolve();1268 }1269 });1270 });1271 await this.waitWithTimeout(promise, timeout);1272 return promise;1273 }12741275 noScheduledTasks() {1276 const api = this.helper.getApi();12771278 // eslint-disable-next-line no-async-promise-executor1279 const promise = new Promise<void>(async resolve => {1280 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1281 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();12821283 if(areThereScheduledTasks.length == 0) {1284 unsubscribe();1285 resolve();1286 }1287 });1288 });12891290 return promise;1291 }12921293 parachainBlockMultiplesOf(val: bigint) {1294 // eslint-disable-next-line no-async-promise-executor1295 const promise = new Promise<void>(async resolve => {1296 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1297 if(data.number.toBigInt() % val == 0n) {1298 console.log(`from waiter: ${data.number.toBigInt()}`);1299 unsubscribe();1300 resolve();1301 }1302 });1303 });1304 return promise;1305 }13061307 event<T extends IEventHelper>(1308 maxBlocksToWait: number,1309 eventHelper: T,1310 filter: (_: any) => boolean = () => true,1311 ): any {1312 // eslint-disable-next-line no-async-promise-executor1313 const promise = new Promise<T | null>(async (resolve) => {1314 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1315 const blockNumber = header.number.toJSON();1316 const blockHash = header.hash;1317 const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1318 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;13191320 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);13211322 const apiAt = await this.helper.getApi().at(blockHash);1323 const eventRecords = (await apiAt.query.system.events()) as any;13241325 const neededEvent = eventRecords.toArray()1326 .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1327 .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1328 .find(filter);13291330 if(neededEvent) {1331 unsubscribe();1332 resolve(neededEvent);1333 } else if(maxBlocksToWait > 0) {1334 maxBlocksToWait--;1335 } else {1336 this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found.1337 The wait lasted until block ${blockNumber} inclusive`);1338 unsubscribe();1339 resolve(null);1340 }1341 });1342 });1343 return promise;1344 }13451346 async expectEvent<T extends IEventHelper>(1347 maxBlocksToWait: number,1348 eventHelper: T,1349 filter: (e: any) => boolean = () => true,1350 ) {1351 const e = await this.event(maxBlocksToWait, eventHelper, filter);1352 if(e == null) {1353 throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1354 } else {1355 return e;1356 }1357 }1358}13591360class SessionGroup {1361 helper: ChainHelperBase;13621363 constructor(helper: ChainHelperBase) {1364 this.helper = helper;1365 }13661367 //todo:collator documentation1368 async getIndex(): Promise<number> {1369 return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1370 }13711372 newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1373 return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1374 }13751376 setOwnKeys(signer: TSigner, key: string) {1377 return this.helper.executeExtrinsic(1378 signer,1379 'api.tx.session.setKeys',1380 [key, '0x0'],1381 true,1382 );1383 }13841385 setOwnKeysFromAddress(signer: TSigner) {1386 return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1387 }1388}13891390class TestUtilGroup {1391 helper: DevUniqueHelper;13921393 constructor(helper: DevUniqueHelper) {1394 this.helper = helper;1395 }13961397 async enable(testUtilsPalletName: string) {1398 if(this.helper.fetchMissingPalletNames([testUtilsPalletName]).length != 0) {1399 return;1400 }14011402 const signer = this.helper.util.fromSeed('//Alice');1403 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1404 }14051406 async setTestValue(signer: TSigner, testVal: number) {1407 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1408 }14091410 async incTestValue(signer: TSigner) {1411 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1412 }14131414 async setTestValueAndRollback(signer: TSigner, testVal: number) {1415 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1416 }14171418 async testValue(blockIdx?: number) {1419 const api = blockIdx1420 ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1421 : this.helper.getApi();14221423 return (await api.query.testUtils.testValue()).toJSON();1424 }14251426 async justTakeFee(signer: TSigner) {1427 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1428 }14291430 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1431 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1432 }1433}14341435class EventCapture {1436 helper: DevUniqueHelper;1437 eventSection: string;1438 eventMethod: string;1439 events: EventRecord[] = [];1440 unsubscribe: VoidFn | null = null;14411442 constructor(1443 helper: DevUniqueHelper,1444 eventSection: string,1445 eventMethod: string,1446 ) {1447 this.helper = helper;1448 this.eventSection = eventSection;1449 this.eventMethod = eventMethod;1450 }14511452 async startCapture() {1453 this.stopCapture();1454 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1455 const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);14561457 this.events.push(...newEvents);1458 })) as any;1459 }14601461 stopCapture() {1462 if(this.unsubscribe !== null) {1463 this.unsubscribe();1464 }1465 }14661467 extractCapturedEvents() {1468 return this.events;1469 }1470}14711472class AdminGroup {1473 helper: UniqueHelper;14741475 constructor(helper: UniqueHelper) {1476 this.helper = helper;1477 }14781479 async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> {1480 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1481 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1482 staker: e.event.data[0].toString(),1483 stake: e.event.data[1].toBigInt(),1484 payout: e.event.data[2].toBigInt(),1485 }));1486 }1487}14881489// eslint-disable-next-line @typescript-eslint/naming-convention1490function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {1491 return class extends Base {1492 scheduleFn: 'schedule' | 'scheduleAfter';1493 blocksNum: number;1494 options: ISchedulerOptions;14951496 constructor(...args: any[]) {1497 const logger = args[0] as ILogger;1498 const options = args[1] as {1499 scheduleFn: 'schedule' | 'scheduleAfter',1500 blocksNum: number,1501 options: ISchedulerOptions1502 };15031504 super(logger);15051506 this.scheduleFn = options.scheduleFn;1507 this.blocksNum = options.blocksNum;1508 this.options = options.options;1509 }15101511 override executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {1512 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);15131514 const mandatorySchedArgs = [1515 this.blocksNum,1516 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,1517 this.options.priority ?? null,1518 scheduledTx,1519 ];15201521 let schedArgs;1522 let scheduleFn;15231524 if(this.options.scheduledId) {1525 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];15261527 if(this.scheduleFn == 'schedule') {1528 scheduleFn = 'scheduleNamed';1529 } else if(this.scheduleFn == 'scheduleAfter') {1530 scheduleFn = 'scheduleNamedAfter';1531 }1532 } else {1533 schedArgs = mandatorySchedArgs;1534 scheduleFn = this.scheduleFn;1535 }15361537 const extrinsic = 'api.tx.scheduler.' + scheduleFn;15381539 return super.executeExtrinsic(1540 sender,1541 extrinsic as any,1542 schedArgs,1543 expectSuccess,1544 );1545 }1546 };1547}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import '@unique/opal-types/augment-api.js';5import '@unique/opal-types/augment-types.js';6import '@unique/opal-types/types-lookup.js';78import {stringToU8a} from '@polkadot/util';9import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';10import type {ChainHelperBaseConstructor, UniqueHelperConstructor} from './unique.js';11import {UniqueHelper, ChainHelperBase, HelperGroup} from './unique.js';12import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';13import * as defs from '@unique/opal-types/definitions.js';14import type {IKeyringPair} from '@polkadot/types/types';15import type {EventRecord} from '@polkadot/types/interfaces';16import type {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types.js';17import type {FrameSystemEventRecord, StagingXcmV2TraitsError, StagingXcmV3TraitsOutcome} from '@polkadot/types/lookup';18import type {SignerOptions, VoidFn} from '@polkadot/api/types';19import {spawnSync} from 'child_process';20import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup, HydraDxHelper} from './unique.xcm.js';21import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance.js';22import type {ICollectiveGroup, IFellowshipGroup} from './unique.governance.js';2324export class SilentLogger {25 log(_msg: any, _level: any): void { }26 level = {27 ERROR: 'ERROR' as const,28 WARNING: 'WARNING' as const,29 INFO: 'INFO' as const,30 };31}3233export class SilentConsole {34 // TODO: Remove, this is temporary: Filter unneeded API output35 // (Jaco promised it will be removed in the next version)36 consoleErr: any;37 consoleLog: any;38 consoleWarn: any;3940 constructor() {41 this.consoleErr = console.error;42 this.consoleLog = console.log;43 this.consoleWarn = console.warn;44 }4546 enable() {47 const outFn = (printer: any) => (...args: any[]) => {48 for(const arg of args) {49 if(typeof arg !== 'string')50 continue;51 const skippedWarnings = ['1000:: Normal connection closure', 'Not decorating unknown runtime apis:', 'RPC methods not decorated:', 'Not decorating runtime apis', 'Bad input data provided to validate_transaction', 'account balance too low', '1006:: Abnormal Closure'];52 const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false);53 if(needToSkip || arg === 'Normal connection closure')54 return;55 }56 printer(...args);57 };5859 console.error = outFn(this.consoleErr.bind(console));60 console.log = outFn(this.consoleLog.bind(console));61 console.warn = outFn(this.consoleWarn.bind(console));62 }6364 disable() {65 console.error = this.consoleErr;66 console.log = this.consoleLog;67 console.warn = this.consoleWarn;68 }69}7071export interface IEventHelper {72 section(): string;7374 method(): string;7576 wrapEvent(data: any[]): any;77}7879// eslint-disable-next-line @typescript-eslint/naming-convention80function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {81 const helperClass = class implements IEventHelper {82 wrapEvent: (data: any[]) => any;83 _section: string;84 _method: string;8586 constructor() {87 this.wrapEvent = wrapEvent;88 this._section = section;89 this._method = method;90 }9192 section(): string {93 return this._section;94 }9596 method(): string {97 return this._method;98 }99100 filter(txres: ITransactionResult) {101 return txres.result.events.filter(e => e.event.section === section && e.event.method === method)102 .map(e => this.wrapEvent(e.event.data));103 }104105 find(txres: ITransactionResult) {106 const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);107 return e ? this.wrapEvent(e.event.data) : null;108 }109110 expect(txres: ITransactionResult) {111 const e = this.find(txres);112 if(e) {113 return e;114 } else {115 throw Error(`Expected event ${section}.${method}`);116 }117 }118 };119120 return helperClass;121}122123function eventJsonData<T = any>(data: any[], index: number) {124 return data[index].toJSON() as T;125}126127function eventHumanData(data: any[], index: number) {128 return data[index].toHuman();129}130131function eventData<T = any>(data: any[], index: number) {132 return data[index] as T;133}134135// eslint-disable-next-line @typescript-eslint/naming-convention136function EventSection(section: string) {137 return class Section {138 static section = section;139140 static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {141 const helperClass = EventHelper(Section.section, name, wrapEvent);142 return new helperClass();143 }144 };145}146147function schedulerSection(schedulerInstance: string) {148 return class extends EventSection(schedulerInstance) {149 static Dispatched = this.Method('Dispatched', data => ({150 task: eventJsonData(data, 0),151 id: eventHumanData(data, 1),152 result: data[2],153 }));154155 static PriorityChanged = this.Method('PriorityChanged', data => ({156 task: eventJsonData(data, 0),157 priority: eventJsonData(data, 1),158 }));159 };160}161162export class Event {163 static Democracy = class extends EventSection('democracy') {164 static Proposed = this.Method('Proposed', data => ({165 proposalIndex: eventJsonData<number>(data, 0),166 }));167168 static ExternalTabled = this.Method('ExternalTabled');169170 static Started = this.Method('Started', data => ({171 referendumIndex: eventJsonData<number>(data, 0),172 threshold: eventHumanData(data, 1),173 }));174175 static Voted = this.Method('Voted', data => ({176 voter: eventJsonData(data, 0),177 referendumIndex: eventJsonData<number>(data, 1),178 vote: eventJsonData(data, 2),179 }));180181 static Passed = this.Method('Passed', data => ({182 referendumIndex: eventJsonData<number>(data, 0),183 }));184185 static ProposalCanceled = this.Method('ProposalCanceled', data => ({186 propIndex: eventJsonData<number>(data, 0),187 }));188189 static Cancelled = this.Method('Cancelled', data => ({190 propIndex: eventJsonData<number>(data, 0),191 }));192193 static Vetoed = this.Method('Vetoed', data => ({194 who: eventHumanData(data, 0),195 proposalHash: eventHumanData(data, 1),196 until: eventJsonData<number>(data, 1),197 }));198 };199200 static Council = class extends EventSection('council') {201 static Proposed = this.Method('Proposed', data => ({202 account: eventHumanData(data, 0),203 proposalIndex: eventJsonData<number>(data, 1),204 proposalHash: eventHumanData(data, 2),205 threshold: eventJsonData<number>(data, 3),206 }));207 static Closed = this.Method('Closed', data => ({208 proposalHash: eventHumanData(data, 0),209 yes: eventJsonData<number>(data, 1),210 no: eventJsonData<number>(data, 2),211 }));212 static Executed = this.Method('Executed', data => ({213 proposalHash: eventHumanData(data, 0),214 }));215 };216217 static TechnicalCommittee = class extends EventSection('technicalCommittee') {218 static Proposed = this.Method('Proposed', data => ({219 account: eventHumanData(data, 0),220 proposalIndex: eventJsonData<number>(data, 1),221 proposalHash: eventHumanData(data, 2),222 threshold: eventJsonData<number>(data, 3),223 }));224 static Closed = this.Method('Closed', data => ({225 proposalHash: eventHumanData(data, 0),226 yes: eventJsonData<number>(data, 1),227 no: eventJsonData<number>(data, 2),228 }));229 static Approved = this.Method('Approved', data => ({230 proposalHash: eventHumanData(data, 0),231 }));232 static Executed = this.Method('Executed', data => ({233 proposalHash: eventHumanData(data, 0),234 result: eventHumanData(data, 1),235 }));236 };237238 static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {239 static Submitted = this.Method('Submitted', data => ({240 referendumIndex: eventJsonData<number>(data, 0),241 trackId: eventJsonData<number>(data, 1),242 proposal: eventJsonData(data, 2),243 }));244245 static Cancelled = this.Method('Cancelled', data => ({246 index: eventJsonData<number>(data, 0),247 tally: eventJsonData(data, 1),248 }));249 };250251 static UniqueScheduler = schedulerSection('uniqueScheduler');252 static Scheduler = schedulerSection('scheduler');253254 static XcmpQueue = class extends EventSection('xcmpQueue') {255 static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({256 messageHash: eventJsonData(data, 0),257 }));258259 static Success = this.Method('Success', data => ({260 messageHash: eventJsonData(data, 0),261 }));262263 static Fail = this.Method('Fail', data => ({264 messageHash: eventJsonData(data, 0),265 outcome: eventData<StagingXcmV2TraitsError>(data, 2),266 }));267 };268269 static DmpQueue = class extends EventSection('dmpQueue') {270 static ExecutedDownward = this.Method('ExecutedDownward', data => ({271 outcome: eventData<StagingXcmV3TraitsOutcome>(data, 2),272 }));273 };274}275276// eslint-disable-next-line @typescript-eslint/naming-convention277export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {278 return class extends Base {279 constructor(...args: any[]) {280 super(...args);281 }282283 override async executeExtrinsic(284 sender: IKeyringPair,285 extrinsic: string,286 params: any[],287 expectSuccess?: boolean,288 options: Partial<SignerOptions> | null = null,289 ): Promise<ITransactionResult> {290 const call = this.constructApiCall(extrinsic, params);291 const result = await super.executeExtrinsic(292 sender,293 'api.tx.sudo.sudo',294 [call],295 expectSuccess,296 options,297 );298299 if(result.status === 'Fail') return result;300301 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;302 if(data.isErr) {303 if(data.asErr.isModule) {304 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;305 const metaError = super.getApi()?.registry.findMetaError(error);306 throw new Error(`${metaError.section}.${metaError.name}`);307 } else if(data.asErr.isToken) {308 throw new Error(`Token: ${data.asErr.asToken}`);309 }310 // May be [object Object] in case of unhandled non-unit enum311 throw new Error(`Misc: ${data.asErr.toHuman()}`);312 }313 return result;314 }315 override async executeExtrinsicUncheckedWeight(316 sender: IKeyringPair,317 extrinsic: string,318 params: any[],319 expectSuccess?: boolean,320 options: Partial<SignerOptions> | null = null,321 ): Promise<ITransactionResult> {322 const call = this.constructApiCall(extrinsic, params);323 const result = await super.executeExtrinsic(324 sender,325 'api.tx.sudo.sudoUncheckedWeight',326 [call, {refTime: 0, proofSize: 0}],327 expectSuccess,328 options,329 );330331 if(result.status === 'Fail') return result;332333 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;334 if(data.isErr) {335 if(data.asErr.isModule) {336 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;337 const metaError = super.getApi()?.registry.findMetaError(error);338 throw new Error(`${metaError.section}.${metaError.name}`);339 } else if(data.asErr.isToken) {340 throw new Error(`Token: ${data.asErr.asToken}`);341 }342 // May be [object Object] in case of unhandled non-unit enum343 throw new Error(`Misc: ${data.asErr.toHuman()}`);344 }345 return result;346 }347 };348}349350class SchedulerGroup extends HelperGroup<UniqueHelper> {351 constructor(helper: UniqueHelper) {352 super(helper);353 }354355 cancelScheduled(signer: TSigner, scheduledId: string) {356 return this.helper.executeExtrinsic(357 signer,358 'api.tx.scheduler.cancelNamed',359 [scheduledId],360 true,361 );362 }363364 changePriority(signer: TSigner, scheduledId: string, priority: number) {365 return this.helper.executeExtrinsic(366 signer,367 'api.tx.scheduler.changeNamedPriority',368 [scheduledId, priority],369 true,370 );371 }372373 scheduleAt<T extends DevUniqueHelper>(374 executionBlockNumber: number,375 options: ISchedulerOptions = {},376 ) {377 return this.schedule<T>('schedule', executionBlockNumber, options);378 }379380 scheduleAfter<T extends DevUniqueHelper>(381 blocksBeforeExecution: number,382 options: ISchedulerOptions = {},383 ) {384 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);385 }386387 schedule<T extends UniqueHelper>(388 scheduleFn: 'schedule' | 'scheduleAfter',389 blocksNum: number,390 options: ISchedulerOptions = {},391 ) {392 // eslint-disable-next-line @typescript-eslint/naming-convention393 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);394 return this.helper.clone(ScheduledHelperType, {395 scheduleFn,396 blocksNum,397 options,398 }) as T;399 }400}401402class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {403 //todo:collator documentation404 addInvulnerable(signer: TSigner, address: string) {405 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);406 }407408 removeInvulnerable(signer: TSigner, address: string) {409 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);410 }411412 async getInvulnerables(): Promise<string[]> {413 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());414 }415416 /** and also total max invulnerables */417 maxCollators(): number {418 return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);419 }420421 async getDesiredCollators(): Promise<number> {422 return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();423 }424425 setLicenseBond(signer: TSigner, amount: bigint) {426 return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);427 }428429 async getLicenseBond(): Promise<bigint> {430 return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();431 }432433 obtainLicense(signer: TSigner) {434 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);435 }436437 releaseLicense(signer: TSigner) {438 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);439 }440441 forceReleaseLicense(signer: TSigner, released: string) {442 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);443 }444445 async hasLicense(address: string): Promise<bigint> {446 return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();447 }448449 onboard(signer: TSigner) {450 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);451 }452453 offboard(signer: TSigner) {454 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);455 }456457 async getCandidates(): Promise<string[]> {458 return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());459 }460}461462export class DevUniqueHelper extends UniqueHelper {463 /**464 * Arrange methods for tests465 */466 arrange: ArrangeGroup;467 wait: WaitGroup;468 admin: AdminGroup;469 session: SessionGroup;470 testUtils: TestUtilGroup;471 foreignAssets: ForeignAssetsGroup;472 xcm: XcmGroup<DevUniqueHelper>;473 xTokens: XTokensGroup<DevUniqueHelper>;474 tokens: TokensGroup<DevUniqueHelper>;475 scheduler: SchedulerGroup;476 collatorSelection: CollatorSelectionGroup;477 council: ICollectiveGroup;478 technicalCommittee: ICollectiveGroup;479 fellowship: IFellowshipGroup;480 democracy: DemocracyGroup;481482 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {483 options.helperBase = options.helperBase ?? DevUniqueHelper;484485 super(logger, options);486 this.arrange = new ArrangeGroup(this);487 this.wait = new WaitGroup(this);488 this.admin = new AdminGroup(this);489 this.testUtils = new TestUtilGroup(this);490 this.session = new SessionGroup(this);491 this.foreignAssets = new ForeignAssetsGroup(this);492 this.xcm = new XcmGroup(this, 'polkadotXcm');493 this.xTokens = new XTokensGroup(this);494 this.tokens = new TokensGroup(this);495 this.scheduler = new SchedulerGroup(this);496 this.collatorSelection = new CollatorSelectionGroup(this);497 this.council = {498 collective: new CollectiveGroup(this, 'council'),499 membership: new CollectiveMembershipGroup(this, 'councilMembership'),500 };501 this.technicalCommittee = {502 collective: new CollectiveGroup(this, 'technicalCommittee'),503 membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),504 };505 this.fellowship = {506 collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),507 referenda: new ReferendaGroup(this, 'fellowshipReferenda'),508 };509 this.democracy = new DemocracyGroup(this);510 }511512 override async connect(wsEndpoint: string, _listeners?: any): Promise<void> {513 if(!wsEndpoint) throw new Error('wsEndpoint was not set');514 const wsProvider = new WsProvider(wsEndpoint);515 this.api = new ApiPromise({516 provider: wsProvider,517 signedExtensions: {518 ContractHelpers: {519 extrinsic: {},520 payload: {},521 },522 CheckMaintenance: {523 extrinsic: {},524 payload: {},525 },526 DisableIdentityCalls: {527 extrinsic: {},528 payload: {},529 },530 FakeTransactionFinalizer: {531 extrinsic: {},532 payload: {},533 },534 },535 rpc: {536 unique: defs.unique.rpc,537 appPromotion: defs.appPromotion.rpc,538 povinfo: defs.povinfo.rpc,539 eth: {540 feeHistory: {541 description: 'Dummy',542 params: [],543 type: 'u8',544 },545 maxPriorityFeePerGas: {546 description: 'Dummy',547 params: [],548 type: 'u8',549 },550 },551 },552 });553 await this.api.isReadyOrError;554 this.network = await UniqueHelper.detectNetwork(this.api);555 this.wsEndpoint = wsEndpoint;556 }557 getSudo<T extends DevUniqueHelper>() {558 // eslint-disable-next-line @typescript-eslint/naming-convention559 const SudoHelperType = SudoHelper(this.helperBase);560 return this.clone(SudoHelperType) as T;561 }562}563564export class DevRelayHelper extends RelayHelper {565 wait: WaitGroup;566567 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {568 options.helperBase = options.helperBase ?? DevRelayHelper;569570 super(logger, options);571 this.wait = new WaitGroup(this);572 }573574 getSudo() {575 // eslint-disable-next-line @typescript-eslint/naming-convention576 const SudoHelperType = SudoHelper(this.helperBase);577 return this.clone(SudoHelperType) as DevRelayHelper;578 }579}580581export class DevWestmintHelper extends WestmintHelper {582 wait: WaitGroup;583584 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {585 options.helperBase = options.helperBase ?? DevWestmintHelper;586587 super(logger, options);588 this.wait = new WaitGroup(this);589 }590}591592export class DevStatemineHelper extends DevWestmintHelper {}593594export class DevStatemintHelper extends DevWestmintHelper {}595596export class DevMoonbeamHelper extends MoonbeamHelper {597 account: MoonbeamAccountGroup;598 wait: WaitGroup;599 fastDemocracy: MoonbeamFastDemocracyGroup;600601 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {602 options.helperBase = options.helperBase ?? DevMoonbeamHelper;603 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';604605 super(logger, options);606 this.account = new MoonbeamAccountGroup(this);607 this.wait = new WaitGroup(this);608 this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);609 }610}611612export class DevMoonriverHelper extends DevMoonbeamHelper {613 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {614 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';615 super(logger, options);616 }617}618619export class DevAstarHelper extends AstarHelper {620 wait: WaitGroup;621622 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {623 options.helperBase = options.helperBase ?? DevAstarHelper;624625 super(logger, options);626 this.wait = new WaitGroup(this);627 }628629 getSudo<T extends AstarHelper>() {630 // eslint-disable-next-line @typescript-eslint/naming-convention631 const SudoHelperType = SudoHelper(this.helperBase);632 return this.clone(SudoHelperType) as T;633 }634}635636export class DevShidenHelper extends DevAstarHelper { }637638export class DevAcalaHelper extends AcalaHelper {639 wait: WaitGroup;640641 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {642 options.helperBase = options.helperBase ?? DevAcalaHelper;643644 super(logger, options);645 this.wait = new WaitGroup(this);646 }647 getSudo() {648 // eslint-disable-next-line @typescript-eslint/naming-convention649 const SudoHelperType = SudoHelper(this.helperBase);650 return this.clone(SudoHelperType) as DevAcalaHelper;651 }652}653654export class DevPolkadexHelper extends PolkadexHelper {655 wait: WaitGroup;656 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {657 options.helperBase = options.helperBase ?? PolkadexHelper;658659 super(logger, options);660 this.wait = new WaitGroup(this);661 }662663 getSudo() {664 // eslint-disable-next-line @typescript-eslint/naming-convention665 const SudoHelperType = SudoHelper(this.helperBase);666 return this.clone(SudoHelperType) as DevPolkadexHelper;667 }668}669670export class DevHydraDxHelper extends HydraDxHelper {671 wait: WaitGroup;672 fastDemocracy: HydraFastDemocracyGroup;673674 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {675 options.helperBase = options.helperBase ?? DevHydraDxHelper;676677 super(logger, options);678679 this.wait = new WaitGroup(this);680 this.fastDemocracy = new HydraFastDemocracyGroup(this);681 }682}683684export class DevKaruraHelper extends DevAcalaHelper {}685686export class ArrangeGroup {687 helper: DevUniqueHelper;688689 scheduledIdSlider = 0;690691 constructor(helper: DevUniqueHelper) {692 this.helper = helper;693 }694695 /**696 * Generates accounts with the specified UNQ token balance697 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.698 * @param donor donor account for balances699 * @returns array of newly created accounts700 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);701 */702 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {703 let nonce = await this.helper.chain.getNonce(donor.address);704 const wait = new WaitGroup(this.helper);705 const ss58Format = this.helper.chain.getChainProperties().ss58Format;706 const tokenNominal = this.helper.balance.getOneTokenNominal();707 const transactions = [];708 const accounts: IKeyringPair[] = [];709 for(const balance of balances) {710 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);711 accounts.push(recipient);712 if(balance !== 0n) {713 const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, balance * tokenNominal]);714 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));715 nonce++;716 }717 }718719 await Promise.all(transactions).catch(_e => {});720721 //#region TODO remove this region, when nonce problem will be solved722 const checkBalances = async () => {723 let isSuccess = true;724 for(let i = 0; i < balances.length; i++) {725 const balance = await this.helper.balance.getSubstrate(accounts[i].address);726 if(balance !== balances[i] * tokenNominal) {727 isSuccess = false;728 break;729 }730 }731 return isSuccess;732 };733734 let accountsCreated = false;735 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;736 // checkBalances retry up to 5-50 blocks737 for(let index = 0; index < maxBlocksChecked; index++) {738 accountsCreated = await checkBalances();739 if(accountsCreated) break;740 await wait.newBlocks(1);741 }742743 if(!accountsCreated) throw Error('Accounts generation failed');744 //#endregion745746 return accounts;747 };748749 // TODO combine this method and createAccounts into one750 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {751 const createAsManyAsCan = async () => {752 let transactions: any = [];753 const accounts: IKeyringPair[] = [];754 let nonce = await this.helper.chain.getNonce(donor.address);755 const tokenNominal = this.helper.balance.getOneTokenNominal();756 const ss58Format = this.helper.chain.getChainProperties().ss58Format;757 for(let i = 0; i < accountsToCreate; i++) {758 if(i === 500) { // if there are too many accounts to create759 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled760 transactions = []; //761 nonce = await this.helper.chain.getNonce(donor.address); // update nonce762 }763 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);764 accounts.push(recipient);765 if(withBalance !== 0n) {766 const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, withBalance * tokenNominal]);767 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));768 nonce++;769 }770 }771772 const fullfilledAccounts = [];773 await Promise.allSettled(transactions);774 for(const account of accounts) {775 const accountBalance = await this.helper.balance.getSubstrate(account.address);776 if(accountBalance === withBalance * tokenNominal) {777 fullfilledAccounts.push(account);778 }779 }780 return fullfilledAccounts;781 };782783784 const crowd: IKeyringPair[] = [];785 // do up to 5 retries786 for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {787 const asManyAsCan = await createAsManyAsCan();788 crowd.push(...asManyAsCan);789 accountsToCreate -= asManyAsCan.length;790 }791792 if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);793794 return crowd;795 };796797 /**798 * Generates one account with zero balance799 * @returns the newly generated account800 * @example const account = await helper.arrange.createEmptyAccount();801 */802 createEmptyAccount = (): IKeyringPair => {803 const ss58Format = this.helper.chain.getChainProperties().ss58Format;804 return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);805 };806807 isDevNode = async () => {808 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();809 if(blockNumber == 0) {810 await this.helper.wait.newBlocks(1);811 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();812 }813 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);814 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);815 const findCreationDate = (block: any) => {816 const humanBlock = block.toHuman();817 let date;818 humanBlock.block.extrinsics.forEach((ext: any) => {819 if(ext.method.section === 'timestamp') {820 date = Number(ext.method.args.now.replaceAll(',', ''));821 }822 });823 return date;824 };825 const block1date = await findCreationDate(block1);826 const block2date = await findCreationDate(block2);827 if(block2date! - block1date! < 9000) return true;828 return false;829 };830831 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {832 const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);833 let balance = await this.helper.balance.getSubstrate(address);834835 await promise();836837 balance -= await this.helper.balance.getSubstrate(address);838839 return balance;840 }841842 async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {843 const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);844845 const kvJson: {[key: string]: string} = {};846847 for(const kv of rawPovInfo.keyValues) {848 kvJson[kv.key.toHex()] = kv.value.toHex();849 }850851 const kvStr = JSON.stringify(kvJson);852853 const chainql = spawnSync(854 'chainql',855 [856 `--tla-code=data=${kvStr}`,857 '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,858 ],859 );860861 if(!chainql.stdout) {862 throw Error('unable to get an output from the `chainql`');863 }864865 return {866 proofSize: rawPovInfo.proofSize.toNumber(),867 compactProofSize: rawPovInfo.compactProofSize.toNumber(),868 compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),869 results: rawPovInfo.results,870 kv: JSON.parse(chainql.stdout.toString()),871 };872 }873874 calculatePalletAddress(palletId: any) {875 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));876 return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);877 }878879 makeScheduledIds(num: number): string[] {880 function makeId(slider: number) {881 const scheduledIdSize = 64;882 const hexId = slider.toString(16);883 const prefixSize = scheduledIdSize - hexId.length;884885 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;886887 return scheduledId;888 }889890 const ids = [];891 for(let i = 0; i < num; i++) {892 ids.push(makeId(this.scheduledIdSlider));893 this.scheduledIdSlider += 1;894 }895896 return ids;897 }898899 makeScheduledId(): string {900 return (this.makeScheduledIds(1))[0];901 }902903 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {904 const capture = new EventCapture(this.helper, eventSection, eventMethod);905 await capture.startCapture();906907 return capture;908 }909910 makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {911 return {912 V2: [913 {914 WithdrawAsset: [915 {916 id,917 fun: {918 Fungible: amount,919 },920 },921 ],922 },923 {924 BuyExecution: {925 fees: {926 id,927 fun: {928 Fungible: amount,929 },930 },931 weightLimit: 'Unlimited',932 },933 },934 {935 DepositAsset: {936 assets: {937 Wild: 'All',938 },939 maxAssets: 1,940 beneficiary: {941 parents: 0,942 interior: {943 X1: {944 AccountId32: {945 network: 'Any',946 id: beneficiary,947 },948 },949 },950 },951 },952 },953 ],954 };955 }956957 makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {958 return {959 V2: [960 {961 ReserveAssetDeposited: [962 {963 id,964 fun: {965 Fungible: amount,966 },967 },968 ],969 },970 {971 BuyExecution: {972 fees: {973 id,974 fun: {975 Fungible: amount,976 },977 },978 weightLimit: 'Unlimited',979 },980 },981 {982 DepositAsset: {983 assets: {984 Wild: 'All',985 },986 maxAssets: 1,987 beneficiary: {988 parents: 0,989 interior: {990 X1: {991 AccountId32: {992 network: 'Any',993 id: beneficiary,994 },995 },996 },997 },998 },999 },1000 ],1001 };1002 }10031004 makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) {1005 return {1006 V3: [1007 {1008 UnpaidExecution: {1009 weightLimit: 'Unlimited',1010 checkOrigin: null,1011 },1012 },1013 {1014 Transact: {1015 originKind: 'Superuser',1016 requireWeightAtMost: {1017 refTime: info.weightMultiplier * 200000000,1018 proofSize: info.weightMultiplier * 3000,1019 },1020 call: {1021 encoded: info.call,1022 },1023 },1024 },1025 ],1026 };1027 }1028}10291030class MoonbeamAccountGroup {1031 helper: MoonbeamHelper;10321033 keyring: Keyring;1034 _alithAccount: IKeyringPair;1035 _baltatharAccount: IKeyringPair;1036 _dorothyAccount: IKeyringPair;10371038 constructor(helper: MoonbeamHelper) {1039 this.helper = helper;10401041 this.keyring = new Keyring({type: 'ethereum'});1042 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';1043 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';1044 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';10451046 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');1047 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');1048 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');1049 }10501051 alithAccount() {1052 return this._alithAccount;1053 }10541055 baltatharAccount() {1056 return this._baltatharAccount;1057 }10581059 dorothyAccount() {1060 return this._dorothyAccount;1061 }10621063 create() {1064 return this.keyring.addFromUri(mnemonicGenerate());1065 }1066}10671068class MoonbeamFastDemocracyGroup {1069 helper: DevMoonbeamHelper;10701071 constructor(helper: DevMoonbeamHelper) {1072 this.helper = helper;1073 }10741075 async executeProposal(proposalDesciption: string, encodedProposal: string) {1076 const proposalHash = blake2AsHex(encodedProposal);10771078 const alithAccount = this.helper.account.alithAccount();1079 const baltatharAccount = this.helper.account.baltatharAccount();1080 const dorothyAccount = this.helper.account.dorothyAccount();10811082 const councilVotingThreshold = 2;1083 const technicalCommitteeThreshold = 2;1084 const fastTrackVotingPeriod = 3;1085 const fastTrackDelayPeriod = 0;10861087 console.log(`[democracy] executing '${proposalDesciption}' proposal`);10881089 // >>> Propose external motion through council >>>1090 console.log('\t* Propose external motion through council.......');1091 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});1092 const encodedMotion = externalMotion?.method.toHex() || '';1093 const motionHash = blake2AsHex(encodedMotion);1094 console.log('\t* Motion hash is %s', motionHash);10951096 await this.helper.collective.council.propose(1097 baltatharAccount,1098 councilVotingThreshold,1099 externalMotion,1100 externalMotion.encodedLength,1101 );11021103 const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;1104 await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);1105 await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);11061107 await this.helper.collective.council.close(1108 dorothyAccount,1109 motionHash,1110 councilProposalIdx,1111 {1112 refTime: 1_000_000_000,1113 proofSize: 1_000_000,1114 },1115 externalMotion.encodedLength,1116 );1117 console.log('\t* Propose external motion through council.......DONE');1118 // <<< Propose external motion through council <<<11191120 // >>> Fast track proposal through technical committee >>>1121 console.log('\t* Fast track proposal through technical committee.......');1122 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);1123 const encodedFastTrack = fastTrack?.method.toHex() || '';1124 const fastTrackHash = blake2AsHex(encodedFastTrack);1125 console.log('\t* FastTrack hash is %s', fastTrackHash);11261127 await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);11281129 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;1130 await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);1131 await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);11321133 await this.helper.collective.techCommittee.close(1134 baltatharAccount,1135 fastTrackHash,1136 techProposalIdx,1137 {1138 refTime: 1_000_000_000,1139 proofSize: 1_000_000,1140 },1141 fastTrack.encodedLength,1142 );1143 console.log('\t* Fast track proposal through technical committee.......DONE');1144 // <<< Fast track proposal through technical committee <<<11451146 const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);1147 const referendumIndex = democracyStarted.referendumIndex;11481149 // >>> Referendum voting >>>1150 console.log(`\t* Referendum #${referendumIndex} voting.......`);1151 await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {1152 balance: 10_000_000_000_000_000_000n,1153 vote: {aye: true, conviction: 1},1154 });1155 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);1156 // <<< Referendum voting <<<11571158 // Wait the proposal to pass1159 await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);11601161 await this.helper.wait.newBlocks(1);11621163 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);1164 }1165}11661167class HydraFastDemocracyGroup {1168 helper: DevHydraDxHelper;11691170 constructor(helper: DevHydraDxHelper) {1171 this.helper = helper;1172 }11731174 async executeProposal(proposalDesciption: string, encodedProposal: string) {1175 const proposalHash = blake2AsHex(encodedProposal);1176 const aliceAccount = this.helper.util.fromSeed('//Alice');1177 const bobAccount = this.helper.util.fromSeed('//Bob');1178 const eveAccount = this.helper.util.fromSeed('//Eve');11791180 const councilVotingThreshold = 1;1181 const technicalCommitteeThreshold = 3;1182 const fastTrackVotingPeriod = 3;1183 const fastTrackDelayPeriod = 0;11841185 console.log(`[democracy] executing '${proposalDesciption}' proposal`);11861187 // >>> Propose external motion through council >>>1188 console.log('\t* Propose external motion through council.......');1189 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});1190 const encodedMotion = externalMotion?.method.toHex() || '';1191 const motionHash = blake2AsHex(encodedMotion);1192 console.log('\t* Motion hash is %s', motionHash);11931194 await this.helper.collective.council.propose(1195 aliceAccount,1196 councilVotingThreshold,1197 externalMotion,1198 externalMotion.encodedLength,1199 );12001201 // const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;1202 // await this.helper.collective.council.vote(aliceAccount, motionHash, councilProposalIdx, true);120312041205 // await this.helper.collective.council.close(1206 // aliceAccount,1207 // motionHash,1208 // councilProposalIdx,1209 // {1210 // refTime: 1_000_000_000,1211 // proofSize: 1_000_000,1212 // },1213 // externalMotion.encodedLength,1214 // );1215 console.log('\t* Propose external motion through council.......DONE');1216 // <<< Propose external motion through council <<<12171218 // >>> Fast track proposal through technical committee >>>1219 console.log('\t* Fast track proposal through technical committee.......');1220 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);1221 const encodedFastTrack = fastTrack?.method.toHex() || '';1222 const fastTrackHash = blake2AsHex(encodedFastTrack);1223 console.log('\t* FastTrack hash is %s', fastTrackHash);12241225 await this.helper.collective.techCommittee.propose(aliceAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);12261227 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;1228 await this.helper.collective.techCommittee.vote(aliceAccount, fastTrackHash, techProposalIdx, true);1229 await this.helper.collective.techCommittee.vote(bobAccount, fastTrackHash, techProposalIdx, true);1230 await this.helper.collective.techCommittee.vote(eveAccount, fastTrackHash, techProposalIdx, true);12311232 await this.helper.collective.techCommittee.close(1233 bobAccount,1234 fastTrackHash,1235 techProposalIdx,1236 {1237 refTime: 1_000_000_000,1238 proofSize: 1_000_000,1239 },1240 fastTrack.encodedLength,1241 );1242 console.log('\t* Fast track proposal through technical committee.......DONE');1243 // <<< Fast track proposal through technical committee <<<12441245 const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);1246 const referendumIndex = democracyStarted.referendumIndex;12471248 // >>> Referendum voting >>>1249 console.log(`\t* Referendum #${referendumIndex} voting.......`);1250 await this.helper.democracy.referendumVote(eveAccount, referendumIndex, {1251 balance: 10_000_000_000_000_000_000n,1252 vote: {aye: true, conviction: 1},1253 });1254 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);1255 // <<< Referendum voting <<<12561257 // Wait the proposal to pass1258 await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);12591260 await this.helper.wait.newBlocks(1);12611262 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);1263 }1264}12651266class WaitGroup {1267 helper: ChainHelperBase;12681269 constructor(helper: ChainHelperBase) {1270 this.helper = helper;1271 }12721273 sleep(milliseconds: number) {1274 return new Promise((resolve) => setTimeout(resolve, milliseconds));1275 }12761277 private async waitWithTimeout(promise: Promise<any>, timeout: number) {1278 let isBlock = false;1279 promise.then(() => isBlock = true).catch(() => isBlock = true);1280 let totalTime = 0;1281 const step = 100;1282 while(!isBlock) {1283 await this.sleep(step);1284 totalTime += step;1285 if(totalTime >= timeout) throw Error('Blocks production failed');1286 }1287 return promise;1288 }12891290 /**1291 * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.1292 * @param promise async operation to race against the timeout1293 * @param timeoutMS time after which to time out1294 * @param timeoutError error message to throw1295 * @returns promise of the same type the operation had1296 */1297 withTimeout<T>(1298 promise: Promise<T>,1299 timeoutMS = 30000,1300 timeoutError = 'The operation has timed out!',1301 ): Promise<T> {1302 const timeout = new Promise<never>((_, reject) => {1303 setTimeout(() => {1304 reject(new Error(timeoutError));1305 }, timeoutMS);1306 });13071308 return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});1309 }13101311 /**1312 * Wait for specified number of blocks1313 * @param blocksCount number of blocks to wait1314 * @returns1315 */1316 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {1317 timeout = timeout ?? blocksCount * 60_000;1318 // eslint-disable-next-line no-async-promise-executor1319 const promise = new Promise<void>(async (resolve) => {1320 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {1321 if(blocksCount > 0) {1322 blocksCount--;1323 } else {1324 unsubscribe();1325 resolve();1326 }1327 });1328 });1329 await this.waitWithTimeout(promise, timeout);1330 return promise;1331 }13321333 /**1334 * Wait for the specified number of sessions to pass.1335 * Only applicable if the Session pallet is turned on.1336 * @param sessionCount number of sessions to wait1337 * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks1338 * @returns1339 */1340 async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {1341 console.log(`Waiting for ${sessionCount} new session${sessionCount>1's'''}.`1342 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');13431344 const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;1345 let currentSessionIndex = -1;13461347 while(currentSessionIndex < expectedSessionIndex) {1348 // eslint-disable-next-line no-async-promise-executor1349 currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {1350 await this.newBlocks(1);1351 const res = await (this.helper as DevUniqueHelper).session.getIndex();1352 resolve(res);1353 }), blockTimeout, 'The chain has stopped producing blocks!');1354 }1355 }13561357 async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {1358 timeout = timeout ?? 30 * 60 * 1000;1359 // eslint-disable-next-line no-async-promise-executor1360 const promise = new Promise<void>(async (resolve) => {1361 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1362 if(data.number.toNumber() >= blockNumber) {1363 unsubscribe();1364 resolve();1365 }1366 });1367 });1368 await this.waitWithTimeout(promise, timeout);1369 return promise;1370 }13711372 async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {1373 timeout = timeout ?? 30 * 60 * 1000;1374 // eslint-disable-next-line no-async-promise-executor1375 const promise = new Promise<void>(async (resolve) => {1376 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {1377 if(data.value.relayParentNumber.toNumber() >= blockNumber) {1378 // @ts-ignore1379 unsubscribe();1380 resolve();1381 }1382 });1383 });1384 await this.waitWithTimeout(promise, timeout);1385 return promise;1386 }13871388 noScheduledTasks() {1389 const api = this.helper.getApi();13901391 // eslint-disable-next-line no-async-promise-executor1392 const promise = new Promise<void>(async resolve => {1393 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1394 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();13951396 if(areThereScheduledTasks.length == 0) {1397 unsubscribe();1398 resolve();1399 }1400 });1401 });14021403 return promise;1404 }14051406 parachainBlockMultiplesOf(val: bigint) {1407 // eslint-disable-next-line no-async-promise-executor1408 const promise = new Promise<void>(async resolve => {1409 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1410 if(data.number.toBigInt() % val == 0n) {1411 console.log(`from waiter: ${data.number.toBigInt()}`);1412 unsubscribe();1413 resolve();1414 }1415 });1416 });1417 return promise;1418 }14191420 event<T extends IEventHelper>(1421 maxBlocksToWait: number,1422 eventHelper: T,1423 filter: (_: any) => boolean = () => true,1424 ): any {1425 // eslint-disable-next-line no-async-promise-executor1426 const promise = new Promise<T | null>(async (resolve) => {1427 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1428 const blockNumber = header.number.toJSON();1429 const blockHash = header.hash;1430 const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1431 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;14321433 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);14341435 const apiAt = await this.helper.getApi().at(blockHash);1436 const eventRecords = (await apiAt.query.system.events()) as any;14371438 const neededEvent = eventRecords.toArray()1439 .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1440 .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1441 .find(filter);14421443 if(neededEvent) {1444 unsubscribe();1445 resolve(neededEvent);1446 } else if(maxBlocksToWait > 0) {1447 maxBlocksToWait--;1448 } else {1449 this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found.1450 The wait lasted until block ${blockNumber} inclusive`);1451 unsubscribe();1452 resolve(null);1453 }1454 });1455 });1456 return promise;1457 }14581459 async expectEvent<T extends IEventHelper>(1460 maxBlocksToWait: number,1461 eventHelper: T,1462 filter: (e: any) => boolean = () => true,1463 ) {1464 const e = await this.event(maxBlocksToWait, eventHelper, filter);1465 if(e == null) {1466 throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1467 } else {1468 return e;1469 }1470 }1471}14721473class SessionGroup {1474 helper: ChainHelperBase;14751476 constructor(helper: ChainHelperBase) {1477 this.helper = helper;1478 }14791480 //todo:collator documentation1481 async getIndex(): Promise<number> {1482 return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1483 }14841485 newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1486 return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1487 }14881489 setOwnKeys(signer: TSigner, key: string) {1490 return this.helper.executeExtrinsic(1491 signer,1492 'api.tx.session.setKeys',1493 [key, '0x0'],1494 true,1495 );1496 }14971498 setOwnKeysFromAddress(signer: TSigner) {1499 return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1500 }1501}15021503class TestUtilGroup {1504 helper: DevUniqueHelper;15051506 constructor(helper: DevUniqueHelper) {1507 this.helper = helper;1508 }15091510 async enable(testUtilsPalletName: string) {1511 if(this.helper.fetchMissingPalletNames([testUtilsPalletName]).length != 0) {1512 return;1513 }15141515 const signer = this.helper.util.fromSeed('//Alice');1516 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1517 }15181519 async setTestValue(signer: TSigner, testVal: number) {1520 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1521 }15221523 async incTestValue(signer: TSigner) {1524 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1525 }15261527 async setTestValueAndRollback(signer: TSigner, testVal: number) {1528 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1529 }15301531 async testValue(blockIdx?: number) {1532 const api = blockIdx1533 ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1534 : this.helper.getApi();15351536 return (await api.query.testUtils.testValue()).toJSON();1537 }15381539 async justTakeFee(signer: TSigner) {1540 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1541 }15421543 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1544 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1545 }1546}15471548class EventCapture {1549 helper: DevUniqueHelper;1550 eventSection: string;1551 eventMethod: string;1552 events: EventRecord[] = [];1553 unsubscribe: VoidFn | null = null;15541555 constructor(1556 helper: DevUniqueHelper,1557 eventSection: string,1558 eventMethod: string,1559 ) {1560 this.helper = helper;1561 this.eventSection = eventSection;1562 this.eventMethod = eventMethod;1563 }15641565 async startCapture() {1566 this.stopCapture();1567 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1568 const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);15691570 this.events.push(...newEvents);1571 })) as any;1572 }15731574 stopCapture() {1575 if(this.unsubscribe !== null) {1576 this.unsubscribe();1577 }1578 }15791580 extractCapturedEvents() {1581 return this.events;1582 }1583}15841585class AdminGroup {1586 helper: UniqueHelper;15871588 constructor(helper: UniqueHelper) {1589 this.helper = helper;1590 }15911592 async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> {1593 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1594 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1595 staker: e.event.data[0].toString(),1596 stake: e.event.data[1].toBigInt(),1597 payout: e.event.data[2].toBigInt(),1598 }));1599 }1600}16011602// eslint-disable-next-line @typescript-eslint/naming-convention1603function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {1604 return class extends Base {1605 scheduleFn: 'schedule' | 'scheduleAfter';1606 blocksNum: number;1607 options: ISchedulerOptions;16081609 constructor(...args: any[]) {1610 const logger = args[0] as ILogger;1611 const options = args[1] as {1612 scheduleFn: 'schedule' | 'scheduleAfter',1613 blocksNum: number,1614 options: ISchedulerOptions1615 };16161617 super(logger);16181619 this.scheduleFn = options.scheduleFn;1620 this.blocksNum = options.blocksNum;1621 this.options = options.options;1622 }16231624 override executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {1625 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);16261627 const mandatorySchedArgs = [1628 this.blocksNum,1629 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,1630 this.options.priority ?? null,1631 scheduledTx,1632 ];16331634 let schedArgs;1635 let scheduleFn;16361637 if(this.options.scheduledId) {1638 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];16391640 if(this.scheduleFn == 'schedule') {1641 scheduleFn = 'scheduleNamed';1642 } else if(this.scheduleFn == 'scheduleAfter') {1643 scheduleFn = 'scheduleNamedAfter';1644 }1645 } else {1646 schedArgs = mandatorySchedArgs;1647 scheduleFn = this.scheduleFn;1648 }16491650 const extrinsic = 'api.tx.scheduler.' + scheduleFn;16511652 return super.executeExtrinsic(1653 sender,1654 extrinsic as any,1655 schedArgs,1656 expectSuccess,1657 );1658 }1659 };1660}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.tsdiffbeforeafterboth--- a/js-packages/tests/xcm/xcm.types.ts
+++ b/js-packages/tests/xcm/xcm.types.ts
@@ -1,6 +1,6 @@
import type {IKeyringPair} from '@polkadot/types/types';
import {hexToString} from '@polkadot/util';
-import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util/index.js';
+import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingHydraDxPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util/index.js';
import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';
import config from '../config.js';
@@ -10,6 +10,7 @@
export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);
export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
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);
@@ -25,6 +26,7 @@
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;
@@ -87,6 +89,7 @@
moonriver: usingMoonriverPlaygrounds,
karura: usingKaruraPlaygrounds,
shiden: usingShidenPlaygrounds,
+ hydraDx: usingHydraDxPlaygrounds,
} as const;
type NetworkNames = keyof typeof NETWORKS;
@@ -108,6 +111,8 @@
return KARURA_CHAIN;
case 'shiden':
return SHIDEN_CHAIN;
+ case 'hydraDx':
+ return HYDRADX_CHAIN;
}
}
@@ -127,6 +132,8 @@
return karuraUrl;
case 'shiden':
return shidenUrl;
+ case 'hydraDx':
+ return hydraDxUrl;
}
}
@@ -277,7 +284,7 @@
since the hash is being checked to ensure
it matches what was sent.
*/
- if(networkName == 'polkadex') {
+ 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);