git.delta.rocks / unique-network / refs/commits / 477a07dafafb

difftreelog

fix(types) sudo for `DevShidenHelper`

PraetorP2023-09-14parent: #bd22bf4.patch.diff
in: master

1 file changed

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