git.delta.rocks / unique-network / refs/commits / 02607563eb3d

difftreelog

source

js-packages/test-utils/index.ts54.6 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 FinCouncil = class extends EventSection('financialCouncil') {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 Executed = this.Method('Executed', data => ({230      proposalHash: eventHumanData(data, 0),231    }));232  };233234  static TechnicalCommittee = class extends EventSection('technicalCommittee') {235    static Proposed = this.Method('Proposed', data => ({236      account: eventHumanData(data, 0),237      proposalIndex: eventJsonData<number>(data, 1),238      proposalHash: eventHumanData(data, 2),239      threshold: eventJsonData<number>(data, 3),240    }));241    static Closed = this.Method('Closed', data => ({242      proposalHash: eventHumanData(data, 0),243      yes: eventJsonData<number>(data, 1),244      no: eventJsonData<number>(data, 2),245    }));246    static Approved = this.Method('Approved', data => ({247      proposalHash: eventHumanData(data, 0),248    }));249    static Executed = this.Method('Executed', data => ({250      proposalHash: eventHumanData(data, 0),251      result: eventHumanData(data, 1),252    }));253  };254255  static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {256    static Submitted = this.Method('Submitted', data => ({257      referendumIndex: eventJsonData<number>(data, 0),258      trackId: eventJsonData<number>(data, 1),259      proposal: eventJsonData(data, 2),260    }));261262    static Cancelled = this.Method('Cancelled', data => ({263      index: eventJsonData<number>(data, 0),264      tally: eventJsonData(data, 1),265    }));266  };267268  static UniqueScheduler = schedulerSection('uniqueScheduler');269  static Scheduler = schedulerSection('scheduler');270271  static XcmpQueue = class extends EventSection('xcmpQueue') {272    static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({273      messageHash: eventJsonData(data, 0),274    }));275276    static Success = this.Method('Success', data => ({277      messageHash: eventJsonData(data, 0),278    }));279280    static Fail = this.Method('Fail', data => ({281      messageHash: eventJsonData(data, 0),282      outcome: eventData<StagingXcmV2TraitsError>(data, 2),283    }));284  };285286  static DmpQueue = class extends EventSection('dmpQueue') {287    static ExecutedDownward = this.Method('ExecutedDownward', data => ({288      outcome: eventData<StagingXcmV3TraitsOutcome>(data, 2),289    }));290  };291}292293// eslint-disable-next-line @typescript-eslint/naming-convention294export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {295  return class extends Base {296    constructor(...args: any[]) {297      super(...args);298    }299300    override async executeExtrinsic(301      sender: IKeyringPair,302      extrinsic: string,303      params: any[],304      expectSuccess?: boolean,305      options: Partial<SignerOptions> | null = null,306    ): Promise<ITransactionResult> {307      const call = this.constructApiCall(extrinsic, params);308      const result = await super.executeExtrinsic(309        sender,310        'api.tx.sudo.sudo',311        [call],312        expectSuccess,313        options,314      );315316      if(result.status === 'Fail') return result;317318      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;319      if(data.isErr) {320        if(data.asErr.isModule) {321          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;322          const metaError = super.getApi()?.registry.findMetaError(error);323          throw new Error(`${metaError.section}.${metaError.name}`);324        } else if(data.asErr.isToken) {325          throw new Error(`Token: ${data.asErr.asToken}`);326        }327        // May be [object Object] in case of unhandled non-unit enum328        throw new Error(`Misc: ${data.asErr.toHuman()}`);329      }330      return result;331    }332    override async executeExtrinsicUncheckedWeight(333      sender: IKeyringPair,334      extrinsic: string,335      params: any[],336      expectSuccess?: boolean,337      options: Partial<SignerOptions> | null = null,338    ): Promise<ITransactionResult> {339      const call = this.constructApiCall(extrinsic, params);340      const result = await super.executeExtrinsic(341        sender,342        'api.tx.sudo.sudoUncheckedWeight',343        [call, {refTime: 0, proofSize: 0}],344        expectSuccess,345        options,346      );347348      if(result.status === 'Fail') return result;349350      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;351      if(data.isErr) {352        if(data.asErr.isModule) {353          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;354          const metaError = super.getApi()?.registry.findMetaError(error);355          throw new Error(`${metaError.section}.${metaError.name}`);356        } else if(data.asErr.isToken) {357          throw new Error(`Token: ${data.asErr.asToken}`);358        }359        // May be [object Object] in case of unhandled non-unit enum360        throw new Error(`Misc: ${data.asErr.toHuman()}`);361      }362      return result;363    }364  };365}366367class SchedulerGroup extends HelperGroup<UniqueHelper> {368  constructor(helper: UniqueHelper) {369    super(helper);370  }371372  cancelScheduled(signer: TSigner, scheduledId: string) {373    return this.helper.executeExtrinsic(374      signer,375      'api.tx.scheduler.cancelNamed',376      [scheduledId],377      true,378    );379  }380381  changePriority(signer: TSigner, scheduledId: string, priority: number) {382    return this.helper.executeExtrinsic(383      signer,384      'api.tx.scheduler.changeNamedPriority',385      [scheduledId, priority],386      true,387    );388  }389390  scheduleAt<T extends DevUniqueHelper>(391    executionBlockNumber: number,392    options: ISchedulerOptions = {},393  ) {394    return this.schedule<T>('schedule', executionBlockNumber, options);395  }396397  scheduleAfter<T extends DevUniqueHelper>(398    blocksBeforeExecution: number,399    options: ISchedulerOptions = {},400  ) {401    return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);402  }403404  schedule<T extends UniqueHelper>(405    scheduleFn: 'schedule' | 'scheduleAfter',406    blocksNum: number,407    options: ISchedulerOptions = {},408  ) {409    // eslint-disable-next-line @typescript-eslint/naming-convention410    const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);411    return this.helper.clone(ScheduledHelperType, {412      scheduleFn,413      blocksNum,414      options,415    }) as T;416  }417}418419class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {420  //todo:collator documentation421  addInvulnerable(signer: TSigner, address: string) {422    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);423  }424425  removeInvulnerable(signer: TSigner, address: string) {426    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);427  }428429  async getInvulnerables(): Promise<string[]> {430    return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());431  }432433  /** and also total max invulnerables */434  maxCollators(): number {435    return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);436  }437438  async getDesiredCollators(): Promise<number> {439    return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();440  }441442  setLicenseBond(signer: TSigner, amount: bigint) {443    return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);444  }445446  async getLicenseBond(): Promise<bigint> {447    return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();448  }449450  obtainLicense(signer: TSigner) {451    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);452  }453454  releaseLicense(signer: TSigner) {455    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);456  }457458  forceReleaseLicense(signer: TSigner, released: string) {459    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);460  }461462  async hasLicense(address: string): Promise<bigint> {463    return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();464  }465466  onboard(signer: TSigner) {467    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);468  }469470  offboard(signer: TSigner) {471    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);472  }473474  async getCandidates(): Promise<string[]> {475    return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());476  }477}478479export class DevUniqueHelper extends UniqueHelper {480  /**481   * Arrange methods for tests482   */483  arrange: ArrangeGroup;484  wait: WaitGroup;485  admin: AdminGroup;486  session: SessionGroup;487  testUtils: TestUtilGroup;488  foreignAssets: ForeignAssetsGroup;489  xcm: XcmGroup<DevUniqueHelper>;490  xTokens: XTokensGroup<DevUniqueHelper>;491  tokens: TokensGroup<DevUniqueHelper>;492  scheduler: SchedulerGroup;493  collatorSelection: CollatorSelectionGroup;494  council: ICollectiveGroup;495  finCouncil: ICollectiveGroup;496  technicalCommittee: ICollectiveGroup;497  fellowship: IFellowshipGroup;498  democracy: DemocracyGroup;499500  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {501    options.helperBase = options.helperBase ?? DevUniqueHelper;502503    super(logger, options);504    this.arrange = new ArrangeGroup(this);505    this.wait = new WaitGroup(this);506    this.admin = new AdminGroup(this);507    this.testUtils = new TestUtilGroup(this);508    this.session = new SessionGroup(this);509    this.foreignAssets = new ForeignAssetsGroup(this);510    this.xcm = new XcmGroup(this, 'polkadotXcm');511    this.xTokens = new XTokensGroup(this);512    this.tokens = new TokensGroup(this);513    this.scheduler = new SchedulerGroup(this);514    this.collatorSelection = new CollatorSelectionGroup(this);515    this.council = {516      collective: new CollectiveGroup(this, 'council'),517      membership: new CollectiveMembershipGroup(this, 'councilMembership'),518    };519    this.finCouncil = {520      collective: new CollectiveGroup(this, 'financialCouncil'),521      membership: new CollectiveMembershipGroup(this, 'financialCouncilMembership'),522    };523    this.technicalCommittee = {524      collective: new CollectiveGroup(this, 'technicalCommittee'),525      membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),526    };527    this.fellowship = {528      collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),529      referenda: new ReferendaGroup(this, 'fellowshipReferenda'),530    };531    this.democracy = new DemocracyGroup(this);532  }533534  override async connect(wsEndpoint: string, _listeners?: any): Promise<void> {535    if(!wsEndpoint) throw new Error('wsEndpoint was not set');536    const wsProvider = new WsProvider(wsEndpoint);537    this.api = new ApiPromise({538      provider: wsProvider,539      signedExtensions: {540        ContractHelpers: {541          extrinsic: {},542          payload: {},543        },544        CheckMaintenance: {545          extrinsic: {},546          payload: {},547        },548        DisableIdentityCalls: {549          extrinsic: {},550          payload: {},551        },552        FakeTransactionFinalizer: {553          extrinsic: {},554          payload: {},555        },556      },557      rpc: {558        unique: defs.unique.rpc,559        appPromotion: defs.appPromotion.rpc,560        povinfo: defs.povinfo.rpc,561        eth: {562          feeHistory: {563            description: 'Dummy',564            params: [],565            type: 'u8',566          },567          maxPriorityFeePerGas: {568            description: 'Dummy',569            params: [],570            type: 'u8',571          },572        },573      },574    });575    await this.api.isReadyOrError;576    this.network = await UniqueHelper.detectNetwork(this.api);577    this.wsEndpoint = wsEndpoint;578  }579  getSudo<T extends DevUniqueHelper>() {580    // eslint-disable-next-line @typescript-eslint/naming-convention581    const SudoHelperType = SudoHelper(this.helperBase);582    return this.clone(SudoHelperType) as T;583  }584}585586export class DevRelayHelper extends RelayHelper {587  wait: WaitGroup;588589  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {590    options.helperBase = options.helperBase ?? DevRelayHelper;591592    super(logger, options);593    this.wait = new WaitGroup(this);594  }595596  getSudo() {597    // eslint-disable-next-line @typescript-eslint/naming-convention598    const SudoHelperType = SudoHelper(this.helperBase);599    return this.clone(SudoHelperType) as DevRelayHelper;600  }601}602603export class DevWestmintHelper extends WestmintHelper {604  wait: WaitGroup;605606  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {607    options.helperBase = options.helperBase ?? DevWestmintHelper;608609    super(logger, options);610    this.wait = new WaitGroup(this);611  }612}613614export class DevStatemineHelper extends DevWestmintHelper {}615616export class DevStatemintHelper extends DevWestmintHelper {}617618export class DevMoonbeamHelper extends MoonbeamHelper {619  account: MoonbeamAccountGroup;620  wait: WaitGroup;621  fastDemocracy: MoonbeamFastDemocracyGroup;622623  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {624    options.helperBase = options.helperBase ?? DevMoonbeamHelper;625    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';626627    super(logger, options);628    this.account = new MoonbeamAccountGroup(this);629    this.wait = new WaitGroup(this);630    this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);631  }632}633634export class DevMoonriverHelper extends DevMoonbeamHelper {635  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {636    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';637    super(logger, options);638  }639}640641export class DevAstarHelper extends AstarHelper {642  wait: WaitGroup;643644  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {645    options.helperBase = options.helperBase ?? DevAstarHelper;646647    super(logger, options);648    this.wait = new WaitGroup(this);649  }650651  getSudo<T extends AstarHelper>() {652    // eslint-disable-next-line @typescript-eslint/naming-convention653    const SudoHelperType = SudoHelper(this.helperBase);654    return this.clone(SudoHelperType) as T;655  }656}657658export class DevShidenHelper extends DevAstarHelper { }659660export class DevAcalaHelper extends AcalaHelper {661  wait: WaitGroup;662663  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {664    options.helperBase = options.helperBase ?? DevAcalaHelper;665666    super(logger, options);667    this.wait = new WaitGroup(this);668  }669  getSudo() {670    // eslint-disable-next-line @typescript-eslint/naming-convention671    const SudoHelperType = SudoHelper(this.helperBase);672    return this.clone(SudoHelperType) as DevAcalaHelper;673  }674}675676export class DevPolkadexHelper extends PolkadexHelper {677  wait: WaitGroup;678  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {679    options.helperBase = options.helperBase ?? PolkadexHelper;680681    super(logger, options);682    this.wait = new WaitGroup(this);683  }684685  getSudo() {686    // eslint-disable-next-line @typescript-eslint/naming-convention687    const SudoHelperType = SudoHelper(this.helperBase);688    return this.clone(SudoHelperType) as DevPolkadexHelper;689  }690}691692export class DevHydraDxHelper extends HydraDxHelper {693  wait: WaitGroup;694  fastDemocracy: HydraFastDemocracyGroup;695696  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {697    options.helperBase = options.helperBase ?? DevHydraDxHelper;698699    super(logger, options);700701    this.wait = new WaitGroup(this);702    this.fastDemocracy = new HydraFastDemocracyGroup(this);703  }704}705706export class DevKaruraHelper extends DevAcalaHelper {}707708export class ArrangeGroup {709  helper: DevUniqueHelper;710711  scheduledIdSlider = 0;712713  constructor(helper: DevUniqueHelper) {714    this.helper = helper;715  }716717  /**718   * Generates accounts with the specified UNQ token balance719   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.720   * @param donor donor account for balances721   * @returns array of newly created accounts722   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);723   */724  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {725    let nonce = await this.helper.chain.getNonce(donor.address);726    const wait = new WaitGroup(this.helper);727    const ss58Format = this.helper.chain.getChainProperties().ss58Format;728    const tokenNominal = this.helper.balance.getOneTokenNominal();729    const transactions = [];730    const accounts: IKeyringPair[] = [];731    for(const balance of balances) {732      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);733      accounts.push(recipient);734      if(balance !== 0n) {735        const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, balance * tokenNominal]);736        transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));737        nonce++;738      }739    }740741    await Promise.all(transactions).catch(_e => {});742743    //#region TODO remove this region, when nonce problem will be solved744    const checkBalances = async () => {745      let isSuccess = true;746      for(let i = 0; i < balances.length; i++) {747        const balance = await this.helper.balance.getSubstrate(accounts[i].address);748        if(balance !== balances[i] * tokenNominal) {749          isSuccess = false;750          break;751        }752      }753      return isSuccess;754    };755756    let accountsCreated = false;757    const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;758    // checkBalances retry up to 5-50 blocks759    for(let index = 0; index < maxBlocksChecked; index++) {760      accountsCreated = await checkBalances();761      if(accountsCreated) break;762      await wait.newBlocks(1);763    }764765    if(!accountsCreated) throw Error('Accounts generation failed');766    //#endregion767768    return accounts;769  };770771  // TODO combine this method and createAccounts into one772  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {773    const createAsManyAsCan = async () => {774      let transactions: any = [];775      const accounts: IKeyringPair[] = [];776      let nonce = await this.helper.chain.getNonce(donor.address);777      const tokenNominal = this.helper.balance.getOneTokenNominal();778      const ss58Format = this.helper.chain.getChainProperties().ss58Format;779      for(let i = 0; i < accountsToCreate; i++) {780        if(i === 500) { // if there are too many accounts to create781          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled782          transactions = []; //783          nonce = await this.helper.chain.getNonce(donor.address); // update nonce784        }785        const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);786        accounts.push(recipient);787        if(withBalance !== 0n) {788          const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, withBalance * tokenNominal]);789          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));790          nonce++;791        }792      }793794      const fullfilledAccounts = [];795      await Promise.allSettled(transactions);796      for(const account of accounts) {797        const accountBalance = await this.helper.balance.getSubstrate(account.address);798        if(accountBalance === withBalance * tokenNominal) {799          fullfilledAccounts.push(account);800        }801      }802      return fullfilledAccounts;803    };804805806    const crowd: IKeyringPair[] = [];807    // do up to 5 retries808    for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {809      const asManyAsCan = await createAsManyAsCan();810      crowd.push(...asManyAsCan);811      accountsToCreate -= asManyAsCan.length;812    }813814    if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);815816    return crowd;817  };818819  /**820   * Generates one account with zero balance821   * @returns the newly generated account822   * @example const account = await helper.arrange.createEmptyAccount();823   */824  createEmptyAccount = (): IKeyringPair => {825    const ss58Format = this.helper.chain.getChainProperties().ss58Format;826    return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);827  };828829  isDevNode = async () => {830    let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();831    if(blockNumber == 0) {832      await this.helper.wait.newBlocks(1);833      blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();834    }835    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);836    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);837    const findCreationDate = (block: any) => {838      const humanBlock = block.toHuman();839      let date;840      humanBlock.block.extrinsics.forEach((ext: any) => {841        if(ext.method.section === 'timestamp') {842          date = Number(ext.method.args.now.replaceAll(',', ''));843        }844      });845      return date;846    };847    const block1date = await findCreationDate(block1);848    const block2date = await findCreationDate(block2);849    if(block2date! - block1date! < 9000) return true;850    return false;851  };852853  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {854    const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);855    let balance = await this.helper.balance.getSubstrate(address);856857    await promise();858859    balance -= await this.helper.balance.getSubstrate(address);860861    return balance;862  }863864  async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {865    const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);866867    const kvJson: {[key: string]: string} = {};868869    for(const kv of rawPovInfo.keyValues) {870      kvJson[kv.key.toHex()] = kv.value.toHex();871    }872873    const kvStr = JSON.stringify(kvJson);874875    const chainql = spawnSync(876      'chainql',877      [878        `--tla-code=data=${kvStr}`,879        '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,880      ],881    );882883    if(!chainql.stdout) {884      throw Error('unable to get an output from the `chainql`');885    }886887    return {888      proofSize: rawPovInfo.proofSize.toNumber(),889      compactProofSize: rawPovInfo.compactProofSize.toNumber(),890      compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),891      results: rawPovInfo.results,892      kv: JSON.parse(chainql.stdout.toString()),893    };894  }895896  calculatePalletAddress(palletId: any) {897    const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));898    return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);899  }900901  makeScheduledIds(num: number): string[] {902    function makeId(slider: number) {903      const scheduledIdSize = 64;904      const hexId = slider.toString(16);905      const prefixSize = scheduledIdSize - hexId.length;906907      const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;908909      return scheduledId;910    }911912    const ids = [];913    for(let i = 0; i < num; i++) {914      ids.push(makeId(this.scheduledIdSlider));915      this.scheduledIdSlider += 1;916    }917918    return ids;919  }920921  makeScheduledId(): string {922    return (this.makeScheduledIds(1))[0];923  }924925  async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {926    const capture = new EventCapture(this.helper, eventSection, eventMethod);927    await capture.startCapture();928929    return capture;930  }931932  makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {933    return {934      V2: [935        {936          WithdrawAsset: [937            {938              id,939              fun: {940                Fungible: amount,941              },942            },943          ],944        },945        {946          BuyExecution: {947            fees: {948              id,949              fun: {950                Fungible: amount,951              },952            },953            weightLimit: 'Unlimited',954          },955        },956        {957          DepositAsset: {958            assets: {959              Wild: 'All',960            },961            maxAssets: 1,962            beneficiary: {963              parents: 0,964              interior: {965                X1: {966                  AccountId32: {967                    network: 'Any',968                    id: beneficiary,969                  },970                },971              },972            },973          },974        },975      ],976    };977  }978979  makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {980    return {981      V2: [982        {983          ReserveAssetDeposited: [984            {985              id,986              fun: {987                Fungible: amount,988              },989            },990          ],991        },992        {993          BuyExecution: {994            fees: {995              id,996              fun: {997                Fungible: amount,998              },999            },1000            weightLimit: 'Unlimited',1001          },1002        },1003        {1004          DepositAsset: {1005            assets: {1006              Wild: 'All',1007            },1008            maxAssets: 1,1009            beneficiary: {1010              parents: 0,1011              interior: {1012                X1: {1013                  AccountId32: {1014                    network: 'Any',1015                    id: beneficiary,1016                  },1017                },1018              },1019            },1020          },1021        },1022      ],1023    };1024  }10251026  makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) {1027    return {1028      V3: [1029        {1030          UnpaidExecution: {1031            weightLimit: 'Unlimited',1032            checkOrigin: null,1033          },1034        },1035        {1036          Transact: {1037            originKind: 'Superuser',1038            requireWeightAtMost: {1039              refTime: info.weightMultiplier * 200000000,1040              proofSize: info.weightMultiplier * 3000,1041            },1042            call: {1043              encoded: info.call,1044            },1045          },1046        },1047      ],1048    };1049  }1050}10511052class MoonbeamAccountGroup {1053  helper: MoonbeamHelper;10541055  keyring: Keyring;1056  _alithAccount: IKeyringPair;1057  _baltatharAccount: IKeyringPair;1058  _dorothyAccount: IKeyringPair;10591060  constructor(helper: MoonbeamHelper) {1061    this.helper = helper;10621063    this.keyring = new Keyring({type: 'ethereum'});1064    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';1065    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';1066    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';10671068    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');1069    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');1070    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');1071  }10721073  alithAccount() {1074    return this._alithAccount;1075  }10761077  baltatharAccount() {1078    return this._baltatharAccount;1079  }10801081  dorothyAccount() {1082    return this._dorothyAccount;1083  }10841085  create() {1086    return this.keyring.addFromUri(mnemonicGenerate());1087  }1088}10891090class MoonbeamFastDemocracyGroup {1091  helper: DevMoonbeamHelper;10921093  constructor(helper: DevMoonbeamHelper) {1094    this.helper = helper;1095  }10961097  async executeProposal(proposalDesciption: string, encodedProposal: string) {1098    const proposalHash = blake2AsHex(encodedProposal);10991100    const alithAccount = this.helper.account.alithAccount();1101    const baltatharAccount = this.helper.account.baltatharAccount();1102    const dorothyAccount = this.helper.account.dorothyAccount();11031104    const councilVotingThreshold = 2;1105    const technicalCommitteeThreshold = 2;1106    const fastTrackVotingPeriod = 3;1107    const fastTrackDelayPeriod = 0;11081109    console.log(`[democracy] executing '${proposalDesciption}' proposal`);11101111    // >>> Propose external motion through council >>>1112    console.log('\t* Propose external motion through council.......');1113    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});1114    const encodedMotion = externalMotion?.method.toHex() || '';1115    const motionHash = blake2AsHex(encodedMotion);1116    console.log('\t* Motion hash is %s', motionHash);11171118    await this.helper.collective.council.propose(1119      baltatharAccount,1120      councilVotingThreshold,1121      externalMotion,1122      externalMotion.encodedLength,1123    );11241125    const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;1126    await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);1127    await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);11281129    await this.helper.collective.council.close(1130      dorothyAccount,1131      motionHash,1132      councilProposalIdx,1133      {1134        refTime: 1_000_000_000,1135        proofSize: 1_000_000,1136      },1137      externalMotion.encodedLength,1138    );1139    console.log('\t* Propose external motion through council.......DONE');1140    // <<< Propose external motion through council <<<11411142    // >>> Fast track proposal through technical committee >>>1143    console.log('\t* Fast track proposal through technical committee.......');1144    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);1145    const encodedFastTrack = fastTrack?.method.toHex() || '';1146    const fastTrackHash = blake2AsHex(encodedFastTrack);1147    console.log('\t* FastTrack hash is %s', fastTrackHash);11481149    await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);11501151    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;1152    await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);1153    await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);11541155    await this.helper.collective.techCommittee.close(1156      baltatharAccount,1157      fastTrackHash,1158      techProposalIdx,1159      {1160        refTime: 1_000_000_000,1161        proofSize: 1_000_000,1162      },1163      fastTrack.encodedLength,1164    );1165    console.log('\t* Fast track proposal through technical committee.......DONE');1166    // <<< Fast track proposal through technical committee <<<11671168    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);1169    const referendumIndex = democracyStarted.referendumIndex;11701171    // >>> Referendum voting >>>1172    console.log(`\t* Referendum #${referendumIndex} voting.......`);1173    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {1174      balance: 10_000_000_000_000_000_000n,1175      vote: {aye: true, conviction: 1},1176    });1177    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);1178    // <<< Referendum voting <<<11791180    // Wait the proposal to pass1181    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);11821183    await this.helper.wait.newBlocks(1);11841185    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);1186  }1187}11881189class HydraFastDemocracyGroup {1190  helper: DevHydraDxHelper;11911192  constructor(helper: DevHydraDxHelper) {1193    this.helper = helper;1194  }11951196  async executeProposal(proposalDesciption: string, encodedProposal: string) {1197    const proposalHash = blake2AsHex(encodedProposal);1198    const aliceAccount = this.helper.util.fromSeed('//Alice');1199    const bobAccount = this.helper.util.fromSeed('//Bob');1200    const eveAccount = this.helper.util.fromSeed('//Eve');12011202    const councilVotingThreshold = 1;1203    const technicalCommitteeThreshold = 3;1204    const fastTrackVotingPeriod = 3;1205    const fastTrackDelayPeriod = 0;12061207    console.log(`[democracy] executing '${proposalDesciption}' proposal`);12081209    // >>> Propose external motion through council >>>1210    console.log('\t* Propose external motion through council.......');1211    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});1212    const encodedMotion = externalMotion?.method.toHex() || '';1213    const motionHash = blake2AsHex(encodedMotion);1214    console.log('\t* Motion hash is %s', motionHash);12151216    await this.helper.collective.council.propose(1217      aliceAccount,1218      councilVotingThreshold,1219      externalMotion,1220      externalMotion.encodedLength,1221    );12221223    console.log('\t* Propose external motion through council.......DONE');1224    // <<< Propose external motion through council <<<12251226    // >>> Fast track proposal through technical committee >>>1227    console.log('\t* Fast track proposal through technical committee.......');1228    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);1229    const encodedFastTrack = fastTrack?.method.toHex() || '';1230    const fastTrackHash = blake2AsHex(encodedFastTrack);1231    console.log('\t* FastTrack hash is %s', fastTrackHash);12321233    await this.helper.collective.techCommittee.propose(aliceAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);12341235    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;1236    await this.helper.collective.techCommittee.vote(aliceAccount, fastTrackHash, techProposalIdx, true);1237    await this.helper.collective.techCommittee.vote(bobAccount, fastTrackHash, techProposalIdx, true);1238    await this.helper.collective.techCommittee.vote(eveAccount, fastTrackHash, techProposalIdx, true);12391240    await this.helper.collective.techCommittee.close(1241      bobAccount,1242      fastTrackHash,1243      techProposalIdx,1244      {1245        refTime: 1_000_000_000,1246        proofSize: 1_000_000,1247      },1248      fastTrack.encodedLength,1249    );1250    console.log('\t* Fast track proposal through technical committee.......DONE');1251    // <<< Fast track proposal through technical committee <<<12521253    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);1254    const referendumIndex = democracyStarted.referendumIndex;12551256    // >>> Referendum voting >>>1257    console.log(`\t* Referendum #${referendumIndex} voting.......`);1258    await this.helper.democracy.referendumVote(eveAccount, referendumIndex, {1259      balance: 10_000_000_000_000_000_000n,1260      vote: {aye: true, conviction: 1},1261    });1262    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);1263    // <<< Referendum voting <<<12641265    // Wait the proposal to pass1266    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);12671268    await this.helper.wait.newBlocks(1);12691270    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);1271  }1272}12731274class WaitGroup {1275  helper: ChainHelperBase;12761277  constructor(helper: ChainHelperBase) {1278    this.helper = helper;1279  }12801281  sleep(milliseconds: number) {1282    return new Promise((resolve) => setTimeout(resolve, milliseconds));1283  }12841285  private async waitWithTimeout(promise: Promise<any>, timeout: number) {1286    let isBlock = false;1287    promise.then(() => isBlock = true).catch(() => isBlock = true);1288    let totalTime = 0;1289    const step = 100;1290    while(!isBlock) {1291      await this.sleep(step);1292      totalTime += step;1293      if(totalTime >= timeout) throw Error('Blocks production failed');1294    }1295    return promise;1296  }12971298  /**1299   * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.1300   * @param promise async operation to race against the timeout1301   * @param timeoutMS time after which to time out1302   * @param timeoutError error message to throw1303   * @returns promise of the same type the operation had1304   */1305  withTimeout<T>(1306    promise: Promise<T>,1307    timeoutMS = 30000,1308    timeoutError = 'The operation has timed out!',1309  ): Promise<T> {1310    const timeout = new Promise<never>((_, reject) => {1311      setTimeout(() => {1312        reject(new Error(timeoutError));1313      }, timeoutMS);1314    });13151316    return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});1317  }13181319  /**1320   * Wait for specified number of blocks1321   * @param blocksCount number of blocks to wait1322   * @returns1323   */1324  async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {1325    timeout = timeout ?? blocksCount * 60_000;1326    // eslint-disable-next-line no-async-promise-executor1327    const promise = new Promise<void>(async (resolve) => {1328      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {1329        if(blocksCount > 0) {1330          blocksCount--;1331        } else {1332          unsubscribe();1333          resolve();1334        }1335      });1336    });1337    await this.waitWithTimeout(promise, timeout);1338    return promise;1339  }13401341  /**1342   * Wait for the specified number of sessions to pass.1343   * Only applicable if the Session pallet is turned on.1344   * @param sessionCount number of sessions to wait1345   * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks1346   * @returns1347   */1348  async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {1349    console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`1350      + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');13511352    const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;1353    let currentSessionIndex = -1;13541355    while(currentSessionIndex < expectedSessionIndex) {1356      // eslint-disable-next-line no-async-promise-executor1357      currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {1358        await this.newBlocks(1);1359        const res = await (this.helper as DevUniqueHelper).session.getIndex();1360        resolve(res);1361      }), blockTimeout, 'The chain has stopped producing blocks!');1362    }1363  }13641365  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {1366    timeout = timeout ?? 30 * 60 * 1000;1367    // eslint-disable-next-line no-async-promise-executor1368    const promise = new Promise<void>(async (resolve) => {1369      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1370        if(data.number.toNumber() >= blockNumber) {1371          unsubscribe();1372          resolve();1373        }1374      });1375    });1376    await this.waitWithTimeout(promise, timeout);1377    return promise;1378  }13791380  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {1381    timeout = timeout ?? 30 * 60 * 1000;1382    // eslint-disable-next-line no-async-promise-executor1383    const promise = new Promise<void>(async (resolve) => {1384      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {1385        if(data.value.relayParentNumber.toNumber() >= blockNumber) {1386          // @ts-ignore1387          unsubscribe();1388          resolve();1389        }1390      });1391    });1392    await this.waitWithTimeout(promise, timeout);1393    return promise;1394  }13951396  noScheduledTasks() {1397    const api = this.helper.getApi();13981399    // eslint-disable-next-line no-async-promise-executor1400    const promise = new Promise<void>(async resolve => {1401      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1402        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();14031404        if(areThereScheduledTasks.length == 0) {1405          unsubscribe();1406          resolve();1407        }1408      });1409    });14101411    return promise;1412  }14131414  parachainBlockMultiplesOf(val: bigint) {1415    // eslint-disable-next-line no-async-promise-executor1416    const promise = new Promise<void>(async resolve => {1417      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1418        if(data.number.toBigInt() % val == 0n) {1419          console.log(`from waiter: ${data.number.toBigInt()}`);1420          unsubscribe();1421          resolve();1422        }1423      });1424    });1425    return promise;1426  }14271428  event<T extends IEventHelper>(1429    maxBlocksToWait: number,1430    eventHelper: T,1431    filter: (_: any) => boolean = () => true,1432  ): any {1433    // eslint-disable-next-line no-async-promise-executor1434    const promise = new Promise<T | null>(async (resolve) => {1435      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1436        const blockNumber = header.number.toJSON();1437        const blockHash = header.hash;1438        const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1439        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;14401441        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);14421443        const apiAt = await this.helper.getApi().at(blockHash);1444        const eventRecords = (await apiAt.query.system.events()) as any;14451446        const neededEvent = eventRecords.toArray()1447          .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1448          .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1449          .find(filter);14501451        if(neededEvent) {1452          unsubscribe();1453          resolve(neededEvent);1454        } else if(maxBlocksToWait > 0) {1455          maxBlocksToWait--;1456        } else {1457          this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found.1458          The wait lasted until block ${blockNumber} inclusive`);1459          unsubscribe();1460          resolve(null);1461        }1462      });1463    });1464    return promise;1465  }14661467  async expectEvent<T extends IEventHelper>(1468    maxBlocksToWait: number,1469    eventHelper: T,1470    filter: (e: any) => boolean = () => true,1471  ) {1472    const e = await this.event(maxBlocksToWait, eventHelper, filter);1473    if(e == null) {1474      throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1475    } else {1476      return e;1477    }1478  }1479}14801481class SessionGroup {1482  helper: ChainHelperBase;14831484  constructor(helper: ChainHelperBase) {1485    this.helper = helper;1486  }14871488  //todo:collator documentation1489  async getIndex(): Promise<number> {1490    return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1491  }14921493  newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1494    return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1495  }14961497  setOwnKeys(signer: TSigner, key: string) {1498    return this.helper.executeExtrinsic(1499      signer,1500      'api.tx.session.setKeys',1501      [key, '0x0'],1502      true,1503    );1504  }15051506  setOwnKeysFromAddress(signer: TSigner) {1507    return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1508  }1509}15101511class TestUtilGroup {1512  helper: DevUniqueHelper;15131514  constructor(helper: DevUniqueHelper) {1515    this.helper = helper;1516  }15171518  async enable(testUtilsPalletName: string) {1519    if(this.helper.fetchMissingPalletNames([testUtilsPalletName]).length != 0) {1520      return;1521    }15221523    const signer = this.helper.util.fromSeed('//Alice');1524    await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1525  }15261527  async setTestValue(signer: TSigner, testVal: number) {1528    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1529  }15301531  async incTestValue(signer: TSigner) {1532    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1533  }15341535  async setTestValueAndRollback(signer: TSigner, testVal: number) {1536    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1537  }15381539  async testValue(blockIdx?: number) {1540    const api = blockIdx1541      ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1542      : this.helper.getApi();15431544    return (await api.query.testUtils.testValue()).toJSON();1545  }15461547  async justTakeFee(signer: TSigner) {1548    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1549  }15501551  async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1552    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1553  }1554}15551556class EventCapture {1557  helper: DevUniqueHelper;1558  eventSection: string;1559  eventMethod: string;1560  events: EventRecord[] = [];1561  unsubscribe: VoidFn | null = null;15621563  constructor(1564    helper: DevUniqueHelper,1565    eventSection: string,1566    eventMethod: string,1567  ) {1568    this.helper = helper;1569    this.eventSection = eventSection;1570    this.eventMethod = eventMethod;1571  }15721573  async startCapture() {1574    this.stopCapture();1575    this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1576      const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);15771578      this.events.push(...newEvents);1579    })) as any;1580  }15811582  stopCapture() {1583    if(this.unsubscribe !== null) {1584      this.unsubscribe();1585    }1586  }15871588  extractCapturedEvents() {1589    return this.events;1590  }1591}15921593class AdminGroup {1594  helper: UniqueHelper;15951596  constructor(helper: UniqueHelper) {1597    this.helper = helper;1598  }15991600  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {1601    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1602    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1603      staker: e.event.data[0].toString(),1604      stake: e.event.data[1].toBigInt(),1605      payout: e.event.data[2].toBigInt(),1606    }));1607  }1608}16091610// eslint-disable-next-line @typescript-eslint/naming-convention1611function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {1612  return class extends Base {1613    scheduleFn: 'schedule' | 'scheduleAfter';1614    blocksNum: number;1615    options: ISchedulerOptions;16161617    constructor(...args: any[]) {1618      const logger = args[0] as ILogger;1619      const options = args[1] as {1620        scheduleFn: 'schedule' | 'scheduleAfter',1621        blocksNum: number,1622        options: ISchedulerOptions1623      };16241625      super(logger);16261627      this.scheduleFn = options.scheduleFn;1628      this.blocksNum = options.blocksNum;1629      this.options = options.options;1630    }16311632    override executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {1633      const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);16341635      const mandatorySchedArgs = [1636        this.blocksNum,1637        this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,1638        this.options.priority ?? null,1639        scheduledTx,1640      ];16411642      let schedArgs;1643      let scheduleFn;16441645      if(this.options.scheduledId) {1646        schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];16471648        if(this.scheduleFn == 'schedule') {1649          scheduleFn = 'scheduleNamed';1650        } else if(this.scheduleFn == 'scheduleAfter') {1651          scheduleFn = 'scheduleNamedAfter';1652        }1653      } else {1654        schedArgs = mandatorySchedArgs;1655        scheduleFn = this.scheduleFn;1656      }16571658      const extrinsic = 'api.tx.scheduler.' + scheduleFn;16591660      return super.executeExtrinsic(1661        sender,1662        extrinsic as any,1663        schedArgs,1664        expectSuccess,1665      );1666    }1667  };1668}