git.delta.rocks / unique-network / refs/commits / 1368751edab4

difftreelog

source

tests/src/util/playgrounds/unique.dev.ts30.8 KiBsourcehistory
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, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper} 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, IPovInfo, TSigner} from './types';12import {FrameSystemEventRecord} from '@polkadot/types/lookup';13import {VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';15import {spawnSync} from 'child_process';1617export class SilentLogger {18  log(_msg: any, _level: any): void { }19  level = {20    ERROR: 'ERROR' as const,21    WARNING: 'WARNING' as const,22    INFO: 'INFO' as const,23  };24}2526export class SilentConsole {27  // TODO: Remove, this is temporary: Filter unneeded API output28  // (Jaco promised it will be removed in the next version)29  consoleErr: any;30  consoleLog: any;31  consoleWarn: any;3233  constructor() {34    this.consoleErr = console.error;35    this.consoleLog = console.log;36    this.consoleWarn = console.warn;37  }3839  enable() {40    const outFn = (printer: any) => (...args: any[]) => {41      for (const arg of args) {42        if (typeof arg !== 'string')43          continue;44        if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')45          return;46      }47      printer(...args);48    };4950    console.error = outFn(this.consoleErr.bind(console));51    console.log = outFn(this.consoleLog.bind(console));52    console.warn = outFn(this.consoleWarn.bind(console));53  }5455  disable() {56    console.error = this.consoleErr;57    console.log = this.consoleLog;58    console.warn = this.consoleWarn;59  }60}6162export class DevUniqueHelper extends UniqueHelper {63  /**64   * Arrange methods for tests65   */66  arrange: ArrangeGroup;67  wait: WaitGroup;68  admin: AdminGroup;69  session: SessionGroup;70  testUtils: TestUtilGroup;7172  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {73    options.helperBase = options.helperBase ?? DevUniqueHelper;7475    super(logger, options);76    this.arrange = new ArrangeGroup(this);77    this.wait = new WaitGroup(this);78    this.admin = new AdminGroup(this);79    this.testUtils = new TestUtilGroup(this);80    this.session = new SessionGroup(this);81  }8283  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {84    const wsProvider = new WsProvider(wsEndpoint);85    this.api = new ApiPromise({86      provider: wsProvider,87      signedExtensions: {88        ContractHelpers: {89          extrinsic: {},90          payload: {},91        },92        CheckMaintenance: {93          extrinsic: {},94          payload: {},95        },96        DisableIdentityCalls: {97          extrinsic: {},98          payload: {},99        },100        FakeTransactionFinalizer: {101          extrinsic: {},102          payload: {},103        },104      },105      rpc: {106        unique: defs.unique.rpc,107        appPromotion: defs.appPromotion.rpc,108        povinfo: defs.povinfo.rpc,109        eth: {110          feeHistory: {111            description: 'Dummy',112            params: [],113            type: 'u8',114          },115          maxPriorityFeePerGas: {116            description: 'Dummy',117            params: [],118            type: 'u8',119          },120        },121      },122    });123    await this.api.isReadyOrError;124    this.network = await UniqueHelper.detectNetwork(this.api);125    this.wsEndpoint = wsEndpoint;126  }127}128129export class DevRelayHelper extends RelayHelper {130  wait: WaitGroup;131132  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {133    options.helperBase = options.helperBase ?? DevRelayHelper;134135    super(logger, options);136    this.wait = new WaitGroup(this);137  }138}139140export class DevWestmintHelper extends WestmintHelper {141  wait: WaitGroup;142143  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {144    options.helperBase = options.helperBase ?? DevWestmintHelper;145146    super(logger, options);147    this.wait = new WaitGroup(this);148  }149}150151export class DevStatemineHelper extends DevWestmintHelper {}152153export class DevStatemintHelper extends DevWestmintHelper {}154155export class DevMoonbeamHelper extends MoonbeamHelper {156  account: MoonbeamAccountGroup;157  wait: WaitGroup;158  fastDemocracy: MoonbeamFastDemocracyGroup;159160  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {161    options.helperBase = options.helperBase ?? DevMoonbeamHelper;162    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';163164    super(logger, options);165    this.account = new MoonbeamAccountGroup(this);166    this.wait = new WaitGroup(this);167    this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);168  }169}170171export class DevMoonriverHelper extends DevMoonbeamHelper {172  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {173    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';174    super(logger, options);175  }176}177178export class DevAstarHelper extends AstarHelper {179  wait: WaitGroup;180181  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {182    options.helperBase = options.helperBase ?? DevAstarHelper;183184    super(logger, options);185    this.wait = new WaitGroup(this);186  }187}188189export class DevShidenHelper extends AstarHelper {190  wait: WaitGroup;191192  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {193    options.helperBase = options.helperBase ?? DevShidenHelper;194195    super(logger, options);196    this.wait = new WaitGroup(this);197  }198}199200export class DevAcalaHelper extends AcalaHelper {201  wait: WaitGroup;202203  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {204    options.helperBase = options.helperBase ?? DevAcalaHelper;205206    super(logger, options);207    this.wait = new WaitGroup(this);208  }209}210211export class DevKaruraHelper extends DevAcalaHelper {}212213export class ArrangeGroup {214  helper: DevUniqueHelper;215216  scheduledIdSlider = 0;217218  constructor(helper: DevUniqueHelper) {219    this.helper = helper;220  }221222  /**223   * Generates accounts with the specified UNQ token balance224   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.225   * @param donor donor account for balances226   * @returns array of newly created accounts227   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);228   */229  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {230    let nonce = await this.helper.chain.getNonce(donor.address);231    const wait = new WaitGroup(this.helper);232    const ss58Format = this.helper.chain.getChainProperties().ss58Format;233    const tokenNominal = this.helper.balance.getOneTokenNominal();234    const transactions = [];235    const accounts: IKeyringPair[] = [];236    for (const balance of balances) {237      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);238      accounts.push(recipient);239      if (balance !== 0n) {240        const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);241        transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));242        nonce++;243      }244    }245246    await Promise.all(transactions).catch(_e => {});247248    //#region TODO remove this region, when nonce problem will be solved249    const checkBalances = async () => {250      let isSuccess = true;251      for (let i = 0; i < balances.length; i++) {252        const balance = await this.helper.balance.getSubstrate(accounts[i].address);253        if (balance !== balances[i] * tokenNominal) {254          isSuccess = false;255          break;256        }257      }258      return isSuccess;259    };260261    let accountsCreated = false;262    const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;263    // checkBalances retry up to 5-50 blocks264    for (let index = 0; index < maxBlocksChecked; index++) {265      accountsCreated = await checkBalances();266      if(accountsCreated) break;267      await wait.newBlocks(1);268    }269270    if (!accountsCreated) throw Error('Accounts generation failed');271    //#endregion272273    return accounts;274  };275276  // TODO combine this method and createAccounts into one277  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {278    const createAsManyAsCan = async () => {279      let transactions: any = [];280      const accounts: IKeyringPair[] = [];281      let nonce = await this.helper.chain.getNonce(donor.address);282      const tokenNominal = this.helper.balance.getOneTokenNominal();283      const ss58Format = this.helper.chain.getChainProperties().ss58Format;284      for (let i = 0; i < accountsToCreate; i++) {285        if (i === 500) { // if there are too many accounts to create286          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled287          transactions = []; //288          nonce = await this.helper.chain.getNonce(donor.address); // update nonce289        }290        const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);291        accounts.push(recipient);292        if (withBalance !== 0n) {293          const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);294          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));295          nonce++;296        }297      }298299      const fullfilledAccounts = [];300      await Promise.allSettled(transactions);301      for (const account of accounts) {302        const accountBalance = await this.helper.balance.getSubstrate(account.address);303        if (accountBalance === withBalance * tokenNominal) {304          fullfilledAccounts.push(account);305        }306      }307      return fullfilledAccounts;308    };309310311    const crowd: IKeyringPair[] = [];312    // do up to 5 retries313    for (let index = 0; index < 5 && accountsToCreate !== 0; index++) {314      const asManyAsCan = await createAsManyAsCan();315      crowd.push(...asManyAsCan);316      accountsToCreate -= asManyAsCan.length;317    }318319    if (accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);320321    return crowd;322  };323324  isDevNode = async () => {325    let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();326    if (blockNumber == 0) {327      await this.helper.wait.newBlocks(1);328      blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();329    }330    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);331    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);332    const findCreationDate = (block: any) => {333      const humanBlock = block.toHuman();334      let date;335      humanBlock.block.extrinsics.forEach((ext: any) => {336        if(ext.method.section === 'timestamp') {337          date = Number(ext.method.args.now.replaceAll(',', ''));338        }339      });340      return date;341    };342    const block1date = await findCreationDate(block1);343    const block2date = await findCreationDate(block2);344    if(block2date! - block1date! < 9000) return true;345  };346347  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {348    const address = payer.Substrate ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum!);349    let balance = await this.helper.balance.getSubstrate(address);350351    await promise();352353    balance -= await this.helper.balance.getSubstrate(address);354355    return balance;356  }357358  async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {359    const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);360361    const kvJson: {[key: string]: string} = {};362363    for (const kv of rawPovInfo.keyValues) {364      kvJson[kv.key.toHex()] = kv.value.toHex();365    }366367    const kvStr = JSON.stringify(kvJson);368369    const chainql = spawnSync(370      'chainql',371      [372        `--tla-code=data=${kvStr}`,373        '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,374      ],375    );376377    if (!chainql.stdout) {378      throw Error('unable to get an output from the `chainql`');379    }380381    return {382      proofSize: rawPovInfo.proofSize.toNumber(),383      compactProofSize: rawPovInfo.compactProofSize.toNumber(),384      compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),385      results: rawPovInfo.results,386      kv: JSON.parse(chainql.stdout.toString()),387    };388  }389390  calculatePalletAddress(palletId: any) {391    const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));392    return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);393  }394395  makeScheduledIds(num: number): string[] {396    function makeId(slider: number) {397      const scheduledIdSize = 64;398      const hexId = slider.toString(16);399      const prefixSize = scheduledIdSize - hexId.length;400401      const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;402403      return scheduledId;404    }405406    const ids = [];407    for (let i = 0; i < num; i++) {408      ids.push(makeId(this.scheduledIdSlider));409      this.scheduledIdSlider += 1;410    }411412    return ids;413  }414415  makeScheduledId(): string {416    return (this.makeScheduledIds(1))[0];417  }418419  async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {420    const capture = new EventCapture(this.helper, eventSection, eventMethod);421    await capture.startCapture();422423    return capture;424  }425426  makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {427    return {428      V2: [429        {430          WithdrawAsset: [431            {432              id,433              fun: {434                Fungible: amount,435              },436            },437          ],438        },439        {440          BuyExecution: {441            fees: {442              id,443              fun: {444                Fungible: amount,445              },446            },447            weightLimit: 'Unlimited',448          },449        },450        {451          DepositAsset: {452            assets: {453              Wild: 'All',454            },455            maxAssets: 1,456            beneficiary: {457              parents: 0,458              interior: {459                X1: {460                  AccountId32: {461                    network: 'Any',462                    id: beneficiary,463                  },464                },465              },466            },467          },468        },469      ],470    };471  }472473  makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {474    return {475      V2: [476        {477          ReserveAssetDeposited: [478            {479              id,480              fun: {481                Fungible: amount,482              },483            },484          ],485        },486        {487          BuyExecution: {488            fees: {489              id,490              fun: {491                Fungible: amount,492              },493            },494            weightLimit: 'Unlimited',495          },496        },497        {498          DepositAsset: {499            assets: {500              Wild: 'All',501            },502            maxAssets: 1,503            beneficiary: {504              parents: 0,505              interior: {506                X1: {507                  AccountId32: {508                    network: 'Any',509                    id: beneficiary,510                  },511                },512              },513            },514          },515        },516      ],517    };518  }519}520521class MoonbeamAccountGroup {522  helper: MoonbeamHelper;523524  keyring: Keyring;525  _alithAccount: IKeyringPair;526  _baltatharAccount: IKeyringPair;527  _dorothyAccount: IKeyringPair;528529  constructor(helper: MoonbeamHelper) {530    this.helper = helper;531532    this.keyring = new Keyring({type: 'ethereum'});533    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';534    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';535    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';536537    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');538    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');539    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');540  }541542  alithAccount() {543    return this._alithAccount;544  }545546  baltatharAccount() {547    return this._baltatharAccount;548  }549550  dorothyAccount() {551    return this._dorothyAccount;552  }553554  create() {555    return this.keyring.addFromUri(mnemonicGenerate());556  }557}558559class MoonbeamFastDemocracyGroup {560  helper: DevMoonbeamHelper;561562  constructor(helper: DevMoonbeamHelper) {563    this.helper = helper;564  }565566  async executeProposal(proposalDesciption: string, encodedProposal: string) {567    const proposalHash = blake2AsHex(encodedProposal);568569    const alithAccount = this.helper.account.alithAccount();570    const baltatharAccount = this.helper.account.baltatharAccount();571    const dorothyAccount = this.helper.account.dorothyAccount();572573    const councilVotingThreshold = 2;574    const technicalCommitteeThreshold = 2;575    const fastTrackVotingPeriod = 3;576    const fastTrackDelayPeriod = 0;577578    console.log(`[democracy] executing '${proposalDesciption}' proposal`);579580    // >>> Propose external motion through council >>>581    console.log('\t* Propose external motion through council.......');582    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});583    const encodedMotion = externalMotion?.method.toHex() || '';584    const motionHash = blake2AsHex(encodedMotion);585    console.log('\t* Motion hash is %s', motionHash);586587    await this.helper.collective.council.propose(588      baltatharAccount,589      councilVotingThreshold,590      externalMotion,591      externalMotion.encodedLength,592    );593594    const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;595    await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);596    await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);597598    await this.helper.collective.council.close(599      dorothyAccount,600      motionHash,601      councilProposalIdx,602      {603        refTime: 1_000_000_000,604        proofSize: 1_000_000,605      },606      externalMotion.encodedLength,607    );608    console.log('\t* Propose external motion through council.......DONE');609    // <<< Propose external motion through council <<<610611    // >>> Fast track proposal through technical committee >>>612    console.log('\t* Fast track proposal through technical committee.......');613    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);614    const encodedFastTrack = fastTrack?.method.toHex() || '';615    const fastTrackHash = blake2AsHex(encodedFastTrack);616    console.log('\t* FastTrack hash is %s', fastTrackHash);617618    await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);619620    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;621    await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);622    await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);623624    await this.helper.collective.techCommittee.close(625      baltatharAccount,626      fastTrackHash,627      techProposalIdx,628      {629        refTime: 1_000_000_000,630        proofSize: 1_000_000,631      },632      fastTrack.encodedLength,633    );634    console.log('\t* Fast track proposal through technical committee.......DONE');635    // <<< Fast track proposal through technical committee <<<636637    const refIndexField = 0;638    const referendumIndex = await this.helper.wait.eventData<number>(3, 'democracy', 'Started', refIndexField);639640    // >>> Referendum voting >>>641    console.log(`\t* Referendum #${referendumIndex} voting.......`);642    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex!, {643      balance: 10_000_000_000_000_000_000n,644      vote: {aye: true, conviction: 1},645    });646    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);647    // <<< Referendum voting <<<648649    // Wait the proposal to pass650    await this.helper.wait.event(3, 'democracy', 'Passed');651    await this.helper.wait.newBlocks(1);652653    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);654  }655}656657class WaitGroup {658  helper: ChainHelperBase;659660  constructor(helper: ChainHelperBase) {661    this.helper = helper;662  }663664  sleep(milliseconds: number) {665    return new Promise((resolve) => setTimeout(resolve, milliseconds));666  }667668  private async waitWithTimeout(promise: Promise<any>, timeout: number) {669    let isBlock = false;670    promise.then(() => isBlock = true).catch(() => isBlock = true);671    let totalTime = 0;672    const step = 100;673    while(!isBlock) {674      await this.sleep(step);675      totalTime += step;676      if(totalTime >= timeout) throw Error('Blocks production failed');677    }678    return promise;679  }680681  /**682   * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.683   * @param promise async operation to race against the timeout684   * @param timeoutMS time after which to time out685   * @param timeoutError error message to throw686   * @returns promise of the same type the operation had687   */688  withTimeout<T>(689    promise: Promise<T>,690    timeoutMS = 30000,691    timeoutError = 'The operation has timed out!',692  ): Promise<T> {693    const timeout = new Promise<never>((_, reject) => {694      setTimeout(() => {695        reject(new Error(timeoutError));696      }, timeoutMS);697    });698699    return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});700  }701702  /**703   * Wait for specified number of blocks704   * @param blocksCount number of blocks to wait705   * @returns706   */707  async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {708    timeout = timeout ?? blocksCount * 60_000;709    // eslint-disable-next-line no-async-promise-executor710    const promise = new Promise<void>(async (resolve) => {711      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {712        if (blocksCount > 0) {713          blocksCount--;714        } else {715          unsubscribe();716          resolve();717        }718      });719    });720    await this.waitWithTimeout(promise, timeout);721    return promise;722  }723724  /**725   * Wait for the specified number of sessions to pass.726   * Only applicable if the Session pallet is turned on.727   * @param sessionCount number of sessions to wait728   * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks729   * @returns730   */731  async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {732    console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`733      + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');734735    const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;736    let currentSessionIndex = -1;737738    while (currentSessionIndex < expectedSessionIndex) {739      // eslint-disable-next-line no-async-promise-executor740      currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {741        await this.newBlocks(1);742        const res = await (this.helper as DevUniqueHelper).session.getIndex();743        resolve(res);744      }), blockTimeout, 'The chain has stopped producing blocks!');745    }746  }747748  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {749    timeout = timeout ?? 30 * 60 * 1000;750    // eslint-disable-next-line no-async-promise-executor751    const promise = new Promise<void>(async (resolve) => {752      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {753        if (data.number.toNumber() >= blockNumber) {754          unsubscribe();755          resolve();756        }757      });758    });759    await this.waitWithTimeout(promise, timeout);760    return promise;761  }762763  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {764    timeout = timeout ?? 30 * 60 * 1000;765    // eslint-disable-next-line no-async-promise-executor766    const promise = new Promise<void>(async (resolve) => {767      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {768        if (data.value.relayParentNumber.toNumber() >= blockNumber) {769          // @ts-ignore770          unsubscribe();771          resolve();772        }773      });774    });775    await this.waitWithTimeout(promise, timeout);776    return promise;777  }778779  noScheduledTasks() {780    const api = this.helper.getApi();781782    // eslint-disable-next-line no-async-promise-executor783    const promise = new Promise<void>(async resolve => {784      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {785        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();786787        if(areThereScheduledTasks.length == 0) {788          unsubscribe();789          resolve();790        }791      });792    });793794    return promise;795  }796797  event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {798    // eslint-disable-next-line no-async-promise-executor799    const promise = new Promise<EventRecord | null>(async (resolve) => {800      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {801        const blockNumber = header.number.toHuman();802        const blockHash = header.hash;803        const eventIdStr = `${eventSection}.${eventMethod}`;804        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;805806        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);807808        const apiAt = await this.helper.getApi().at(blockHash);809        const eventRecords = (await apiAt.query.system.events()) as any;810811        const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {812          return r.event.section == eventSection && r.event.method == eventMethod;813        });814815        if (neededEvent) {816          unsubscribe();817          resolve(neededEvent);818        } else if (maxBlocksToWait > 0) {819          maxBlocksToWait--;820        } else {821          this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);822          unsubscribe();823          resolve(null);824        }825      });826    });827    return promise;828  }829830  async eventData<EventT>(maxBlocksToWait: number, eventSection: string, eventMethod: string, fieldIndex: number) {831    const eventRecord = await this.event(maxBlocksToWait, eventSection, eventMethod);832833    if (eventRecord == null) {834      return null;835    }836837    const event = eventRecord!.event;838    const data = event.data[fieldIndex] as EventT;839840    return data;841  }842}843844class SessionGroup {845  helper: ChainHelperBase;846847  constructor(helper: ChainHelperBase) {848    this.helper = helper;849  }850851  //todo:collator documentation852  async getIndex(): Promise<number> {853    return (await this.helper.callRpc('api.query.session.currentIndex')).toNumber();854  }855856  newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {857    return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);858  }859860  setOwnKeys(signer: TSigner, key: string) {861    return this.helper.executeExtrinsic(862      signer,863      'api.tx.session.setKeys',864      [key, '0x0'],865      true,866    );867  }868869  setOwnKeysFromAddress(signer: TSigner) {870    return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));871  }872}873874class TestUtilGroup {875  helper: DevUniqueHelper;876877  constructor(helper: DevUniqueHelper) {878    this.helper = helper;879  }880881  async enable() {882    if (this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {883      return;884    }885886    const signer = this.helper.util.fromSeed('//Alice');887    await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);888  }889890  async setTestValue(signer: TSigner, testVal: number) {891    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);892  }893894  async incTestValue(signer: TSigner) {895    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);896  }897898  async setTestValueAndRollback(signer: TSigner, testVal: number) {899    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);900  }901902  async testValue(blockIdx?: number) {903    const api = blockIdx904      ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))905      : this.helper.getApi();906907    return (await api.query.testUtils.testValue()).toJSON();908  }909910  async justTakeFee(signer: TSigner) {911    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);912  }913914  async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {915    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);916  }917}918919class EventCapture {920  helper: DevUniqueHelper;921  eventSection: string;922  eventMethod: string;923  events: EventRecord[] = [];924  unsubscribe: VoidFn | null = null;925926  constructor(927    helper: DevUniqueHelper,928    eventSection: string,929    eventMethod: string,930  ) {931    this.helper = helper;932    this.eventSection = eventSection;933    this.eventMethod = eventMethod;934  }935936  async startCapture() {937    this.stopCapture();938    this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {939      const newEvents = eventRecords.filter(r => {940        return r.event.section == this.eventSection && r.event.method == this.eventMethod;941      });942943      this.events.push(...newEvents);944    })) as any;945  }946947  stopCapture() {948    if (this.unsubscribe !== null) {949      this.unsubscribe();950    }951  }952953  extractCapturedEvents() {954    return this.events;955  }956}957958class AdminGroup {959  helper: UniqueHelper;960961  constructor(helper: UniqueHelper) {962    this.helper = helper;963  }964965  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {966    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);967    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => {968      return {969        staker: e.event.data[0].toString(),970        stake: e.event.data[1].toBigInt(),971        payout: e.event.data[2].toBigInt(),972      };973    });974  }975}