difftreelog
fix(test) remove token schedulers from tests
in: master
3 files changed
tests/src/maintenance.seqtest.tsdiffbeforeafterboth--- a/tests/src/maintenance.seqtest.ts
+++ b/tests/src/maintenance.seqtest.ts
@@ -192,19 +192,22 @@
const blocksToWait = 6;
// Scheduling works before the maintenance
- await nftBeforeMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM})
+ .nft.transferToken(bob, collection.collectionId, nftBeforeMM.tokenId, {Substrate: superuser.address});
+
await helper.wait.newBlocks(blocksToWait + 1);
expect(await nftBeforeMM.getOwner()).to.be.deep.equal({Substrate: superuser.address});
// Schedule a transaction that should occur *during* the maintenance
- await nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM})
+ .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address});
+
// Schedule a transaction that should occur *after* the maintenance
- await nftDuringMM.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM})
+ .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address});
+
await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []);
expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true;
@@ -214,16 +217,16 @@
expect(await nftDuringMM.getOwner()).to.be.deep.equal({Substrate: bob.address});
// Any attempts to schedule a tx during the MM should be rejected
- await expect(nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM})
- .transfer(bob, {Substrate: superuser.address}))
+ await expect(helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM})
+ .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address}))
.to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/);
await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []);
expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false;
// Scheduling works after the maintenance
- await nftAfterMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM})
+ .nft.transferToken(bob, collection.collectionId, nftAfterMM.tokenId, {Substrate: superuser.address});
await helper.wait.newBlocks(blocksToWait + 1);
tests/src/scheduler.seqtest.tsdiffbeforeafterboth--- a/tests/src/scheduler.seqtest.ts
+++ b/tests/src/scheduler.seqtest.ts
@@ -47,9 +47,8 @@
const token = await collection.mintToken(alice);
const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;
const blocksBeforeExecution = 4;
-
- await token.scheduleAfter(blocksBeforeExecution, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(blocksBeforeExecution, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;
expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
@@ -103,8 +102,8 @@
expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
await helper.scheduler.cancelScheduled(alice, scheduledId);
@@ -363,8 +362,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(bob, {Substrate: alice.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
await helper.getSudo().scheduler.cancelScheduled(superuser, scheduledId);
@@ -404,8 +403,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 6;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(bob, {Substrate: alice.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
const priority = 112;
@@ -583,8 +582,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId});
@@ -614,8 +613,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
await expect(helper.scheduler.cancelScheduled(bob, scheduledId))
@@ -655,8 +654,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(bob, {Substrate: alice.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
const priority = 112;
await expect(helper.scheduler.changePriority(alice, scheduledId, priority))
tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {stringToU8a} from '@polkadot/util';5import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';6import {UniqueHelper, ChainHelperBase, ChainHelperBaseConstructor, HelperGroup, UniqueHelperConstructor} from './unique';7import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';8import * as defs from '../../interfaces/definitions';9import {IKeyringPair} from '@polkadot/types/types';10import {EventRecord} from '@polkadot/types/interfaces';11import {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types';12import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';13import {SignerOptions, VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';15import {spawnSync} from 'child_process';16import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup} from './unique.xcm';17import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, ICollectiveGroup, IFellowshipGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance';1819export class SilentLogger {20 log(_msg: any, _level: any): void { }21 level = {22 ERROR: 'ERROR' as const,23 WARNING: 'WARNING' as const,24 INFO: 'INFO' as const,25 };26}2728export class SilentConsole {29 // TODO: Remove, this is temporary: Filter unneeded API output30 // (Jaco promised it will be removed in the next version)31 consoleErr: any;32 consoleLog: any;33 consoleWarn: any;3435 constructor() {36 this.consoleErr = console.error;37 this.consoleLog = console.log;38 this.consoleWarn = console.warn;39 }4041 enable() {42 const outFn = (printer: any) => (...args: any[]) => {43 for(const arg of args) {44 if(typeof arg !== 'string')45 continue;46 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'];47 const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false);48 if(needToSkip || arg === 'Normal connection closure')49 return;50 }51 printer(...args);52 };5354 console.error = outFn(this.consoleErr.bind(console));55 console.log = outFn(this.consoleLog.bind(console));56 console.warn = outFn(this.consoleWarn.bind(console));57 }5859 disable() {60 console.error = this.consoleErr;61 console.log = this.consoleLog;62 console.warn = this.consoleWarn;63 }64}6566export interface IEventHelper {67 section(): string;6869 method(): string;7071 wrapEvent(data: any[]): any;72}7374// eslint-disable-next-line @typescript-eslint/naming-convention75function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {76 const helperClass = class implements IEventHelper {77 wrapEvent: (data: any[]) => any;78 _section: string;79 _method: string;8081 constructor() {82 this.wrapEvent = wrapEvent;83 this._section = section;84 this._method = method;85 }8687 section(): string {88 return this._section;89 }9091 method(): string {92 return this._method;93 }9495 filter(txres: ITransactionResult) {96 return txres.result.events.filter(e => e.event.section === section && e.event.method === method)97 .map(e => this.wrapEvent(e.event.data));98 }99100 find(txres: ITransactionResult) {101 const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);102 return e ? this.wrapEvent(e.event.data) : null;103 }104105 expect(txres: ITransactionResult) {106 const e = this.find(txres);107 if(e) {108 return e;109 } else {110 throw Error(`Expected event ${section}.${method}`);111 }112 }113 };114115 return helperClass;116}117118function eventJsonData<T = any>(data: any[], index: number) {119 return data[index].toJSON() as T;120}121122function eventHumanData(data: any[], index: number) {123 return data[index].toHuman();124}125126function eventData<T = any>(data: any[], index: number) {127 return data[index] as T;128}129130// eslint-disable-next-line @typescript-eslint/naming-convention131function EventSection(section: string) {132 return class Section {133 static section = section;134135 static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {136 const helperClass = EventHelper(Section.section, name, wrapEvent);137 return new helperClass();138 }139 };140}141142function schedulerSection(schedulerInstance: string) {143 return class extends EventSection(schedulerInstance) {144 static Dispatched = this.Method('Dispatched', data => ({145 task: eventJsonData(data, 0),146 id: eventHumanData(data, 1),147 result: data[2],148 }));149150 static PriorityChanged = this.Method('PriorityChanged', data => ({151 task: eventJsonData(data, 0),152 priority: eventJsonData(data, 1),153 }));154 };155}156157export class Event {158 static Democracy = class extends EventSection('democracy') {159 static Proposed = this.Method('Proposed', data => ({160 proposalIndex: eventJsonData<number>(data, 0),161 }));162163 static ExternalTabled = this.Method('ExternalTabled');164165 static Started = this.Method('Started', data => ({166 referendumIndex: eventJsonData<number>(data, 0),167 threshold: eventHumanData(data, 1),168 }));169170 static Voted = this.Method('Voted', data => ({171 voter: eventJsonData(data, 0),172 referendumIndex: eventJsonData<number>(data, 1),173 vote: eventJsonData(data, 2),174 }));175176 static Passed = this.Method('Passed', data => ({177 referendumIndex: eventJsonData<number>(data, 0),178 }));179180 static ProposalCanceled = this.Method('ProposalCanceled', data => ({181 propIndex: eventJsonData<number>(data, 0),182 }));183184 static Cancelled = this.Method('Cancelled', data => ({185 propIndex: eventJsonData<number>(data, 0),186 }));187188 static Vetoed = this.Method('Vetoed', data => ({189 who: eventHumanData(data, 0),190 proposalHash: eventHumanData(data, 1),191 until: eventJsonData<number>(data, 1),192 }));193 };194195 static Council = class extends EventSection('council') {196 static Proposed = this.Method('Proposed', data => ({197 account: eventHumanData(data, 0),198 proposalIndex: eventJsonData<number>(data, 1),199 proposalHash: eventHumanData(data, 2),200 threshold: eventJsonData<number>(data, 3),201 }));202 static Closed = this.Method('Closed', data => ({203 proposalHash: eventHumanData(data, 0),204 yes: eventJsonData<number>(data, 1),205 no: eventJsonData<number>(data, 2),206 }));207 static Executed = this.Method('Executed', data => ({208 proposalHash: eventHumanData(data, 0),209 }));210 };211212 static TechnicalCommittee = class extends EventSection('technicalCommittee') {213 static Proposed = this.Method('Proposed', data => ({214 account: eventHumanData(data, 0),215 proposalIndex: eventJsonData<number>(data, 1),216 proposalHash: eventHumanData(data, 2),217 threshold: eventJsonData<number>(data, 3),218 }));219 static Closed = this.Method('Closed', data => ({220 proposalHash: eventHumanData(data, 0),221 yes: eventJsonData<number>(data, 1),222 no: eventJsonData<number>(data, 2),223 }));224 static Approved = this.Method('Approved', data => ({225 proposalHash: eventHumanData(data, 0),226 }));227 static Executed = this.Method('Executed', data => ({228 proposalHash: eventHumanData(data, 0),229 result: eventHumanData(data, 1),230 }));231 };232233 static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {234 static Submitted = this.Method('Submitted', data => ({235 referendumIndex: eventJsonData<number>(data, 0),236 trackId: eventJsonData<number>(data, 1),237 proposal: eventJsonData(data, 2),238 }));239240 static Cancelled = this.Method('Cancelled', data => ({241 index: eventJsonData<number>(data, 0),242 tally: eventJsonData(data, 1),243 }));244 };245246 static UniqueScheduler = schedulerSection('uniqueScheduler');247 static Scheduler = schedulerSection('scheduler');248249 static XcmpQueue = class extends EventSection('xcmpQueue') {250 static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({251 messageHash: eventJsonData(data, 0),252 }));253254 static Success = this.Method('Success', data => ({255 messageHash: eventJsonData(data, 0),256 }));257258 static Fail = this.Method('Fail', data => ({259 messageHash: eventJsonData(data, 0),260 outcome: eventData<XcmV2TraitsError>(data, 1),261 }));262 };263}264265// eslint-disable-next-line @typescript-eslint/naming-convention266export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {267 return class extends Base {268 constructor(...args: any[]) {269 super(...args);270 }271272 async executeExtrinsic(273 sender: IKeyringPair,274 extrinsic: string,275 params: any[],276 expectSuccess?: boolean,277 options: Partial<SignerOptions> | null = null,278 ): Promise<ITransactionResult> {279 const call = this.constructApiCall(extrinsic, params);280 const result = await super.executeExtrinsic(281 sender,282 'api.tx.sudo.sudo',283 [call],284 expectSuccess,285 options,286 );287288 if(result.status === 'Fail') return result;289290 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;291 if(data.isErr) {292 if(data.asErr.isModule) {293 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;294 const metaError = super.getApi()?.registry.findMetaError(error);295 throw new Error(`${metaError.section}.${metaError.name}`);296 } else if(data.asErr.isToken) {297 throw new Error(`Token: ${data.asErr.asToken}`);298 }299 // May be [object Object] in case of unhandled non-unit enum300 throw new Error(`Misc: ${data.asErr.toHuman()}`);301 }302 return result;303 }304 async executeExtrinsicUncheckedWeight(305 sender: IKeyringPair,306 extrinsic: string,307 params: any[],308 expectSuccess?: boolean,309 options: Partial<SignerOptions> | null = null,310 ): Promise<ITransactionResult> {311 const call = this.constructApiCall(extrinsic, params);312 const result = await super.executeExtrinsic(313 sender,314 'api.tx.sudo.sudoUncheckedWeight',315 [call, {refTime: 0, proofSize: 0}],316 expectSuccess,317 options,318 );319320 if(result.status === 'Fail') return result;321322 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;323 if(data.isErr) {324 if(data.asErr.isModule) {325 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;326 const metaError = super.getApi()?.registry.findMetaError(error);327 throw new Error(`${metaError.section}.${metaError.name}`);328 } else if(data.asErr.isToken) {329 throw new Error(`Token: ${data.asErr.asToken}`);330 }331 // May be [object Object] in case of unhandled non-unit enum332 throw new Error(`Misc: ${data.asErr.toHuman()}`);333 }334 return result;335 }336 };337}338339class SchedulerGroup extends HelperGroup<UniqueHelper> {340 constructor(helper: UniqueHelper) {341 super(helper);342 }343344 cancelScheduled(signer: TSigner, scheduledId: string) {345 return this.helper.executeExtrinsic(346 signer,347 'api.tx.scheduler.cancelNamed',348 [scheduledId],349 true,350 );351 }352353 changePriority(signer: TSigner, scheduledId: string, priority: number) {354 return this.helper.executeExtrinsic(355 signer,356 'api.tx.scheduler.changeNamedPriority',357 [scheduledId, priority],358 true,359 );360 }361362 scheduleAt<T extends DevUniqueHelper>(363 executionBlockNumber: number,364 options: ISchedulerOptions = {},365 ) {366 return this.schedule<T>('schedule', executionBlockNumber, options);367 }368369 scheduleAfter<T extends DevUniqueHelper>(370 blocksBeforeExecution: number,371 options: ISchedulerOptions = {},372 ) {373 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);374 }375376 schedule<T extends UniqueHelper>(377 scheduleFn: 'schedule' | 'scheduleAfter',378 blocksNum: number,379 options: ISchedulerOptions = {},380 ) {381 // eslint-disable-next-line @typescript-eslint/naming-convention382 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);383 return this.helper.clone(ScheduledHelperType, {384 scheduleFn,385 blocksNum,386 options,387 }) as T;388 }389}390391class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {392 //todo:collator documentation393 addInvulnerable(signer: TSigner, address: string) {394 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);395 }396397 removeInvulnerable(signer: TSigner, address: string) {398 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);399 }400401 async getInvulnerables(): Promise<string[]> {402 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());403 }404405 /** and also total max invulnerables */406 maxCollators(): number {407 return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);408 }409410 async getDesiredCollators(): Promise<number> {411 return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();412 }413414 setLicenseBond(signer: TSigner, amount: bigint) {415 return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);416 }417418 async getLicenseBond(): Promise<bigint> {419 return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();420 }421422 obtainLicense(signer: TSigner) {423 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);424 }425426 releaseLicense(signer: TSigner) {427 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);428 }429430 forceReleaseLicense(signer: TSigner, released: string) {431 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);432 }433434 async hasLicense(address: string): Promise<bigint> {435 return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();436 }437438 onboard(signer: TSigner) {439 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);440 }441442 offboard(signer: TSigner) {443 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);444 }445446 async getCandidates(): Promise<string[]> {447 return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());448 }449}450451export class DevUniqueHelper extends UniqueHelper {452 /**453 * Arrange methods for tests454 */455 arrange: ArrangeGroup;456 wait: WaitGroup;457 admin: AdminGroup;458 session: SessionGroup;459 testUtils: TestUtilGroup;460 foreignAssets: ForeignAssetsGroup;461 xcm: XcmGroup<UniqueHelper>;462 xTokens: XTokensGroup<UniqueHelper>;463 tokens: TokensGroup<UniqueHelper>;464 scheduler: SchedulerGroup;465 collatorSelection: CollatorSelectionGroup;466 council: ICollectiveGroup;467 technicalCommittee: ICollectiveGroup;468 fellowship: IFellowshipGroup;469 democracy: DemocracyGroup;470471 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {472 options.helperBase = options.helperBase ?? DevUniqueHelper;473474 super(logger, options);475 this.arrange = new ArrangeGroup(this);476 this.wait = new WaitGroup(this);477 this.admin = new AdminGroup(this);478 this.testUtils = new TestUtilGroup(this);479 this.session = new SessionGroup(this);480 this.foreignAssets = new ForeignAssetsGroup(this);481 this.xcm = new XcmGroup(this, 'polkadotXcm');482 this.xTokens = new XTokensGroup(this);483 this.tokens = new TokensGroup(this);484 this.scheduler = new SchedulerGroup(this);485 this.collatorSelection = new CollatorSelectionGroup(this);486 this.council = {487 collective: new CollectiveGroup(this, 'council'),488 membership: new CollectiveMembershipGroup(this, 'councilMembership'),489 };490 this.technicalCommittee = {491 collective: new CollectiveGroup(this, 'technicalCommittee'),492 membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),493 };494 this.fellowship = {495 collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),496 referenda: new ReferendaGroup(this, 'fellowshipReferenda'),497 };498 this.democracy = new DemocracyGroup(this);499 }500501 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {502 if(!wsEndpoint) throw new Error('wsEndpoint was not set');503 const wsProvider = new WsProvider(wsEndpoint);504 this.api = new ApiPromise({505 provider: wsProvider,506 signedExtensions: {507 ContractHelpers: {508 extrinsic: {},509 payload: {},510 },511 CheckMaintenance: {512 extrinsic: {},513 payload: {},514 },515 DisableIdentityCalls: {516 extrinsic: {},517 payload: {},518 },519 FakeTransactionFinalizer: {520 extrinsic: {},521 payload: {},522 },523 },524 rpc: {525 unique: defs.unique.rpc,526 appPromotion: defs.appPromotion.rpc,527 povinfo: defs.povinfo.rpc,528 eth: {529 feeHistory: {530 description: 'Dummy',531 params: [],532 type: 'u8',533 },534 maxPriorityFeePerGas: {535 description: 'Dummy',536 params: [],537 type: 'u8',538 },539 },540 },541 });542 await this.api.isReadyOrError;543 this.network = await UniqueHelper.detectNetwork(this.api);544 this.wsEndpoint = wsEndpoint;545 }546 getSudo<T extends DevUniqueHelper>() {547 // eslint-disable-next-line @typescript-eslint/naming-convention548 const SudoHelperType = SudoHelper(this.helperBase);549 return this.clone(SudoHelperType) as T;550 }551}552553export class DevRelayHelper extends RelayHelper {554 wait: WaitGroup;555556 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {557 options.helperBase = options.helperBase ?? DevRelayHelper;558559 super(logger, options);560 this.wait = new WaitGroup(this);561 }562}563564export class DevWestmintHelper extends WestmintHelper {565 wait: WaitGroup;566567 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {568 options.helperBase = options.helperBase ?? DevWestmintHelper;569570 super(logger, options);571 this.wait = new WaitGroup(this);572 }573}574575export class DevStatemineHelper extends DevWestmintHelper {}576577export class DevStatemintHelper extends DevWestmintHelper {}578579export class DevMoonbeamHelper extends MoonbeamHelper {580 account: MoonbeamAccountGroup;581 wait: WaitGroup;582 fastDemocracy: MoonbeamFastDemocracyGroup;583584 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {585 options.helperBase = options.helperBase ?? DevMoonbeamHelper;586 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';587588 super(logger, options);589 this.account = new MoonbeamAccountGroup(this);590 this.wait = new WaitGroup(this);591 this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);592 }593}594595export class DevMoonriverHelper extends DevMoonbeamHelper {596 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {597 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';598 super(logger, options);599 }600}601602export class DevAstarHelper extends AstarHelper {603 wait: WaitGroup;604605 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {606 options.helperBase = options.helperBase ?? DevAstarHelper;607608 super(logger, options);609 this.wait = new WaitGroup(this);610 }611612 getSudo<T extends AstarHelper>() {613 // eslint-disable-next-line @typescript-eslint/naming-convention614 const SudoHelperType = SudoHelper(this.helperBase);615 return this.clone(SudoHelperType) as T;616 }617}618619export class DevShidenHelper 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 ?? DevShidenHelper;624625 super(logger, options);626 this.wait = new WaitGroup(this);627 }628}629630export class DevAcalaHelper extends AcalaHelper {631 wait: WaitGroup;632633 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {634 options.helperBase = options.helperBase ?? DevAcalaHelper;635636 super(logger, options);637 this.wait = new WaitGroup(this);638 }639 getSudo<T extends AcalaHelper>() {640 // eslint-disable-next-line @typescript-eslint/naming-convention641 const SudoHelperType = SudoHelper(this.helperBase);642 return this.clone(SudoHelperType) as T;643 }644}645646export class DevPolkadexHelper extends PolkadexHelper {647 wait: WaitGroup;648 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {649 options.helperBase = options.helperBase ?? PolkadexHelper;650651 super(logger, options);652 this.wait = new WaitGroup(this);653 }654655 getSudo<T extends PolkadexHelper>() {656 // eslint-disable-next-line @typescript-eslint/naming-convention657 const SudoHelperType = SudoHelper(this.helperBase);658 return this.clone(SudoHelperType) as T;659 }660}661662export class DevKaruraHelper extends DevAcalaHelper {}663664export class ArrangeGroup {665 helper: DevUniqueHelper;666667 scheduledIdSlider = 0;668669 constructor(helper: DevUniqueHelper) {670 this.helper = helper;671 }672673 /**674 * Generates accounts with the specified UNQ token balance675 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.676 * @param donor donor account for balances677 * @returns array of newly created accounts678 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);679 */680 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {681 let nonce = await this.helper.chain.getNonce(donor.address);682 const wait = new WaitGroup(this.helper);683 const ss58Format = this.helper.chain.getChainProperties().ss58Format;684 const tokenNominal = this.helper.balance.getOneTokenNominal();685 const transactions = [];686 const accounts: IKeyringPair[] = [];687 for(const balance of balances) {688 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);689 accounts.push(recipient);690 if(balance !== 0n) {691 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);692 transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));693 nonce++;694 }695 }696697 await Promise.all(transactions).catch(_e => {});698699 //#region TODO remove this region, when nonce problem will be solved700 const checkBalances = async () => {701 let isSuccess = true;702 for(let i = 0; i < balances.length; i++) {703 const balance = await this.helper.balance.getSubstrate(accounts[i].address);704 if(balance !== balances[i] * tokenNominal) {705 isSuccess = false;706 break;707 }708 }709 return isSuccess;710 };711712 let accountsCreated = false;713 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;714 // checkBalances retry up to 5-50 blocks715 for(let index = 0; index < maxBlocksChecked; index++) {716 accountsCreated = await checkBalances();717 if(accountsCreated) break;718 await wait.newBlocks(1);719 }720721 if(!accountsCreated) throw Error('Accounts generation failed');722 //#endregion723724 return accounts;725 };726727 // TODO combine this method and createAccounts into one728 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {729 const createAsManyAsCan = async () => {730 let transactions: any = [];731 const accounts: IKeyringPair[] = [];732 let nonce = await this.helper.chain.getNonce(donor.address);733 const tokenNominal = this.helper.balance.getOneTokenNominal();734 const ss58Format = this.helper.chain.getChainProperties().ss58Format;735 for(let i = 0; i < accountsToCreate; i++) {736 if(i === 500) { // if there are too many accounts to create737 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled738 transactions = []; //739 nonce = await this.helper.chain.getNonce(donor.address); // update nonce740 }741 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);742 accounts.push(recipient);743 if(withBalance !== 0n) {744 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);745 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));746 nonce++;747 }748 }749750 const fullfilledAccounts = [];751 await Promise.allSettled(transactions);752 for(const account of accounts) {753 const accountBalance = await this.helper.balance.getSubstrate(account.address);754 if(accountBalance === withBalance * tokenNominal) {755 fullfilledAccounts.push(account);756 }757 }758 return fullfilledAccounts;759 };760761762 const crowd: IKeyringPair[] = [];763 // do up to 5 retries764 for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {765 const asManyAsCan = await createAsManyAsCan();766 crowd.push(...asManyAsCan);767 accountsToCreate -= asManyAsCan.length;768 }769770 if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);771772 return crowd;773 };774775 /**776 * Generates one account with zero balance777 * @returns the newly generated account778 * @example const account = await helper.arrange.createEmptyAccount();779 */780 createEmptyAccount = (): IKeyringPair => {781 const ss58Format = this.helper.chain.getChainProperties().ss58Format;782 return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);783 };784785 isDevNode = async () => {786 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();787 if(blockNumber == 0) {788 await this.helper.wait.newBlocks(1);789 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();790 }791 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);792 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);793 const findCreationDate = (block: any) => {794 const humanBlock = block.toHuman();795 let date;796 humanBlock.block.extrinsics.forEach((ext: any) => {797 if(ext.method.section === 'timestamp') {798 date = Number(ext.method.args.now.replaceAll(',', ''));799 }800 });801 return date;802 };803 const block1date = await findCreationDate(block1);804 const block2date = await findCreationDate(block2);805 if(block2date! - block1date! < 9000) return true;806 };807808 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {809 const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);810 let balance = await this.helper.balance.getSubstrate(address);811812 await promise();813814 balance -= await this.helper.balance.getSubstrate(address);815816 return balance;817 }818819 async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {820 const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);821822 const kvJson: {[key: string]: string} = {};823824 for(const kv of rawPovInfo.keyValues) {825 kvJson[kv.key.toHex()] = kv.value.toHex();826 }827828 const kvStr = JSON.stringify(kvJson);829830 const chainql = spawnSync(831 'chainql',832 [833 `--tla-code=data=${kvStr}`,834 '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,835 ],836 );837838 if(!chainql.stdout) {839 throw Error('unable to get an output from the `chainql`');840 }841842 return {843 proofSize: rawPovInfo.proofSize.toNumber(),844 compactProofSize: rawPovInfo.compactProofSize.toNumber(),845 compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),846 results: rawPovInfo.results,847 kv: JSON.parse(chainql.stdout.toString()),848 };849 }850851 calculatePalletAddress(palletId: any) {852 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));853 return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);854 }855856 makeScheduledIds(num: number): string[] {857 function makeId(slider: number) {858 const scheduledIdSize = 64;859 const hexId = slider.toString(16);860 const prefixSize = scheduledIdSize - hexId.length;861862 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;863864 return scheduledId;865 }866867 const ids = [];868 for(let i = 0; i < num; i++) {869 ids.push(makeId(this.scheduledIdSlider));870 this.scheduledIdSlider += 1;871 }872873 return ids;874 }875876 makeScheduledId(): string {877 return (this.makeScheduledIds(1))[0];878 }879880 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {881 const capture = new EventCapture(this.helper, eventSection, eventMethod);882 await capture.startCapture();883884 return capture;885 }886887 makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {888 return {889 V2: [890 {891 WithdrawAsset: [892 {893 id,894 fun: {895 Fungible: amount,896 },897 },898 ],899 },900 {901 BuyExecution: {902 fees: {903 id,904 fun: {905 Fungible: amount,906 },907 },908 weightLimit: 'Unlimited',909 },910 },911 {912 DepositAsset: {913 assets: {914 Wild: 'All',915 },916 maxAssets: 1,917 beneficiary: {918 parents: 0,919 interior: {920 X1: {921 AccountId32: {922 network: 'Any',923 id: beneficiary,924 },925 },926 },927 },928 },929 },930 ],931 };932 }933934 makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {935 return {936 V2: [937 {938 ReserveAssetDeposited: [939 {940 id,941 fun: {942 Fungible: amount,943 },944 },945 ],946 },947 {948 BuyExecution: {949 fees: {950 id,951 fun: {952 Fungible: amount,953 },954 },955 weightLimit: 'Unlimited',956 },957 },958 {959 DepositAsset: {960 assets: {961 Wild: 'All',962 },963 maxAssets: 1,964 beneficiary: {965 parents: 0,966 interior: {967 X1: {968 AccountId32: {969 network: 'Any',970 id: beneficiary,971 },972 },973 },974 },975 },976 },977 ],978 };979 }980}981982class MoonbeamAccountGroup {983 helper: MoonbeamHelper;984985 keyring: Keyring;986 _alithAccount: IKeyringPair;987 _baltatharAccount: IKeyringPair;988 _dorothyAccount: IKeyringPair;989990 constructor(helper: MoonbeamHelper) {991 this.helper = helper;992993 this.keyring = new Keyring({type: 'ethereum'});994 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';995 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';996 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';997998 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');999 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');1000 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');1001 }10021003 alithAccount() {1004 return this._alithAccount;1005 }10061007 baltatharAccount() {1008 return this._baltatharAccount;1009 }10101011 dorothyAccount() {1012 return this._dorothyAccount;1013 }10141015 create() {1016 return this.keyring.addFromUri(mnemonicGenerate());1017 }1018}10191020class MoonbeamFastDemocracyGroup {1021 helper: DevMoonbeamHelper;10221023 constructor(helper: DevMoonbeamHelper) {1024 this.helper = helper;1025 }10261027 async executeProposal(proposalDesciption: string, encodedProposal: string) {1028 const proposalHash = blake2AsHex(encodedProposal);10291030 const alithAccount = this.helper.account.alithAccount();1031 const baltatharAccount = this.helper.account.baltatharAccount();1032 const dorothyAccount = this.helper.account.dorothyAccount();10331034 const councilVotingThreshold = 2;1035 const technicalCommitteeThreshold = 2;1036 const fastTrackVotingPeriod = 3;1037 const fastTrackDelayPeriod = 0;10381039 console.log(`[democracy] executing '${proposalDesciption}' proposal`);10401041 // >>> Propose external motion through council >>>1042 console.log('\t* Propose external motion through council.......');1043 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});1044 const encodedMotion = externalMotion?.method.toHex() || '';1045 const motionHash = blake2AsHex(encodedMotion);1046 console.log('\t* Motion hash is %s', motionHash);10471048 await this.helper.collective.council.propose(1049 baltatharAccount,1050 councilVotingThreshold,1051 externalMotion,1052 externalMotion.encodedLength,1053 );10541055 const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;1056 await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);1057 await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);10581059 await this.helper.collective.council.close(1060 dorothyAccount,1061 motionHash,1062 councilProposalIdx,1063 {1064 refTime: 1_000_000_000,1065 proofSize: 1_000_000,1066 },1067 externalMotion.encodedLength,1068 );1069 console.log('\t* Propose external motion through council.......DONE');1070 // <<< Propose external motion through council <<<10711072 // >>> Fast track proposal through technical committee >>>1073 console.log('\t* Fast track proposal through technical committee.......');1074 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);1075 const encodedFastTrack = fastTrack?.method.toHex() || '';1076 const fastTrackHash = blake2AsHex(encodedFastTrack);1077 console.log('\t* FastTrack hash is %s', fastTrackHash);10781079 await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);10801081 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;1082 await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);1083 await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);10841085 await this.helper.collective.techCommittee.close(1086 baltatharAccount,1087 fastTrackHash,1088 techProposalIdx,1089 {1090 refTime: 1_000_000_000,1091 proofSize: 1_000_000,1092 },1093 fastTrack.encodedLength,1094 );1095 console.log('\t* Fast track proposal through technical committee.......DONE');1096 // <<< Fast track proposal through technical committee <<<10971098 const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);1099 const referendumIndex = democracyStarted.referendumIndex;11001101 // >>> Referendum voting >>>1102 console.log(`\t* Referendum #${referendumIndex} voting.......`);1103 await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {1104 balance: 10_000_000_000_000_000_000n,1105 vote: {aye: true, conviction: 1},1106 });1107 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);1108 // <<< Referendum voting <<<11091110 // Wait the proposal to pass1111 await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);11121113 await this.helper.wait.newBlocks(1);11141115 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);1116 }1117}11181119class WaitGroup {1120 helper: ChainHelperBase;11211122 constructor(helper: ChainHelperBase) {1123 this.helper = helper;1124 }11251126 sleep(milliseconds: number) {1127 return new Promise((resolve) => setTimeout(resolve, milliseconds));1128 }11291130 private async waitWithTimeout(promise: Promise<any>, timeout: number) {1131 let isBlock = false;1132 promise.then(() => isBlock = true).catch(() => isBlock = true);1133 let totalTime = 0;1134 const step = 100;1135 while(!isBlock) {1136 await this.sleep(step);1137 totalTime += step;1138 if(totalTime >= timeout) throw Error('Blocks production failed');1139 }1140 return promise;1141 }11421143 /**1144 * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.1145 * @param promise async operation to race against the timeout1146 * @param timeoutMS time after which to time out1147 * @param timeoutError error message to throw1148 * @returns promise of the same type the operation had1149 */1150 withTimeout<T>(1151 promise: Promise<T>,1152 timeoutMS = 30000,1153 timeoutError = 'The operation has timed out!',1154 ): Promise<T> {1155 const timeout = new Promise<never>((_, reject) => {1156 setTimeout(() => {1157 reject(new Error(timeoutError));1158 }, timeoutMS);1159 });11601161 return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});1162 }11631164 /**1165 * Wait for specified number of blocks1166 * @param blocksCount number of blocks to wait1167 * @returns1168 */1169 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {1170 timeout = timeout ?? blocksCount * 60_000;1171 // eslint-disable-next-line no-async-promise-executor1172 const promise = new Promise<void>(async (resolve) => {1173 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {1174 if(blocksCount > 0) {1175 blocksCount--;1176 } else {1177 unsubscribe();1178 resolve();1179 }1180 });1181 });1182 await this.waitWithTimeout(promise, timeout);1183 return promise;1184 }11851186 /**1187 * Wait for the specified number of sessions to pass.1188 * Only applicable if the Session pallet is turned on.1189 * @param sessionCount number of sessions to wait1190 * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks1191 * @returns1192 */1193 async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {1194 console.log(`Waiting for ${sessionCount} new session${sessionCount>1's'''}.`1195 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');11961197 const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;1198 let currentSessionIndex = -1;11991200 while(currentSessionIndex < expectedSessionIndex) {1201 // eslint-disable-next-line no-async-promise-executor1202 currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {1203 await this.newBlocks(1);1204 const res = await (this.helper as DevUniqueHelper).session.getIndex();1205 resolve(res);1206 }), blockTimeout, 'The chain has stopped producing blocks!');1207 }1208 }12091210 async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {1211 timeout = timeout ?? 30 * 60 * 1000;1212 // eslint-disable-next-line no-async-promise-executor1213 const promise = new Promise<void>(async (resolve) => {1214 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1215 if(data.number.toNumber() >= blockNumber) {1216 unsubscribe();1217 resolve();1218 }1219 });1220 });1221 await this.waitWithTimeout(promise, timeout);1222 return promise;1223 }12241225 async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {1226 timeout = timeout ?? 30 * 60 * 1000;1227 // eslint-disable-next-line no-async-promise-executor1228 const promise = new Promise<void>(async (resolve) => {1229 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {1230 if(data.value.relayParentNumber.toNumber() >= blockNumber) {1231 // @ts-ignore1232 unsubscribe();1233 resolve();1234 }1235 });1236 });1237 await this.waitWithTimeout(promise, timeout);1238 return promise;1239 }12401241 noScheduledTasks() {1242 const api = this.helper.getApi();12431244 // eslint-disable-next-line no-async-promise-executor1245 const promise = new Promise<void>(async resolve => {1246 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1247 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();12481249 if(areThereScheduledTasks.length == 0) {1250 unsubscribe();1251 resolve();1252 }1253 });1254 });12551256 return promise;1257 }12581259 parachainBlockMultiplesOf(val: bigint) {1260 // eslint-disable-next-line no-async-promise-executor1261 const promise = new Promise<void>(async resolve => {1262 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1263 if(data.number.toBigInt() % val == 0n) {1264 console.log(`from waiter: ${data.number.toBigInt()}`);1265 unsubscribe();1266 resolve();1267 }1268 });1269 });1270 return promise;1271 }12721273 event<T extends IEventHelper>(1274 maxBlocksToWait: number,1275 eventHelper: T,1276 filter: (_: any) => boolean = () => true,1277 ): any {1278 // eslint-disable-next-line no-async-promise-executor1279 const promise = new Promise<T | null>(async (resolve) => {1280 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1281 const blockNumber = header.number.toHuman();1282 const blockHash = header.hash;1283 const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1284 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;12851286 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);12871288 const apiAt = await this.helper.getApi().at(blockHash);1289 const eventRecords = (await apiAt.query.system.events()) as any;12901291 const neededEvent = eventRecords.toArray()1292 .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1293 .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1294 .find(filter);12951296 if(neededEvent) {1297 unsubscribe();1298 resolve(neededEvent);1299 } else if(maxBlocksToWait > 0) {1300 maxBlocksToWait--;1301 } else {1302 this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);1303 unsubscribe();1304 resolve(null);1305 }1306 });1307 });1308 return promise;1309 }13101311 async expectEvent<T extends IEventHelper>(1312 maxBlocksToWait: number,1313 eventHelper: T,1314 filter: (e: any) => boolean = () => true,1315 ) {1316 const e = await this.event(maxBlocksToWait, eventHelper, filter);1317 if(e == null) {1318 throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1319 } else {1320 return e;1321 }1322 }1323}13241325class SessionGroup {1326 helper: ChainHelperBase;13271328 constructor(helper: ChainHelperBase) {1329 this.helper = helper;1330 }13311332 //todo:collator documentation1333 async getIndex(): Promise<number> {1334 return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1335 }13361337 newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1338 return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1339 }13401341 setOwnKeys(signer: TSigner, key: string) {1342 return this.helper.executeExtrinsic(1343 signer,1344 'api.tx.session.setKeys',1345 [key, '0x0'],1346 true,1347 );1348 }13491350 setOwnKeysFromAddress(signer: TSigner) {1351 return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1352 }1353}13541355class TestUtilGroup {1356 helper: DevUniqueHelper;13571358 constructor(helper: DevUniqueHelper) {1359 this.helper = helper;1360 }13611362 async enable() {1363 if(this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {1364 return;1365 }13661367 const signer = this.helper.util.fromSeed('//Alice');1368 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1369 }13701371 async setTestValue(signer: TSigner, testVal: number) {1372 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1373 }13741375 async incTestValue(signer: TSigner) {1376 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1377 }13781379 async setTestValueAndRollback(signer: TSigner, testVal: number) {1380 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1381 }13821383 async testValue(blockIdx?: number) {1384 const api = blockIdx1385 ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1386 : this.helper.getApi();13871388 return (await api.query.testUtils.testValue()).toJSON();1389 }13901391 async justTakeFee(signer: TSigner) {1392 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1393 }13941395 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1396 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1397 }1398}13991400class EventCapture {1401 helper: DevUniqueHelper;1402 eventSection: string;1403 eventMethod: string;1404 events: EventRecord[] = [];1405 unsubscribe: VoidFn | null = null;14061407 constructor(1408 helper: DevUniqueHelper,1409 eventSection: string,1410 eventMethod: string,1411 ) {1412 this.helper = helper;1413 this.eventSection = eventSection;1414 this.eventMethod = eventMethod;1415 }14161417 async startCapture() {1418 this.stopCapture();1419 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1420 const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);14211422 this.events.push(...newEvents);1423 })) as any;1424 }14251426 stopCapture() {1427 if(this.unsubscribe !== null) {1428 this.unsubscribe();1429 }1430 }14311432 extractCapturedEvents() {1433 return this.events;1434 }1435}14361437class AdminGroup {1438 helper: UniqueHelper;14391440 constructor(helper: UniqueHelper) {1441 this.helper = helper;1442 }14431444 async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> {1445 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1446 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1447 staker: e.event.data[0].toString(),1448 stake: e.event.data[1].toBigInt(),1449 payout: e.event.data[2].toBigInt(),1450 }));1451 }1452}14531454// eslint-disable-next-line @typescript-eslint/naming-convention1455function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {1456 return class extends Base {1457 scheduleFn: 'schedule' | 'scheduleAfter';1458 blocksNum: number;1459 options: ISchedulerOptions;14601461 constructor(...args: any[]) {1462 const logger = args[0] as ILogger;1463 const options = args[1] as {1464 scheduleFn: 'schedule' | 'scheduleAfter',1465 blocksNum: number,1466 options: ISchedulerOptions1467 };14681469 super(logger);14701471 this.scheduleFn = options.scheduleFn;1472 this.blocksNum = options.blocksNum;1473 this.options = options.options;1474 }14751476 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {1477 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);14781479 const mandatorySchedArgs = [1480 this.blocksNum,1481 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,1482 this.options.priority ?? null,1483 scheduledTx,1484 ];14851486 let schedArgs;1487 let scheduleFn;14881489 if(this.options.scheduledId) {1490 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];14911492 if(this.scheduleFn == 'schedule') {1493 scheduleFn = 'scheduleNamed';1494 } else if(this.scheduleFn == 'scheduleAfter') {1495 scheduleFn = 'scheduleNamedAfter';1496 }1497 } else {1498 schedArgs = mandatorySchedArgs;1499 scheduleFn = this.scheduleFn;1500 }15011502 const extrinsic = 'api.tx.scheduler.' + scheduleFn;15031504 return super.executeExtrinsic(1505 sender,1506 extrinsic as any,1507 schedArgs,1508 expectSuccess,1509 );1510 }1511 };1512}