git.delta.rocks / unique-network / refs/commits / 29e3de62d300

difftreelog

source

js-packages/test-utils/index.ts53.8 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import '@unique-nft/opal-testnet-types/augment-api.js';5import '@unique-nft/opal-testnet-types/augment-types.js';6import '@unique-nft/opal-testnet-types/types-lookup.js';78import {stringToU8a} from '@polkadot/util';9import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';10import type {ChainHelperBaseConstructor, UniqueHelperConstructor} from '@unique-nft/playgrounds/unique.js';11import {UniqueHelper, ChainHelperBase, HelperGroup} from '@unique-nft/playgrounds/unique.js';12import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';13import * as defs from '@unique-nft/opal-testnet-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 '@unique-nft/playgrounds/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 './xcm/index.js';21import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './governance.js';22import type {ICollectiveGroup, IFellowshipGroup} from './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    console.log('\t* Propose external motion through council.......DONE');1202    // <<< Propose external motion through council <<<12031204    // >>> Fast track proposal through technical committee >>>1205    console.log('\t* Fast track proposal through technical committee.......');1206    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);1207    const encodedFastTrack = fastTrack?.method.toHex() || '';1208    const fastTrackHash = blake2AsHex(encodedFastTrack);1209    console.log('\t* FastTrack hash is %s', fastTrackHash);12101211    await this.helper.collective.techCommittee.propose(aliceAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);12121213    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;1214    await this.helper.collective.techCommittee.vote(aliceAccount, fastTrackHash, techProposalIdx, true);1215    await this.helper.collective.techCommittee.vote(bobAccount, fastTrackHash, techProposalIdx, true);1216    await this.helper.collective.techCommittee.vote(eveAccount, fastTrackHash, techProposalIdx, true);12171218    await this.helper.collective.techCommittee.close(1219      bobAccount,1220      fastTrackHash,1221      techProposalIdx,1222      {1223        refTime: 1_000_000_000,1224        proofSize: 1_000_000,1225      },1226      fastTrack.encodedLength,1227    );1228    console.log('\t* Fast track proposal through technical committee.......DONE');1229    // <<< Fast track proposal through technical committee <<<12301231    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);1232    const referendumIndex = democracyStarted.referendumIndex;12331234    // >>> Referendum voting >>>1235    console.log(`\t* Referendum #${referendumIndex} voting.......`);1236    await this.helper.democracy.referendumVote(eveAccount, referendumIndex, {1237      balance: 10_000_000_000_000_000_000n,1238      vote: {aye: true, conviction: 1},1239    });1240    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);1241    // <<< Referendum voting <<<12421243    // Wait the proposal to pass1244    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);12451246    await this.helper.wait.newBlocks(1);12471248    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);1249  }1250}12511252class WaitGroup {1253  helper: ChainHelperBase;12541255  constructor(helper: ChainHelperBase) {1256    this.helper = helper;1257  }12581259  sleep(milliseconds: number) {1260    return new Promise((resolve) => setTimeout(resolve, milliseconds));1261  }12621263  private async waitWithTimeout(promise: Promise<any>, timeout: number) {1264    let isBlock = false;1265    promise.then(() => isBlock = true).catch(() => isBlock = true);1266    let totalTime = 0;1267    const step = 100;1268    while(!isBlock) {1269      await this.sleep(step);1270      totalTime += step;1271      if(totalTime >= timeout) throw Error('Blocks production failed');1272    }1273    return promise;1274  }12751276  /**1277   * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.1278   * @param promise async operation to race against the timeout1279   * @param timeoutMS time after which to time out1280   * @param timeoutError error message to throw1281   * @returns promise of the same type the operation had1282   */1283  withTimeout<T>(1284    promise: Promise<T>,1285    timeoutMS = 30000,1286    timeoutError = 'The operation has timed out!',1287  ): Promise<T> {1288    const timeout = new Promise<never>((_, reject) => {1289      setTimeout(() => {1290        reject(new Error(timeoutError));1291      }, timeoutMS);1292    });12931294    return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});1295  }12961297  /**1298   * Wait for specified number of blocks1299   * @param blocksCount number of blocks to wait1300   * @returns1301   */1302  async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {1303    timeout = timeout ?? blocksCount * 60_000;1304    // eslint-disable-next-line no-async-promise-executor1305    const promise = new Promise<void>(async (resolve) => {1306      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {1307        if(blocksCount > 0) {1308          blocksCount--;1309        } else {1310          unsubscribe();1311          resolve();1312        }1313      });1314    });1315    await this.waitWithTimeout(promise, timeout);1316    return promise;1317  }13181319  /**1320   * Wait for the specified number of sessions to pass.1321   * Only applicable if the Session pallet is turned on.1322   * @param sessionCount number of sessions to wait1323   * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks1324   * @returns1325   */1326  async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {1327    console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`1328      + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');13291330    const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;1331    let currentSessionIndex = -1;13321333    while(currentSessionIndex < expectedSessionIndex) {1334      // eslint-disable-next-line no-async-promise-executor1335      currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {1336        await this.newBlocks(1);1337        const res = await (this.helper as DevUniqueHelper).session.getIndex();1338        resolve(res);1339      }), blockTimeout, 'The chain has stopped producing blocks!');1340    }1341  }13421343  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {1344    timeout = timeout ?? 30 * 60 * 1000;1345    // eslint-disable-next-line no-async-promise-executor1346    const promise = new Promise<void>(async (resolve) => {1347      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1348        if(data.number.toNumber() >= blockNumber) {1349          unsubscribe();1350          resolve();1351        }1352      });1353    });1354    await this.waitWithTimeout(promise, timeout);1355    return promise;1356  }13571358  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {1359    timeout = timeout ?? 30 * 60 * 1000;1360    // eslint-disable-next-line no-async-promise-executor1361    const promise = new Promise<void>(async (resolve) => {1362      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {1363        if(data.value.relayParentNumber.toNumber() >= blockNumber) {1364          // @ts-ignore1365          unsubscribe();1366          resolve();1367        }1368      });1369    });1370    await this.waitWithTimeout(promise, timeout);1371    return promise;1372  }13731374  noScheduledTasks() {1375    const api = this.helper.getApi();13761377    // eslint-disable-next-line no-async-promise-executor1378    const promise = new Promise<void>(async resolve => {1379      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1380        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();13811382        if(areThereScheduledTasks.length == 0) {1383          unsubscribe();1384          resolve();1385        }1386      });1387    });13881389    return promise;1390  }13911392  parachainBlockMultiplesOf(val: bigint) {1393    // eslint-disable-next-line no-async-promise-executor1394    const promise = new Promise<void>(async resolve => {1395      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1396        if(data.number.toBigInt() % val == 0n) {1397          console.log(`from waiter: ${data.number.toBigInt()}`);1398          unsubscribe();1399          resolve();1400        }1401      });1402    });1403    return promise;1404  }14051406  event<T extends IEventHelper>(1407    maxBlocksToWait: number,1408    eventHelper: T,1409    filter: (_: any) => boolean = () => true,1410  ): any {1411    // eslint-disable-next-line no-async-promise-executor1412    const promise = new Promise<T | null>(async (resolve) => {1413      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1414        const blockNumber = header.number.toJSON();1415        const blockHash = header.hash;1416        const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1417        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;14181419        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);14201421        const apiAt = await this.helper.getApi().at(blockHash);1422        const eventRecords = (await apiAt.query.system.events()) as any;14231424        const neededEvent = eventRecords.toArray()1425          .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1426          .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1427          .find(filter);14281429        if(neededEvent) {1430          unsubscribe();1431          resolve(neededEvent);1432        } else if(maxBlocksToWait > 0) {1433          maxBlocksToWait--;1434        } else {1435          this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found.1436          The wait lasted until block ${blockNumber} inclusive`);1437          unsubscribe();1438          resolve(null);1439        }1440      });1441    });1442    return promise;1443  }14441445  async expectEvent<T extends IEventHelper>(1446    maxBlocksToWait: number,1447    eventHelper: T,1448    filter: (e: any) => boolean = () => true,1449  ) {1450    const e = await this.event(maxBlocksToWait, eventHelper, filter);1451    if(e == null) {1452      throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1453    } else {1454      return e;1455    }1456  }1457}14581459class SessionGroup {1460  helper: ChainHelperBase;14611462  constructor(helper: ChainHelperBase) {1463    this.helper = helper;1464  }14651466  //todo:collator documentation1467  async getIndex(): Promise<number> {1468    return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1469  }14701471  newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1472    return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1473  }14741475  setOwnKeys(signer: TSigner, key: string) {1476    return this.helper.executeExtrinsic(1477      signer,1478      'api.tx.session.setKeys',1479      [key, '0x0'],1480      true,1481    );1482  }14831484  setOwnKeysFromAddress(signer: TSigner) {1485    return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1486  }1487}14881489class TestUtilGroup {1490  helper: DevUniqueHelper;14911492  constructor(helper: DevUniqueHelper) {1493    this.helper = helper;1494  }14951496  async enable(testUtilsPalletName: string) {1497    if(this.helper.fetchMissingPalletNames([testUtilsPalletName]).length != 0) {1498      return;1499    }15001501    const signer = this.helper.util.fromSeed('//Alice');1502    await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1503  }15041505  async setTestValue(signer: TSigner, testVal: number) {1506    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1507  }15081509  async incTestValue(signer: TSigner) {1510    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1511  }15121513  async setTestValueAndRollback(signer: TSigner, testVal: number) {1514    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1515  }15161517  async testValue(blockIdx?: number) {1518    const api = blockIdx1519      ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1520      : this.helper.getApi();15211522    return (await api.query.testUtils.testValue()).toJSON();1523  }15241525  async justTakeFee(signer: TSigner) {1526    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1527  }15281529  async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1530    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1531  }1532}15331534class EventCapture {1535  helper: DevUniqueHelper;1536  eventSection: string;1537  eventMethod: string;1538  events: EventRecord[] = [];1539  unsubscribe: VoidFn | null = null;15401541  constructor(1542    helper: DevUniqueHelper,1543    eventSection: string,1544    eventMethod: string,1545  ) {1546    this.helper = helper;1547    this.eventSection = eventSection;1548    this.eventMethod = eventMethod;1549  }15501551  async startCapture() {1552    this.stopCapture();1553    this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1554      const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);15551556      this.events.push(...newEvents);1557    })) as any;1558  }15591560  stopCapture() {1561    if(this.unsubscribe !== null) {1562      this.unsubscribe();1563    }1564  }15651566  extractCapturedEvents() {1567    return this.events;1568  }1569}15701571class AdminGroup {1572  helper: UniqueHelper;15731574  constructor(helper: UniqueHelper) {1575    this.helper = helper;1576  }15771578  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {1579    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1580    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1581      staker: e.event.data[0].toString(),1582      stake: e.event.data[1].toBigInt(),1583      payout: e.event.data[2].toBigInt(),1584    }));1585  }1586}15871588// eslint-disable-next-line @typescript-eslint/naming-convention1589function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {1590  return class extends Base {1591    scheduleFn: 'schedule' | 'scheduleAfter';1592    blocksNum: number;1593    options: ISchedulerOptions;15941595    constructor(...args: any[]) {1596      const logger = args[0] as ILogger;1597      const options = args[1] as {1598        scheduleFn: 'schedule' | 'scheduleAfter',1599        blocksNum: number,1600        options: ISchedulerOptions1601      };16021603      super(logger);16041605      this.scheduleFn = options.scheduleFn;1606      this.blocksNum = options.blocksNum;1607      this.options = options.options;1608    }16091610    override executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {1611      const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);16121613      const mandatorySchedArgs = [1614        this.blocksNum,1615        this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,1616        this.options.priority ?? null,1617        scheduledTx,1618      ];16191620      let schedArgs;1621      let scheduleFn;16221623      if(this.options.scheduledId) {1624        schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];16251626        if(this.scheduleFn == 'schedule') {1627          scheduleFn = 'scheduleNamed';1628        } else if(this.scheduleFn == 'scheduleAfter') {1629          scheduleFn = 'scheduleNamedAfter';1630        }1631      } else {1632        schedArgs = mandatorySchedArgs;1633        scheduleFn = this.scheduleFn;1634      }16351636      const extrinsic = 'api.tx.scheduler.' + scheduleFn;16371638      return super.executeExtrinsic(1639        sender,1640        extrinsic as any,1641        schedArgs,1642        expectSuccess,1643      );1644    }1645  };1646}