git.delta.rocks / unique-network / refs/commits / eee52efe2cf2

difftreelog

source

tests/src/util/playgrounds/unique.dev.ts16.9 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {stringToU8a} from '@polkadot/util';5import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';6import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper} 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} from './types';12import {FrameSystemEventRecord} from '@polkadot/types/lookup';13import {VoidFn} from '@polkadot/api/types';1415export class SilentLogger {16  log(_msg: any, _level: any): void { }17  level = {18    ERROR: 'ERROR' as const,19    WARNING: 'WARNING' as const,20    INFO: 'INFO' as const,21  };22}2324export class SilentConsole {25  // TODO: Remove, this is temporary: Filter unneeded API output26  // (Jaco promised it will be removed in the next version)27  consoleErr: any;28  consoleLog: any;29  consoleWarn: any;3031  constructor() {32    this.consoleErr = console.error;33    this.consoleLog = console.log;34    this.consoleWarn = console.warn;35  }3637  enable() {  38    const outFn = (printer: any) => (...args: any[]) => {39      for (const arg of args) {40        if (typeof arg !== 'string')41          continue;42        if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')43          return;44      }45      printer(...args);46    };47  48    console.error = outFn(this.consoleErr.bind(console));49    console.log = outFn(this.consoleLog.bind(console));50    console.warn = outFn(this.consoleWarn.bind(console));51  }5253  disable() {54    console.error = this.consoleErr;55    console.log = this.consoleLog;56    console.warn = this.consoleWarn;57  }58}5960export class DevUniqueHelper extends UniqueHelper {61  /**62   * Arrange methods for tests63   */64  arrange: ArrangeGroup;65  wait: WaitGroup;66  admin: AdminGroup;6768  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {69    options.helperBase = options.helperBase ?? DevUniqueHelper;7071    super(logger, options);72    this.arrange = new ArrangeGroup(this);73    this.wait = new WaitGroup(this);74    this.admin = new AdminGroup(this);75  }7677  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {78    const wsProvider = new WsProvider(wsEndpoint);79    this.api = new ApiPromise({80      provider: wsProvider,81      signedExtensions: {82        ContractHelpers: {83          extrinsic: {},84          payload: {},85        },86        FakeTransactionFinalizer: {87          extrinsic: {},88          payload: {},89        },90      },91      rpc: {92        unique: defs.unique.rpc,93        appPromotion: defs.appPromotion.rpc,94        rmrk: defs.rmrk.rpc,95        eth: {96          feeHistory: {97            description: 'Dummy',98            params: [],99            type: 'u8',100          },101          maxPriorityFeePerGas: {102            description: 'Dummy',103            params: [],104            type: 'u8',105          },106        },107      },108    });109    await this.api.isReadyOrError;110    this.network = await UniqueHelper.detectNetwork(this.api);111  }112}113114export class DevRelayHelper extends RelayHelper {}115116export class DevWestmintHelper extends WestmintHelper {117  wait: WaitGroup;118119  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {120    options.helperBase = options.helperBase ?? DevWestmintHelper;121122    super(logger, options);123    this.wait = new WaitGroup(this);124  }125}126127export class DevMoonbeamHelper extends MoonbeamHelper {128  account: MoonbeamAccountGroup;129  wait: WaitGroup;130131  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {132    options.helperBase = options.helperBase ?? DevMoonbeamHelper;133134    super(logger, options);135    this.account = new MoonbeamAccountGroup(this);136    this.wait = new WaitGroup(this);137  }138}139140export class DevMoonriverHelper extends DevMoonbeamHelper {}141142export class DevAcalaHelper extends AcalaHelper {143  wait: WaitGroup;144145  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {146    options.helperBase = options.helperBase ?? DevAcalaHelper;147148    super(logger, options);149    this.wait = new WaitGroup(this);150  }151}152153export class DevKaruraHelper extends DevAcalaHelper {}154155class ArrangeGroup {156  helper: DevUniqueHelper;157158  scheduledIdSlider = 0;159160  constructor(helper: DevUniqueHelper) {161    this.helper = helper;162  }163164  /**165   * Generates accounts with the specified UNQ token balance 166   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.167   * @param donor donor account for balances168   * @returns array of newly created accounts169   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor); 170   */171  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {172    let nonce = await this.helper.chain.getNonce(donor.address);173    const wait = new WaitGroup(this.helper);174    const ss58Format = this.helper.chain.getChainProperties().ss58Format;175    const tokenNominal = this.helper.balance.getOneTokenNominal();176    const transactions = [];177    const accounts: IKeyringPair[] = [];178    for (const balance of balances) {179      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);180      accounts.push(recipient);181      if (balance !== 0n) {182        const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);183        transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));184        nonce++;185      }186    }187188    await Promise.all(transactions).catch(_e => {});189    190    //#region TODO remove this region, when nonce problem will be solved191    const checkBalances = async () => {192      let isSuccess = true;193      for (let i = 0; i < balances.length; i++) {194        const balance = await this.helper.balance.getSubstrate(accounts[i].address);195        if (balance !== balances[i] * tokenNominal) {196          isSuccess = false;197          break;198        }199      }200      return isSuccess;201    };202203    let accountsCreated = false;204    const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;205    // checkBalances retry up to 5-50 blocks206    for (let index = 0; index < maxBlocksChecked; index++) {207      accountsCreated = await checkBalances();208      if(accountsCreated) break;209      await wait.newBlocks(1);210    }211212    if (!accountsCreated) throw Error('Accounts generation failed');213    //#endregion214215    return accounts;216  };217218  // TODO combine this method and createAccounts into one219  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {  220    const createAsManyAsCan = async () => {221      let transactions: any = [];222      const accounts: IKeyringPair[] = [];223      let nonce = await this.helper.chain.getNonce(donor.address);224      const tokenNominal = this.helper.balance.getOneTokenNominal();225      for (let i = 0; i < accountsToCreate; i++) {226        if (i === 500) { // if there are too many accounts to create227          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled 228          transactions = []; //229          nonce = await this.helper.chain.getNonce(donor.address); // update nonce 230        }231        const recepient = this.helper.util.fromSeed(mnemonicGenerate());232        accounts.push(recepient);233        if (withBalance !== 0n) {234          const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, withBalance * tokenNominal]);235          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));236          nonce++;237        }238      }239      240      const fullfilledAccounts = [];241      await Promise.allSettled(transactions);242      for (const account of accounts) {243        const accountBalance = await this.helper.balance.getSubstrate(account.address);244        if (accountBalance === withBalance * tokenNominal) {245          fullfilledAccounts.push(account);246        }247      }248      return fullfilledAccounts;249    };250251    252    const crowd: IKeyringPair[] = [];253    // do up to 5 retries254    for (let index = 0; index < 5 && accountsToCreate !== 0; index++) {255      const asManyAsCan = await createAsManyAsCan();256      crowd.push(...asManyAsCan);257      accountsToCreate -= asManyAsCan.length;258    }259260    if (accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);261262    return crowd;263  };264265  isDevNode = async () => {266    let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();267    if (blockNumber == 0) {268      await this.helper.wait.newBlocks(1); 269      blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();270    }271    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);272    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);273    const findCreationDate = async (block: any) => {274      const humanBlock = block.toHuman();275      let date;276      humanBlock.block.extrinsics.forEach((ext: any) => {277        if(ext.method.section === 'timestamp') {278          date = Number(ext.method.args.now.replaceAll(',', ''));279        }280      });281      return date;282    };283    const block1date = await findCreationDate(block1);284    const block2date = await findCreationDate(block2);285    if(block2date! - block1date! < 9000) return true;286  };287  288  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {289    const address = payer.Substrate ? payer.Substrate : await this.helper.address.ethToSubstrate(payer.Ethereum!);290    let balance = await this.helper.balance.getSubstrate(address); 291    292    await promise();293    294    balance -= await this.helper.balance.getSubstrate(address);295    296    return balance;297  }298299  calculatePalletAddress(palletId: any) {300    const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));301    return encodeAddress(address);302  }303304  async makeScheduledIds(num: number): Promise<string[]> {305    await this.helper.wait.noScheduledTasks();306307    function makeId(slider: number) {308      const scheduledIdSize = 32;309      const hexId = slider.toString(16);310      const prefixSize = scheduledIdSize - hexId.length;311312      const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;313314      return scheduledId;  315    }316317    const ids = [];318    for (let i = 0; i < num; i++) {319      ids.push(makeId(this.scheduledIdSlider));320      this.scheduledIdSlider += 1;321    }322323    return ids;324  }325326  async makeScheduledId(): Promise<string> {327    return (await this.makeScheduledIds(1))[0];328  }329330  async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {331    const capture = new EventCapture(this.helper, eventSection, eventMethod);332    await capture.startCapture();333334    return capture;335  }336}337338class MoonbeamAccountGroup {339  helper: MoonbeamHelper;340341  keyring: Keyring;342  _alithAccount: IKeyringPair;343  _baltatharAccount: IKeyringPair;344  _dorothyAccount: IKeyringPair;345346  constructor(helper: MoonbeamHelper) {347    this.helper = helper;348349    this.keyring = new Keyring({type: 'ethereum'});350    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';351    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';352    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';353354    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');355    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');356    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');357  }358359  alithAccount() {360    return this._alithAccount;361  }362363  baltatharAccount() {364    return this._baltatharAccount;365  }366367  dorothyAccount() {368    return this._dorothyAccount;369  }370371  create() {372    return this.keyring.addFromUri(mnemonicGenerate());373  }374}375376class WaitGroup {377  helper: ChainHelperBase;378379  constructor(helper: ChainHelperBase) {380    this.helper = helper;381  }382383  /**384   * Wait for specified number of blocks385   * @param blocksCount number of blocks to wait386   * @returns 387   */388  async newBlocks(blocksCount = 1): Promise<void> {389    // eslint-disable-next-line no-async-promise-executor390    const promise = new Promise<void>(async (resolve) => {391      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {392        if (blocksCount > 0) {393          blocksCount--;394        } else {395          unsubscribe();396          resolve();397        }398      });399    });400    return promise;401  }402403  async forParachainBlockNumber(blockNumber: bigint) {404    // eslint-disable-next-line no-async-promise-executor405    return new Promise<void>(async (resolve) => {406      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async (data: any) => {407        if (data.number.toNumber() >= blockNumber) {408          unsubscribe();409          resolve();410        }411      });412    });413  }414  415  async forRelayBlockNumber(blockNumber: bigint) {416    // eslint-disable-next-line no-async-promise-executor417    return new Promise<void>(async (resolve) => {418      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData(async (data: any) => {419        if (data.value.relayParentNumber.toNumber() >= blockNumber) {420          // @ts-ignore421          unsubscribe();422          resolve();423        }424      });425    });426  }427428  async noScheduledTasks() {429    const api = this.helper.getApi();430    431    // eslint-disable-next-line no-async-promise-executor432    const promise = new Promise<void>(async resolve => {433      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {434        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();435436        if(areThereScheduledTasks.length == 0) {437          unsubscribe();438          resolve();439        }440      }); 441    });442443    return promise;444  }445446  async event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {447    // eslint-disable-next-line no-async-promise-executor448    const promise = new Promise<EventRecord | null>(async (resolve) => {449      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {450        const blockNumber = header.number.toHuman();451        const blockHash = header.hash;452        const eventIdStr = `${eventSection}.${eventMethod}`;453        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;454  455        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);456  457        const apiAt = await this.helper.getApi().at(blockHash);458        const eventRecords = (await apiAt.query.system.events()) as any;459  460        const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {461          return r.event.section == eventSection && r.event.method == eventMethod;462        });463  464        if (neededEvent) {465          unsubscribe();466          resolve(neededEvent);467        } else if (maxBlocksToWait > 0) {468          maxBlocksToWait--;469        } else {470          this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);471          unsubscribe();472          resolve(null);473        }474      });475    });476    return promise;477  }478}479480class EventCapture {481  helper: DevUniqueHelper;482  eventSection: string;483  eventMethod: string;484  events: EventRecord[] = [];485  unsubscribe: VoidFn | null = null;486487  constructor(488    helper: DevUniqueHelper,489    eventSection: string,490    eventMethod: string,491  ) {492    this.helper = helper;493    this.eventSection = eventSection;494    this.eventMethod = eventMethod;495  }496497  async startCapture() {498    this.stopCapture();499    this.unsubscribe = await this.helper.getApi().query.system.events(eventRecords => {500      const newEvents = eventRecords.filter(r => {501        return r.event.section == this.eventSection && r.event.method == this.eventMethod;502      });503504      this.events.push(...newEvents);505    });506  }507508  stopCapture() {509    if (this.unsubscribe !== null) {510      this.unsubscribe();511    }512  }513514  extractCapturedEvents() {515    return this.events;516  }517}518519class AdminGroup {520  helper: UniqueHelper;521522  constructor(helper: UniqueHelper) {523    this.helper = helper;524  }525526  async payoutStakers(signer: IKeyringPair, stakersToPayout: number) {527    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);528    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => {529      return {530        staker: e.event.data[0].toString(),531        stake: e.event.data[1].toBigInt(),532        payout: e.event.data[2].toBigInt(),533      };534    });535  }536}