git.delta.rocks / unique-network / refs/commits / 7b023557183b

difftreelog

source

tests/src/util/playgrounds/unique.dev.ts14.1 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {mnemonicGenerate} from '@polkadot/util-crypto';5import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper} from './unique';6import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';7import * as defs from '../../interfaces/definitions';8import {IKeyringPair} from '@polkadot/types/types';9import {EventRecord} from '@polkadot/types/interfaces';10import {ICrossAccountId} from './types';11import {FrameSystemEventRecord} from '@polkadot/types/lookup';1213export class SilentLogger {14  log(_msg: any, _level: any): void { }15  level = {16    ERROR: 'ERROR' as const,17    WARNING: 'WARNING' as const,18    INFO: 'INFO' as const,19  };20}2122export class SilentConsole {23  // TODO: Remove, this is temporary: Filter unneeded API output24  // (Jaco promised it will be removed in the next version)25  consoleErr: any;26  consoleLog: any;27  consoleWarn: any;2829  constructor() {30    this.consoleErr = console.error;31    this.consoleLog = console.log;32    this.consoleWarn = console.warn;33  }3435  enable() {  36    const outFn = (printer: any) => (...args: any[]) => {37      for (const arg of args) {38        if (typeof arg !== 'string')39          continue;40        if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')41          return;42      }43      printer(...args);44    };45  46    console.error = outFn(this.consoleErr.bind(console));47    console.log = outFn(this.consoleLog.bind(console));48    console.warn = outFn(this.consoleWarn.bind(console));49  }5051  disable() {52    console.error = this.consoleErr;53    console.log = this.consoleLog;54    console.warn = this.consoleWarn;55  }56}5758export class DevUniqueHelper extends UniqueHelper {59  /**60   * Arrange methods for tests61   */62  arrange: ArrangeGroup;63  wait: WaitGroup;64  admin: AdminGroup;6566  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {67    options.helperBase = options.helperBase ?? DevUniqueHelper;6869    super(logger, options);70    this.arrange = new ArrangeGroup(this);71    this.wait = new WaitGroup(this);72    this.admin = new AdminGroup(this);73  }7475  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {76    const wsProvider = new WsProvider(wsEndpoint);77    this.api = new ApiPromise({78      provider: wsProvider,79      signedExtensions: {80        ContractHelpers: {81          extrinsic: {},82          payload: {},83        },84        FakeTransactionFinalizer: {85          extrinsic: {},86          payload: {},87        },88      },89      rpc: {90        unique: defs.unique.rpc,91        appPromotion: defs.appPromotion.rpc,92        rmrk: defs.rmrk.rpc,93        eth: {94          feeHistory: {95            description: 'Dummy',96            params: [],97            type: 'u8',98          },99          maxPriorityFeePerGas: {100            description: 'Dummy',101            params: [],102            type: 'u8',103          },104        },105      },106    });107    await this.api.isReadyOrError;108    this.network = await UniqueHelper.detectNetwork(this.api);109  }110}111112export class DevRelayHelper extends RelayHelper {}113114export class DevWestmintHelper extends WestmintHelper {115  wait: WaitGroup;116117  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {118    options.helperBase = options.helperBase ?? DevWestmintHelper;119120    super(logger, options);121    this.wait = new WaitGroup(this);122  }123}124125export class DevMoonbeamHelper extends MoonbeamHelper {126  account: MoonbeamAccountGroup;127  wait: WaitGroup;128129  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {130    options.helperBase = options.helperBase ?? DevMoonbeamHelper;131132    super(logger, options);133    this.account = new MoonbeamAccountGroup(this);134    this.wait = new WaitGroup(this);135  }136}137138export class DevMoonriverHelper extends DevMoonbeamHelper {}139140export class DevAcalaHelper extends AcalaHelper {141  wait: WaitGroup;142143  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {144    options.helperBase = options.helperBase ?? DevAcalaHelper;145146    super(logger, options);147    this.wait = new WaitGroup(this);148  }149}150151export class DevKaruraHelper extends DevAcalaHelper {}152153class ArrangeGroup {154  helper: DevUniqueHelper;155156  constructor(helper: DevUniqueHelper) {157    this.helper = helper;158  }159160  /**161   * Generates accounts with the specified UNQ token balance 162   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.163   * @param donor donor account for balances164   * @returns array of newly created accounts165   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor); 166   */167  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {168    let nonce = await this.helper.chain.getNonce(donor.address);169    const wait = new WaitGroup(this.helper);170    const ss58Format = this.helper.chain.getChainProperties().ss58Format;171    const tokenNominal = this.helper.balance.getOneTokenNominal();172    const transactions = [];173    const accounts: IKeyringPair[] = [];174    for (const balance of balances) {175      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);176      accounts.push(recipient);177      if (balance !== 0n) {178        const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);179        transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));180        nonce++;181      }182    }183184    await Promise.all(transactions).catch(_e => {});185    186    //#region TODO remove this region, when nonce problem will be solved187    const checkBalances = async () => {188      let isSuccess = true;189      for (let i = 0; i < balances.length; i++) {190        const balance = await this.helper.balance.getSubstrate(accounts[i].address);191        if (balance !== balances[i] * tokenNominal) {192          isSuccess = false;193          break;194        }195      }196      return isSuccess;197    };198199    let accountsCreated = false;200    // checkBalances retry up to 5 blocks201    for (let index = 0; index < 5; index++) {202      accountsCreated = await checkBalances();203      if(accountsCreated) break;204      await wait.newBlocks(1);205    }206207    if (!accountsCreated) throw Error('Accounts generation failed');208    //#endregion209210    return accounts;211  };212213  // TODO combine this method and createAccounts into one214  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {  215    const createAsManyAsCan = async () => {216      let transactions: any = [];217      const accounts: IKeyringPair[] = [];218      let nonce = await this.helper.chain.getNonce(donor.address);219      const tokenNominal = this.helper.balance.getOneTokenNominal();220      for (let i = 0; i < accountsToCreate; i++) {221        if (i === 500) { // if there are too many accounts to create222          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled 223          transactions = []; //224          nonce = await this.helper.chain.getNonce(donor.address); // update nonce 225        }226        const recepient = this.helper.util.fromSeed(mnemonicGenerate());227        accounts.push(recepient);228        if (withBalance !== 0n) {229          const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, withBalance * tokenNominal]);230          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));231          nonce++;232        }233      }234      235      const fullfilledAccounts = [];236      await Promise.allSettled(transactions);237      for (const account of accounts) {238        const accountBalance = await this.helper.balance.getSubstrate(account.address);239        if (accountBalance === withBalance * tokenNominal) {240          fullfilledAccounts.push(account);241        }242      }243      return fullfilledAccounts;244    };245246    247    const crowd: IKeyringPair[] = [];248    // do up to 5 retries249    for (let index = 0; index < 5 && accountsToCreate !== 0; index++) {250      const asManyAsCan = await createAsManyAsCan();251      crowd.push(...asManyAsCan);252      accountsToCreate -= asManyAsCan.length;253    }254255    if (accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);256257    return crowd;258  };259260  isDevNode = async () => {261    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [1])]);262    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [2])]);263    const findCreationDate = async (block: any) => {264      const humanBlock = block.toHuman();265      let date;266      humanBlock.block.extrinsics.forEach((ext: any) => {267        if(ext.method.section === 'timestamp') {268          date = Number(ext.method.args.now.replaceAll(',', ''));269        }270      });271      return date;272    };273    const block1date = await findCreationDate(block1);274    const block2date = await findCreationDate(block2);275    if(block2date! - block1date! < 9000) return true;276  };277  278  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {279    const address = payer.Substrate ? payer.Substrate : await this.helper.address.ethToSubstrate(payer.Ethereum!);280    let balance = await this.helper.balance.getSubstrate(address); 281    282    await promise();283    284    balance -= await this.helper.balance.getSubstrate(address);285    286    return balance;287  }288}289290class MoonbeamAccountGroup {291  helper: MoonbeamHelper;292293  keyring: Keyring;294  _alithAccount: IKeyringPair;295  _baltatharAccount: IKeyringPair;296  _dorothyAccount: IKeyringPair;297298  constructor(helper: MoonbeamHelper) {299    this.helper = helper;300301    this.keyring = new Keyring({type: 'ethereum'});302    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';303    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';304    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';305306    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');307    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');308    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');309  }310311  alithAccount() {312    return this._alithAccount;313  }314315  baltatharAccount() {316    return this._baltatharAccount;317  }318319  dorothyAccount() {320    return this._dorothyAccount;321  }322323  create() {324    return this.keyring.addFromUri(mnemonicGenerate());325  }326}327328class WaitGroup {329  helper: ChainHelperBase;330331  constructor(helper: ChainHelperBase) {332    this.helper = helper;333  }334335  /**336   * Wait for specified number of blocks337   * @param blocksCount number of blocks to wait338   * @returns 339   */340  async newBlocks(blocksCount = 1): Promise<void> {341    // eslint-disable-next-line no-async-promise-executor342    const promise = new Promise<void>(async (resolve) => {343      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {344        if (blocksCount > 0) {345          blocksCount--;346        } else {347          unsubscribe();348          resolve();349        }350      });351    });352    return promise;353  }354355  async forParachainBlockNumber(blockNumber: bigint) {356    // eslint-disable-next-line no-async-promise-executor357    return new Promise<void>(async (resolve) => {358      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async (data: any) => {359        if (data.number.toNumber() >= blockNumber) {360          unsubscribe();361          resolve();362        }363      });364    });365  }366  367  async forRelayBlockNumber(blockNumber: bigint) {368    // eslint-disable-next-line no-async-promise-executor369    return new Promise<void>(async (resolve) => {370      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData(async (data: any) => {371        if (data.value.relayParentNumber.toNumber() >= blockNumber) {372          // @ts-ignore373          unsubscribe();374          resolve();375        }376      });377    });378  }379380  async event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {381    // eslint-disable-next-line no-async-promise-executor382    const promise = new Promise<EventRecord | null>(async (resolve) => {383      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {384        const blockNumber = header.number.toHuman();385        const blockHash = header.hash;386        const eventIdStr = `${eventSection}.${eventMethod}`;387        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;388  389        console.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);390  391        const apiAt = await this.helper.getApi().at(blockHash);392        const eventRecords = (await apiAt.query.system.events()) as any;393  394        const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {395          return r.event.section == eventSection && r.event.method == eventMethod;396        });397  398        if (neededEvent) {399          unsubscribe();400          resolve(neededEvent);401        } else if (maxBlocksToWait > 0) {402          maxBlocksToWait--;403        } else {404          console.log(`Event \`${eventIdStr}\` is NOT found`);405  406          unsubscribe();407          resolve(null);408        }409      });410    });411    return promise;412  }413}414415class AdminGroup {416  helper: UniqueHelper;417418  constructor(helper: UniqueHelper) {419    this.helper = helper;420  }421422  async payoutStakers(signer: IKeyringPair, stakersToPayout: number) {423    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);424    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => {425      return {426        staker: e.event.data[0].toString(),427        stake: e.event.data[1].toBigInt(),428        payout: e.event.data[2].toBigInt(),429      };430    });431  }432}