difftreelog
test add event asserts (#982)
in: master
* add events asserts * fix eslint * eslint fix - unused vars
7 files changed
tests/src/benchmarks/nesting/index.tsdiffbeforeafterboth--- a/tests/src/benchmarks/nesting/index.ts
+++ b/tests/src/benchmarks/nesting/index.ts
@@ -1,17 +1,11 @@
import {EthUniqueHelper, usingEthPlaygrounds} from '../../eth/util';
import {readFile} from 'fs/promises';
-import {
- ICrossAccountId,
-} from '../../util/playgrounds/types';
import {IKeyringPair} from '@polkadot/types/types';
-import {UniqueNFTCollection} from '../../util/playgrounds/unique';
import {Contract} from 'web3-eth-contract';
-import {createObjectCsvWriter} from 'csv-writer';
-import {convertToTokens, createCollectionForBenchmarks, PERMISSIONS, PROPERTIES} from '../utils/common';
+import {convertToTokens} from '../utils/common';
import {makeNames} from '../../util';
import {ContractImports} from '../../eth/util/playgrounds/types';
import {RMRKNestableMintable} from './ABIGEN';
-import {rm} from 'fs';
const {dirname} = makeNames(import.meta.url);
tests/src/eth/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -162,7 +162,7 @@
const malignant = await helper.eth.createAccountWithBalance(donor);
const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner);
- const {collectionId: collectionIdB, contract: contractB} = await createNestingCollection(helper, owner);
+ const {contract: contractB} = await createNestingCollection(helper, owner);
await contractA.methods.setCollectionNesting([true, false, [contractA.options.address, contractB.options.address]]).send({from: owner});
tests/src/fetchMetadata.tsdiffbeforeafterboth--- a/tests/src/fetchMetadata.ts
+++ b/tests/src/fetchMetadata.ts
@@ -1,16 +1,16 @@
-import { writeFile } from "fs/promises";
-import { join } from "path";
-import { exit } from "process";
-import { fileURLToPath } from "url";
+import {writeFile} from 'fs/promises';
+import {join} from 'path';
+import {exit} from 'process';
+import {fileURLToPath} from 'url';
const url = process.env.RPC_URL;
-if (!url) throw new Error('RPC_URL is not set');
+if(!url) throw new Error('RPC_URL is not set');
const srcDir = fileURLToPath(new URL('.', import.meta.url));
-for (let i = 0; i < 10; i++) {
+for(let i = 0; i < 10; i++) {
try {
- console.log(`Trying to fetch metadata, retry ${i + 1}/${10}`)
+ console.log(`Trying to fetch metadata, retry ${i + 1}/${10}`);
const response = await fetch(url, {
method: 'POST',
headers: {
tests/src/governance/technicalCommittee.test.tsdiffbeforeafterboth--- a/tests/src/governance/technicalCommittee.test.ts
+++ b/tests/src/governance/technicalCommittee.test.ts
@@ -36,8 +36,8 @@
});
});
- async function proposalFromAllCommittee(proposal: any) {
- return await usingPlaygrounds(async (helper) => {
+ function proposalFromAllCommittee(proposal: any) {
+ return usingPlaygrounds(async (helper) => {
expect((await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON().length).to.be.equal(allTechCommitteeThreshold);
const proposeResult = await helper.technicalCommittee.collective.propose(
techcomms.andy,
@@ -54,7 +54,13 @@
await helper.technicalCommittee.collective.vote(techcomms.constantine, proposalHash, proposalIndex, true);
await helper.technicalCommittee.collective.vote(techcomms.greg, proposalHash, proposalIndex, true);
- return await helper.technicalCommittee.collective.close(techcomms.andy, proposalHash, proposalIndex);
+ const closeResult = await helper.technicalCommittee.collective.close(techcomms.andy, proposalHash, proposalIndex);
+ Event.TechnicalCommittee.Closed.expect(closeResult);
+ Event.TechnicalCommittee.Approved.expect(closeResult);
+ const {result} = Event.TechnicalCommittee.Executed.expect(closeResult);
+ expect(result).to.eq('Ok');
+
+ return closeResult;
});
}
@@ -64,16 +70,16 @@
await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);
- await expect(proposalFromAllCommittee(helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0)))
- .to.be.fulfilled;
+ const fastTrackProposal = await proposalFromAllCommittee(helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0));
+ Event.Democracy.Started.expect(fastTrackProposal);
});
itSub('TechComm can cancel Democracy proposals', async ({helper}) => {
const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);
const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;
- await expect(proposalFromAllCommittee(helper.democracy.cancelProposalCall(proposalIndex)))
- .to.be.fulfilled;
+ const cancelProposal = await proposalFromAllCommittee(helper.democracy.cancelProposalCall(proposalIndex));
+ Event.Democracy.ProposalCanceled.expect(cancelProposal);
});
itSub('TechComm can cancel ongoing Democracy referendums', async ({helper}) => {
@@ -81,19 +87,19 @@
const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);
const referendumIndex = startedEvent.referendumIndex;
- await expect(proposalFromAllCommittee(helper.democracy.emergencyCancelCall(referendumIndex)))
- .to.be.fulfilled;
+ const emergencyCancelProposal = await proposalFromAllCommittee(helper.democracy.emergencyCancelCall(referendumIndex));
+ Event.Democracy.Cancelled.expect(emergencyCancelProposal);
});
-
itSub('TechComm member can veto Democracy proposals', async ({helper}) => {
const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);
await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);
- await expect(helper.technicalCommittee.collective.execute(
+ const vetoExternalCall = await helper.technicalCommittee.collective.execute(
techcomms.andy,
helper.democracy.vetoExternalCall(preimageHash),
- )).to.be.fulfilled;
+ );
+ Event.Democracy.Vetoed.expect(vetoExternalCall);
});
itSub('TechComm can cancel Fellowship referendums', async ({helper}) => {
@@ -108,7 +114,8 @@
defaultEnactmentMoment,
);
const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;
- await expect(proposalFromAllCommittee(helper.fellowship.referenda.cancelCall(referendumIndex))).to.be.fulfilled;
+ const cancelProposal = await proposalFromAllCommittee(helper.fellowship.referenda.cancelCall(referendumIndex));
+ Event.FellowshipReferenda.Cancelled.expect(cancelProposal);
});
itSub.skip('TechComm member can add a Fellowship member', async ({helper}) => {
tests/src/maintenance.seqtest.tsdiffbeforeafterboth--- a/tests/src/maintenance.seqtest.ts
+++ b/tests/src/maintenance.seqtest.ts
@@ -18,7 +18,6 @@
import {ApiPromise} from '@polkadot/api';
import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util';
import {itEth} from './eth/util';
-import {UniqueHelper} from './util/playgrounds/unique';
import {main as correctState} from './migrations/correctStateAfterMaintenance';
async function maintenanceEnabled(api: ApiPromise): Promise<boolean> {
tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth1// 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, ITransactionResult, TSigner} from './types';12import {FrameSystemEventRecord, XcmV2TraitsError} 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 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'];45 const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false);46 if(needToSkip || arg === 'Normal connection closure')47 return;48 }49 printer(...args);50 };5152 console.error = outFn(this.consoleErr.bind(console));53 console.log = outFn(this.consoleLog.bind(console));54 console.warn = outFn(this.consoleWarn.bind(console));55 }5657 disable() {58 console.error = this.consoleErr;59 console.log = this.consoleLog;60 console.warn = this.consoleWarn;61 }62}6364export interface IEventHelper {65 section(): string;6667 method(): string;6869 wrapEvent(data: any[]): any;70}7172// eslint-disable-next-line @typescript-eslint/naming-convention73function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {74 const helperClass = class implements IEventHelper {75 wrapEvent: (data: any[]) => any;76 _section: string;77 _method: string;7879 constructor() {80 this.wrapEvent = wrapEvent;81 this._section = section;82 this._method = method;83 }8485 section(): string {86 return this._section;87 }8889 method(): string {90 return this._method;91 }9293 filter(txres: ITransactionResult) {94 return txres.result.events.filter(e => e.event.section === section && e.event.method === method)95 .map(e => this.wrapEvent(e.event.data));96 }9798 find(txres: ITransactionResult) {99 const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);100 return e ? this.wrapEvent(e.event.data) : null;101 }102103 expect(txres: ITransactionResult) {104 const e = this.find(txres);105 if(e) {106 return e;107 } else {108 throw Error(`Expected event ${section}.${method}`);109 }110 }111 };112113 return helperClass;114}115116function eventJsonData<T = any>(data: any[], index: number) {117 return data[index].toJSON() as T;118}119120function eventHumanData(data: any[], index: number) {121 return data[index].toHuman();122}123124function eventData<T = any>(data: any[], index: number) {125 return data[index] as T;126}127128// eslint-disable-next-line @typescript-eslint/naming-convention129function EventSection(section: string) {130 return class Section {131 static section = section;132133 static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {134 const helperClass = EventHelper(Section.section, name, wrapEvent);135 return new helperClass();136 }137 };138}139140function schedulerSection(schedulerInstance: string) {141 return class extends EventSection(schedulerInstance) {142 static Dispatched = this.Method('Dispatched', data => ({143 task: eventJsonData(data, 0),144 id: eventHumanData(data, 1),145 result: data[2],146 }));147148 static PriorityChanged = this.Method('PriorityChanged', data => ({149 task: eventJsonData(data, 0),150 priority: eventJsonData(data, 1),151 }));152 };153}154155export class Event {156 static Democracy = class extends EventSection('democracy') {157 static Proposed = this.Method('Proposed', data => ({158 proposalIndex: eventJsonData<number>(data, 0),159 }));160161 static ExternalTabled = this.Method('ExternalTabled');162163 static Started = this.Method('Started', data => ({164 referendumIndex: eventJsonData<number>(data, 0),165 threshold: eventHumanData(data, 1),166 }));167168 static Voted = this.Method('Voted', data => ({169 voter: eventJsonData(data, 0),170 referendumIndex: eventJsonData<number>(data, 1),171 vote: eventJsonData(data, 2),172 }));173174 static Passed = this.Method('Passed', data => ({175 referendumIndex: eventJsonData<number>(data, 0),176 }));177 };178179 static Council = class extends EventSection('council') {180 static Proposed = this.Method('Proposed', data => ({181 account: eventHumanData(data, 0),182 proposalIndex: eventJsonData<number>(data, 1),183 proposalHash: eventHumanData(data, 2),184 threshold: eventJsonData<number>(data, 3),185 }));186 static Closed = this.Method('Closed', data => ({187 proposalHash: eventHumanData(data, 0),188 yes: eventJsonData<number>(data, 1),189 no: eventJsonData<number>(data, 2),190 }));191 };192193 static TechnicalCommittee = class extends EventSection('technicalCommittee') {194 static Proposed = this.Method('Proposed', data => ({195 account: eventHumanData(data, 0),196 proposalIndex: eventJsonData<number>(data, 1),197 proposalHash: eventHumanData(data, 2),198 threshold: eventJsonData<number>(data, 3),199 }));200 static Closed = this.Method('Closed', data => ({201 proposalHash: eventHumanData(data, 0),202 yes: eventJsonData<number>(data, 1),203 no: eventJsonData<number>(data, 2),204 }));205 };206207 static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {208 static Submitted = this.Method('Submitted', data => ({209 referendumIndex: eventJsonData<number>(data, 0),210 trackId: eventJsonData<number>(data, 1),211 proposal: eventJsonData(data, 2),212 }));213 };214215 static UniqueScheduler = schedulerSection('uniqueScheduler');216 static Scheduler = schedulerSection('scheduler');217218 static XcmpQueue = class extends EventSection('xcmpQueue') {219 static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({220 messageHash: eventJsonData(data, 0),221 }));222223 static Fail = this.Method('Fail', data => ({224 messageHash: eventJsonData(data, 0),225 outcome: eventData<XcmV2TraitsError>(data, 1),226 }));227 };228}229230export class DevUniqueHelper extends UniqueHelper {231 /**232 * Arrange methods for tests233 */234 arrange: ArrangeGroup;235 wait: WaitGroup;236 admin: AdminGroup;237 session: SessionGroup;238 testUtils: TestUtilGroup;239240 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {241 options.helperBase = options.helperBase ?? DevUniqueHelper;242243 super(logger, options);244 this.arrange = new ArrangeGroup(this);245 this.wait = new WaitGroup(this);246 this.admin = new AdminGroup(this);247 this.testUtils = new TestUtilGroup(this);248 this.session = new SessionGroup(this);249 }250251 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {252 const wsProvider = new WsProvider(wsEndpoint);253 this.api = new ApiPromise({254 provider: wsProvider,255 signedExtensions: {256 ContractHelpers: {257 extrinsic: {},258 payload: {},259 },260 CheckMaintenance: {261 extrinsic: {},262 payload: {},263 },264 DisableIdentityCalls: {265 extrinsic: {},266 payload: {},267 },268 FakeTransactionFinalizer: {269 extrinsic: {},270 payload: {},271 },272 },273 rpc: {274 unique: defs.unique.rpc,275 appPromotion: defs.appPromotion.rpc,276 povinfo: defs.povinfo.rpc,277 eth: {278 feeHistory: {279 description: 'Dummy',280 params: [],281 type: 'u8',282 },283 maxPriorityFeePerGas: {284 description: 'Dummy',285 params: [],286 type: 'u8',287 },288 },289 },290 });291 await this.api.isReadyOrError;292 this.network = await UniqueHelper.detectNetwork(this.api);293 this.wsEndpoint = wsEndpoint;294 }295}296297export class DevRelayHelper extends RelayHelper {298 wait: WaitGroup;299300 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {301 options.helperBase = options.helperBase ?? DevRelayHelper;302303 super(logger, options);304 this.wait = new WaitGroup(this);305 }306}307308export class DevWestmintHelper extends WestmintHelper {309 wait: WaitGroup;310311 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {312 options.helperBase = options.helperBase ?? DevWestmintHelper;313314 super(logger, options);315 this.wait = new WaitGroup(this);316 }317}318319export class DevStatemineHelper extends DevWestmintHelper {}320321export class DevStatemintHelper extends DevWestmintHelper {}322323export class DevMoonbeamHelper extends MoonbeamHelper {324 account: MoonbeamAccountGroup;325 wait: WaitGroup;326 fastDemocracy: MoonbeamFastDemocracyGroup;327328 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {329 options.helperBase = options.helperBase ?? DevMoonbeamHelper;330 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';331332 super(logger, options);333 this.account = new MoonbeamAccountGroup(this);334 this.wait = new WaitGroup(this);335 this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);336 }337}338339export class DevMoonriverHelper extends DevMoonbeamHelper {340 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {341 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';342 super(logger, options);343 }344}345346export class DevAstarHelper extends AstarHelper {347 wait: WaitGroup;348349 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {350 options.helperBase = options.helperBase ?? DevAstarHelper;351352 super(logger, options);353 this.wait = new WaitGroup(this);354 }355}356357export class DevShidenHelper extends AstarHelper {358 wait: WaitGroup;359360 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {361 options.helperBase = options.helperBase ?? DevShidenHelper;362363 super(logger, options);364 this.wait = new WaitGroup(this);365 }366}367368export class DevAcalaHelper extends AcalaHelper {369 wait: WaitGroup;370371 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {372 options.helperBase = options.helperBase ?? DevAcalaHelper;373374 super(logger, options);375 this.wait = new WaitGroup(this);376 }377}378379export class DevKaruraHelper extends DevAcalaHelper {}380381export class ArrangeGroup {382 helper: DevUniqueHelper;383384 scheduledIdSlider = 0;385386 constructor(helper: DevUniqueHelper) {387 this.helper = helper;388 }389390 /**391 * Generates accounts with the specified UNQ token balance392 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.393 * @param donor donor account for balances394 * @returns array of newly created accounts395 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);396 */397 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {398 let nonce = await this.helper.chain.getNonce(donor.address);399 const wait = new WaitGroup(this.helper);400 const ss58Format = this.helper.chain.getChainProperties().ss58Format;401 const tokenNominal = this.helper.balance.getOneTokenNominal();402 const transactions = [];403 const accounts: IKeyringPair[] = [];404 for(const balance of balances) {405 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);406 accounts.push(recipient);407 if(balance !== 0n) {408 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);409 transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));410 nonce++;411 }412 }413414 await Promise.all(transactions).catch(_e => {});415416 //#region TODO remove this region, when nonce problem will be solved417 const checkBalances = async () => {418 let isSuccess = true;419 for(let i = 0; i < balances.length; i++) {420 const balance = await this.helper.balance.getSubstrate(accounts[i].address);421 if(balance !== balances[i] * tokenNominal) {422 isSuccess = false;423 break;424 }425 }426 return isSuccess;427 };428429 let accountsCreated = false;430 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;431 // checkBalances retry up to 5-50 blocks432 for(let index = 0; index < maxBlocksChecked; index++) {433 accountsCreated = await checkBalances();434 if(accountsCreated) break;435 await wait.newBlocks(1);436 }437438 if(!accountsCreated) throw Error('Accounts generation failed');439 //#endregion440441 return accounts;442 };443444 // TODO combine this method and createAccounts into one445 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {446 const createAsManyAsCan = async () => {447 let transactions: any = [];448 const accounts: IKeyringPair[] = [];449 let nonce = await this.helper.chain.getNonce(donor.address);450 const tokenNominal = this.helper.balance.getOneTokenNominal();451 const ss58Format = this.helper.chain.getChainProperties().ss58Format;452 for(let i = 0; i < accountsToCreate; i++) {453 if(i === 500) { // if there are too many accounts to create454 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled455 transactions = []; //456 nonce = await this.helper.chain.getNonce(donor.address); // update nonce457 }458 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);459 accounts.push(recipient);460 if(withBalance !== 0n) {461 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);462 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));463 nonce++;464 }465 }466467 const fullfilledAccounts = [];468 await Promise.allSettled(transactions);469 for(const account of accounts) {470 const accountBalance = await this.helper.balance.getSubstrate(account.address);471 if(accountBalance === withBalance * tokenNominal) {472 fullfilledAccounts.push(account);473 }474 }475 return fullfilledAccounts;476 };477478479 const crowd: IKeyringPair[] = [];480 // do up to 5 retries481 for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {482 const asManyAsCan = await createAsManyAsCan();483 crowd.push(...asManyAsCan);484 accountsToCreate -= asManyAsCan.length;485 }486487 if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);488489 return crowd;490 };491492 /**493 * Generates one account with zero balance494 * @returns the newly generated account495 * @example const account = await helper.arrange.createEmptyAccount();496 */497 createEmptyAccount = (): IKeyringPair => {498 const ss58Format = this.helper.chain.getChainProperties().ss58Format;499 return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);500 };501502 isDevNode = async () => {503 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();504 if(blockNumber == 0) {505 await this.helper.wait.newBlocks(1);506 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();507 }508 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);509 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);510 const findCreationDate = (block: any) => {511 const humanBlock = block.toHuman();512 let date;513 humanBlock.block.extrinsics.forEach((ext: any) => {514 if(ext.method.section === 'timestamp') {515 date = Number(ext.method.args.now.replaceAll(',', ''));516 }517 });518 return date;519 };520 const block1date = await findCreationDate(block1);521 const block2date = await findCreationDate(block2);522 if(block2date! - block1date! < 9000) return true;523 };524525 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {526 const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);527 let balance = await this.helper.balance.getSubstrate(address);528529 await promise();530531 balance -= await this.helper.balance.getSubstrate(address);532533 return balance;534 }535536 async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {537 const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);538539 const kvJson: {[key: string]: string} = {};540541 for(const kv of rawPovInfo.keyValues) {542 kvJson[kv.key.toHex()] = kv.value.toHex();543 }544545 const kvStr = JSON.stringify(kvJson);546547 const chainql = spawnSync(548 'chainql',549 [550 `--tla-code=data=${kvStr}`,551 '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,552 ],553 );554555 if(!chainql.stdout) {556 throw Error('unable to get an output from the `chainql`');557 }558559 return {560 proofSize: rawPovInfo.proofSize.toNumber(),561 compactProofSize: rawPovInfo.compactProofSize.toNumber(),562 compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),563 results: rawPovInfo.results,564 kv: JSON.parse(chainql.stdout.toString()),565 };566 }567568 calculatePalletAddress(palletId: any) {569 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));570 return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);571 }572573 makeScheduledIds(num: number): string[] {574 function makeId(slider: number) {575 const scheduledIdSize = 64;576 const hexId = slider.toString(16);577 const prefixSize = scheduledIdSize - hexId.length;578579 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;580581 return scheduledId;582 }583584 const ids = [];585 for(let i = 0; i < num; i++) {586 ids.push(makeId(this.scheduledIdSlider));587 this.scheduledIdSlider += 1;588 }589590 return ids;591 }592593 makeScheduledId(): string {594 return (this.makeScheduledIds(1))[0];595 }596597 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {598 const capture = new EventCapture(this.helper, eventSection, eventMethod);599 await capture.startCapture();600601 return capture;602 }603604 makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {605 return {606 V2: [607 {608 WithdrawAsset: [609 {610 id,611 fun: {612 Fungible: amount,613 },614 },615 ],616 },617 {618 BuyExecution: {619 fees: {620 id,621 fun: {622 Fungible: amount,623 },624 },625 weightLimit: 'Unlimited',626 },627 },628 {629 DepositAsset: {630 assets: {631 Wild: 'All',632 },633 maxAssets: 1,634 beneficiary: {635 parents: 0,636 interior: {637 X1: {638 AccountId32: {639 network: 'Any',640 id: beneficiary,641 },642 },643 },644 },645 },646 },647 ],648 };649 }650651 makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {652 return {653 V2: [654 {655 ReserveAssetDeposited: [656 {657 id,658 fun: {659 Fungible: amount,660 },661 },662 ],663 },664 {665 BuyExecution: {666 fees: {667 id,668 fun: {669 Fungible: amount,670 },671 },672 weightLimit: 'Unlimited',673 },674 },675 {676 DepositAsset: {677 assets: {678 Wild: 'All',679 },680 maxAssets: 1,681 beneficiary: {682 parents: 0,683 interior: {684 X1: {685 AccountId32: {686 network: 'Any',687 id: beneficiary,688 },689 },690 },691 },692 },693 },694 ],695 };696 }697}698699class MoonbeamAccountGroup {700 helper: MoonbeamHelper;701702 keyring: Keyring;703 _alithAccount: IKeyringPair;704 _baltatharAccount: IKeyringPair;705 _dorothyAccount: IKeyringPair;706707 constructor(helper: MoonbeamHelper) {708 this.helper = helper;709710 this.keyring = new Keyring({type: 'ethereum'});711 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';712 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';713 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';714715 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');716 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');717 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');718 }719720 alithAccount() {721 return this._alithAccount;722 }723724 baltatharAccount() {725 return this._baltatharAccount;726 }727728 dorothyAccount() {729 return this._dorothyAccount;730 }731732 create() {733 return this.keyring.addFromUri(mnemonicGenerate());734 }735}736737class MoonbeamFastDemocracyGroup {738 helper: DevMoonbeamHelper;739740 constructor(helper: DevMoonbeamHelper) {741 this.helper = helper;742 }743744 async executeProposal(proposalDesciption: string, encodedProposal: string) {745 const proposalHash = blake2AsHex(encodedProposal);746747 const alithAccount = this.helper.account.alithAccount();748 const baltatharAccount = this.helper.account.baltatharAccount();749 const dorothyAccount = this.helper.account.dorothyAccount();750751 const councilVotingThreshold = 2;752 const technicalCommitteeThreshold = 2;753 const fastTrackVotingPeriod = 3;754 const fastTrackDelayPeriod = 0;755756 console.log(`[democracy] executing '${proposalDesciption}' proposal`);757758 // >>> Propose external motion through council >>>759 console.log('\t* Propose external motion through council.......');760 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});761 const encodedMotion = externalMotion?.method.toHex() || '';762 const motionHash = blake2AsHex(encodedMotion);763 console.log('\t* Motion hash is %s', motionHash);764765 await this.helper.collective.council.propose(766 baltatharAccount,767 councilVotingThreshold,768 externalMotion,769 externalMotion.encodedLength,770 );771772 const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;773 await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);774 await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);775776 await this.helper.collective.council.close(777 dorothyAccount,778 motionHash,779 councilProposalIdx,780 {781 refTime: 1_000_000_000,782 proofSize: 1_000_000,783 },784 externalMotion.encodedLength,785 );786 console.log('\t* Propose external motion through council.......DONE');787 // <<< Propose external motion through council <<<788789 // >>> Fast track proposal through technical committee >>>790 console.log('\t* Fast track proposal through technical committee.......');791 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);792 const encodedFastTrack = fastTrack?.method.toHex() || '';793 const fastTrackHash = blake2AsHex(encodedFastTrack);794 console.log('\t* FastTrack hash is %s', fastTrackHash);795796 await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);797798 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;799 await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);800 await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);801802 await this.helper.collective.techCommittee.close(803 baltatharAccount,804 fastTrackHash,805 techProposalIdx,806 {807 refTime: 1_000_000_000,808 proofSize: 1_000_000,809 },810 fastTrack.encodedLength,811 );812 console.log('\t* Fast track proposal through technical committee.......DONE');813 // <<< Fast track proposal through technical committee <<<814815 const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);816 const referendumIndex = democracyStarted.referendumIndex;817818 // >>> Referendum voting >>>819 console.log(`\t* Referendum #${referendumIndex} voting.......`);820 await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {821 balance: 10_000_000_000_000_000_000n,822 vote: {aye: true, conviction: 1},823 });824 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);825 // <<< Referendum voting <<<826827 // Wait the proposal to pass828 await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);829830 await this.helper.wait.newBlocks(1);831832 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);833 }834}835836class WaitGroup {837 helper: ChainHelperBase;838839 constructor(helper: ChainHelperBase) {840 this.helper = helper;841 }842843 sleep(milliseconds: number) {844 return new Promise((resolve) => setTimeout(resolve, milliseconds));845 }846847 private async waitWithTimeout(promise: Promise<any>, timeout: number) {848 let isBlock = false;849 promise.then(() => isBlock = true).catch(() => isBlock = true);850 let totalTime = 0;851 const step = 100;852 while(!isBlock) {853 await this.sleep(step);854 totalTime += step;855 if(totalTime >= timeout) throw Error('Blocks production failed');856 }857 return promise;858 }859860 /**861 * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.862 * @param promise async operation to race against the timeout863 * @param timeoutMS time after which to time out864 * @param timeoutError error message to throw865 * @returns promise of the same type the operation had866 */867 withTimeout<T>(868 promise: Promise<T>,869 timeoutMS = 30000,870 timeoutError = 'The operation has timed out!',871 ): Promise<T> {872 const timeout = new Promise<never>((_, reject) => {873 setTimeout(() => {874 reject(new Error(timeoutError));875 }, timeoutMS);876 });877878 return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});879 }880881 /**882 * Wait for specified number of blocks883 * @param blocksCount number of blocks to wait884 * @returns885 */886 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {887 timeout = timeout ?? blocksCount * 60_000;888 // eslint-disable-next-line no-async-promise-executor889 const promise = new Promise<void>(async (resolve) => {890 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {891 if(blocksCount > 0) {892 blocksCount--;893 } else {894 unsubscribe();895 resolve();896 }897 });898 });899 await this.waitWithTimeout(promise, timeout);900 return promise;901 }902903 /**904 * Wait for the specified number of sessions to pass.905 * Only applicable if the Session pallet is turned on.906 * @param sessionCount number of sessions to wait907 * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks908 * @returns909 */910 async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {911 console.log(`Waiting for ${sessionCount} new session${sessionCount>1's'''}.`912 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');913914 const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;915 let currentSessionIndex = -1;916917 while(currentSessionIndex < expectedSessionIndex) {918 // eslint-disable-next-line no-async-promise-executor919 currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {920 await this.newBlocks(1);921 const res = await (this.helper as DevUniqueHelper).session.getIndex();922 resolve(res);923 }), blockTimeout, 'The chain has stopped producing blocks!');924 }925 }926927 async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {928 timeout = timeout ?? 30 * 60 * 1000;929 // eslint-disable-next-line no-async-promise-executor930 const promise = new Promise<void>(async (resolve) => {931 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {932 if(data.number.toNumber() >= blockNumber) {933 unsubscribe();934 resolve();935 }936 });937 });938 await this.waitWithTimeout(promise, timeout);939 return promise;940 }941942 async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {943 timeout = timeout ?? 30 * 60 * 1000;944 // eslint-disable-next-line no-async-promise-executor945 const promise = new Promise<void>(async (resolve) => {946 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {947 if(data.value.relayParentNumber.toNumber() >= blockNumber) {948 // @ts-ignore949 unsubscribe();950 resolve();951 }952 });953 });954 await this.waitWithTimeout(promise, timeout);955 return promise;956 }957958 noScheduledTasks() {959 const api = this.helper.getApi();960961 // eslint-disable-next-line no-async-promise-executor962 const promise = new Promise<void>(async resolve => {963 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {964 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();965966 if(areThereScheduledTasks.length == 0) {967 unsubscribe();968 resolve();969 }970 });971 });972973 return promise;974 }975976 parachainBlockMultiplesOf(val: bigint) {977 // eslint-disable-next-line no-async-promise-executor978 const promise = new Promise<void>(async resolve => {979 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {980 if(data.number.toBigInt() % val == 0n) {981 console.log(`from waiter: ${data.number.toBigInt()}`);982 unsubscribe();983 resolve();984 }985 });986 });987 return promise;988 }989990 event<T extends IEventHelper>(991 maxBlocksToWait: number,992 eventHelper: T,993 filter: (_: any) => boolean = () => true,994 ): any {995 // eslint-disable-next-line no-async-promise-executor996 const promise = new Promise<T | null>(async (resolve) => {997 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {998 const blockNumber = header.number.toHuman();999 const blockHash = header.hash;1000 const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1001 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;10021003 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);10041005 const apiAt = await this.helper.getApi().at(blockHash);1006 const eventRecords = (await apiAt.query.system.events()) as any;10071008 const neededEvent = eventRecords.toArray()1009 .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1010 .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1011 .find(filter);10121013 if(neededEvent) {1014 unsubscribe();1015 resolve(neededEvent);1016 } else if(maxBlocksToWait > 0) {1017 maxBlocksToWait--;1018 } else {1019 this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);1020 unsubscribe();1021 resolve(null);1022 }1023 });1024 });1025 return promise;1026 }10271028 async expectEvent<T extends IEventHelper>(1029 maxBlocksToWait: number,1030 eventHelper: T,1031 filter: (e: any) => boolean = () => true,1032 ) {1033 const e = await this.event(maxBlocksToWait, eventHelper, filter);1034 if(e == null) {1035 throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1036 } else {1037 return e;1038 }1039 }1040}10411042class SessionGroup {1043 helper: ChainHelperBase;10441045 constructor(helper: ChainHelperBase) {1046 this.helper = helper;1047 }10481049 //todo:collator documentation1050 async getIndex(): Promise<number> {1051 return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1052 }10531054 newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1055 return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1056 }10571058 setOwnKeys(signer: TSigner, key: string) {1059 return this.helper.executeExtrinsic(1060 signer,1061 'api.tx.session.setKeys',1062 [key, '0x0'],1063 true,1064 );1065 }10661067 setOwnKeysFromAddress(signer: TSigner) {1068 return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1069 }1070}10711072class TestUtilGroup {1073 helper: DevUniqueHelper;10741075 constructor(helper: DevUniqueHelper) {1076 this.helper = helper;1077 }10781079 async enable() {1080 if(this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {1081 return;1082 }10831084 const signer = this.helper.util.fromSeed('//Alice');1085 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1086 }10871088 async setTestValue(signer: TSigner, testVal: number) {1089 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1090 }10911092 async incTestValue(signer: TSigner) {1093 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1094 }10951096 async setTestValueAndRollback(signer: TSigner, testVal: number) {1097 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1098 }10991100 async testValue(blockIdx?: number) {1101 const api = blockIdx1102 ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1103 : this.helper.getApi();11041105 return (await api.query.testUtils.testValue()).toJSON();1106 }11071108 async justTakeFee(signer: TSigner) {1109 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1110 }11111112 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1113 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1114 }1115}11161117class EventCapture {1118 helper: DevUniqueHelper;1119 eventSection: string;1120 eventMethod: string;1121 events: EventRecord[] = [];1122 unsubscribe: VoidFn | null = null;11231124 constructor(1125 helper: DevUniqueHelper,1126 eventSection: string,1127 eventMethod: string,1128 ) {1129 this.helper = helper;1130 this.eventSection = eventSection;1131 this.eventMethod = eventMethod;1132 }11331134 async startCapture() {1135 this.stopCapture();1136 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1137 const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);11381139 this.events.push(...newEvents);1140 })) as any;1141 }11421143 stopCapture() {1144 if(this.unsubscribe !== null) {1145 this.unsubscribe();1146 }1147 }11481149 extractCapturedEvents() {1150 return this.events;1151 }1152}11531154class AdminGroup {1155 helper: UniqueHelper;11561157 constructor(helper: UniqueHelper) {1158 this.helper = helper;1159 }11601161 async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> {1162 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1163 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1164 staker: e.event.data[0].toString(),1165 stake: e.event.data[1].toBigInt(),1166 payout: e.event.data[2].toBigInt(),1167 }));1168 }1169}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, ITransactionResult, TSigner} from './types';12import {FrameSystemEventRecord, XcmV2TraitsError} 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 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'];45 const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false);46 if(needToSkip || arg === 'Normal connection closure')47 return;48 }49 printer(...args);50 };5152 console.error = outFn(this.consoleErr.bind(console));53 console.log = outFn(this.consoleLog.bind(console));54 console.warn = outFn(this.consoleWarn.bind(console));55 }5657 disable() {58 console.error = this.consoleErr;59 console.log = this.consoleLog;60 console.warn = this.consoleWarn;61 }62}6364export interface IEventHelper {65 section(): string;6667 method(): string;6869 wrapEvent(data: any[]): any;70}7172// eslint-disable-next-line @typescript-eslint/naming-convention73function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {74 const helperClass = class implements IEventHelper {75 wrapEvent: (data: any[]) => any;76 _section: string;77 _method: string;7879 constructor() {80 this.wrapEvent = wrapEvent;81 this._section = section;82 this._method = method;83 }8485 section(): string {86 return this._section;87 }8889 method(): string {90 return this._method;91 }9293 filter(txres: ITransactionResult) {94 return txres.result.events.filter(e => e.event.section === section && e.event.method === method)95 .map(e => this.wrapEvent(e.event.data));96 }9798 find(txres: ITransactionResult) {99 const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);100 return e ? this.wrapEvent(e.event.data) : null;101 }102103 expect(txres: ITransactionResult) {104 const e = this.find(txres);105 if(e) {106 return e;107 } else {108 throw Error(`Expected event ${section}.${method}`);109 }110 }111 };112113 return helperClass;114}115116function eventJsonData<T = any>(data: any[], index: number) {117 return data[index].toJSON() as T;118}119120function eventHumanData(data: any[], index: number) {121 return data[index].toHuman();122}123124function eventData<T = any>(data: any[], index: number) {125 return data[index] as T;126}127128// eslint-disable-next-line @typescript-eslint/naming-convention129function EventSection(section: string) {130 return class Section {131 static section = section;132133 static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {134 const helperClass = EventHelper(Section.section, name, wrapEvent);135 return new helperClass();136 }137 };138}139140function schedulerSection(schedulerInstance: string) {141 return class extends EventSection(schedulerInstance) {142 static Dispatched = this.Method('Dispatched', data => ({143 task: eventJsonData(data, 0),144 id: eventHumanData(data, 1),145 result: data[2],146 }));147148 static PriorityChanged = this.Method('PriorityChanged', data => ({149 task: eventJsonData(data, 0),150 priority: eventJsonData(data, 1),151 }));152 };153}154155export class Event {156 static Democracy = class extends EventSection('democracy') {157 static Proposed = this.Method('Proposed', data => ({158 proposalIndex: eventJsonData<number>(data, 0),159 }));160161 static ExternalTabled = this.Method('ExternalTabled');162163 static Started = this.Method('Started', data => ({164 referendumIndex: eventJsonData<number>(data, 0),165 threshold: eventHumanData(data, 1),166 }));167168 static Voted = this.Method('Voted', data => ({169 voter: eventJsonData(data, 0),170 referendumIndex: eventJsonData<number>(data, 1),171 vote: eventJsonData(data, 2),172 }));173174 static Passed = this.Method('Passed', data => ({175 referendumIndex: eventJsonData<number>(data, 0),176 }));177178 static ProposalCanceled = this.Method('ProposalCanceled', data => ({179 propIndex: eventJsonData<number>(data, 0),180 }));181182 static Cancelled = this.Method('Cancelled', data => ({183 propIndex: eventJsonData<number>(data, 0),184 }));185186 static Vetoed = this.Method('Vetoed', data => ({187 who: eventHumanData(data, 0),188 proposalHash: eventHumanData(data, 1),189 until: eventJsonData<number>(data, 1),190 }));191 };192193 static Council = class extends EventSection('council') {194 static Proposed = this.Method('Proposed', data => ({195 account: eventHumanData(data, 0),196 proposalIndex: eventJsonData<number>(data, 1),197 proposalHash: eventHumanData(data, 2),198 threshold: eventJsonData<number>(data, 3),199 }));200 static Closed = this.Method('Closed', data => ({201 proposalHash: eventHumanData(data, 0),202 yes: eventJsonData<number>(data, 1),203 no: eventJsonData<number>(data, 2),204 }));205 static Executed = this.Method('Executed', data => ({206 proposalHash: eventHumanData(data, 0),207 }));208 };209210 static TechnicalCommittee = class extends EventSection('technicalCommittee') {211 static Proposed = this.Method('Proposed', data => ({212 account: eventHumanData(data, 0),213 proposalIndex: eventJsonData<number>(data, 1),214 proposalHash: eventHumanData(data, 2),215 threshold: eventJsonData<number>(data, 3),216 }));217 static Closed = this.Method('Closed', data => ({218 proposalHash: eventHumanData(data, 0),219 yes: eventJsonData<number>(data, 1),220 no: eventJsonData<number>(data, 2),221 }));222 static Approved = this.Method('Approved', data => ({223 proposalHash: eventHumanData(data, 0),224 }));225 static Executed = this.Method('Executed', data => ({226 proposalHash: eventHumanData(data, 0),227 result: eventHumanData(data, 1),228 }));229 };230231 static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {232 static Submitted = this.Method('Submitted', data => ({233 referendumIndex: eventJsonData<number>(data, 0),234 trackId: eventJsonData<number>(data, 1),235 proposal: eventJsonData(data, 2),236 }));237238 static Cancelled = this.Method('Cancelled', data => ({239 index: eventJsonData<number>(data, 0),240 tally: eventJsonData(data, 1),241 }));242 };243244 static UniqueScheduler = schedulerSection('uniqueScheduler');245 static Scheduler = schedulerSection('scheduler');246247 static XcmpQueue = class extends EventSection('xcmpQueue') {248 static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({249 messageHash: eventJsonData(data, 0),250 }));251252 static Fail = this.Method('Fail', data => ({253 messageHash: eventJsonData(data, 0),254 outcome: eventData<XcmV2TraitsError>(data, 1),255 }));256 };257}258259export class DevUniqueHelper extends UniqueHelper {260 /**261 * Arrange methods for tests262 */263 arrange: ArrangeGroup;264 wait: WaitGroup;265 admin: AdminGroup;266 session: SessionGroup;267 testUtils: TestUtilGroup;268269 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {270 options.helperBase = options.helperBase ?? DevUniqueHelper;271272 super(logger, options);273 this.arrange = new ArrangeGroup(this);274 this.wait = new WaitGroup(this);275 this.admin = new AdminGroup(this);276 this.testUtils = new TestUtilGroup(this);277 this.session = new SessionGroup(this);278 }279280 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {281 const wsProvider = new WsProvider(wsEndpoint);282 this.api = new ApiPromise({283 provider: wsProvider,284 signedExtensions: {285 ContractHelpers: {286 extrinsic: {},287 payload: {},288 },289 CheckMaintenance: {290 extrinsic: {},291 payload: {},292 },293 DisableIdentityCalls: {294 extrinsic: {},295 payload: {},296 },297 FakeTransactionFinalizer: {298 extrinsic: {},299 payload: {},300 },301 },302 rpc: {303 unique: defs.unique.rpc,304 appPromotion: defs.appPromotion.rpc,305 povinfo: defs.povinfo.rpc,306 eth: {307 feeHistory: {308 description: 'Dummy',309 params: [],310 type: 'u8',311 },312 maxPriorityFeePerGas: {313 description: 'Dummy',314 params: [],315 type: 'u8',316 },317 },318 },319 });320 await this.api.isReadyOrError;321 this.network = await UniqueHelper.detectNetwork(this.api);322 this.wsEndpoint = wsEndpoint;323 }324}325326export class DevRelayHelper extends RelayHelper {327 wait: WaitGroup;328329 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {330 options.helperBase = options.helperBase ?? DevRelayHelper;331332 super(logger, options);333 this.wait = new WaitGroup(this);334 }335}336337export class DevWestmintHelper extends WestmintHelper {338 wait: WaitGroup;339340 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {341 options.helperBase = options.helperBase ?? DevWestmintHelper;342343 super(logger, options);344 this.wait = new WaitGroup(this);345 }346}347348export class DevStatemineHelper extends DevWestmintHelper {}349350export class DevStatemintHelper extends DevWestmintHelper {}351352export class DevMoonbeamHelper extends MoonbeamHelper {353 account: MoonbeamAccountGroup;354 wait: WaitGroup;355 fastDemocracy: MoonbeamFastDemocracyGroup;356357 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {358 options.helperBase = options.helperBase ?? DevMoonbeamHelper;359 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';360361 super(logger, options);362 this.account = new MoonbeamAccountGroup(this);363 this.wait = new WaitGroup(this);364 this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);365 }366}367368export class DevMoonriverHelper extends DevMoonbeamHelper {369 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {370 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';371 super(logger, options);372 }373}374375export class DevAstarHelper extends AstarHelper {376 wait: WaitGroup;377378 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {379 options.helperBase = options.helperBase ?? DevAstarHelper;380381 super(logger, options);382 this.wait = new WaitGroup(this);383 }384}385386export class DevShidenHelper extends AstarHelper {387 wait: WaitGroup;388389 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {390 options.helperBase = options.helperBase ?? DevShidenHelper;391392 super(logger, options);393 this.wait = new WaitGroup(this);394 }395}396397export class DevAcalaHelper extends AcalaHelper {398 wait: WaitGroup;399400 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {401 options.helperBase = options.helperBase ?? DevAcalaHelper;402403 super(logger, options);404 this.wait = new WaitGroup(this);405 }406}407408export class DevKaruraHelper extends DevAcalaHelper {}409410export class ArrangeGroup {411 helper: DevUniqueHelper;412413 scheduledIdSlider = 0;414415 constructor(helper: DevUniqueHelper) {416 this.helper = helper;417 }418419 /**420 * Generates accounts with the specified UNQ token balance421 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.422 * @param donor donor account for balances423 * @returns array of newly created accounts424 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);425 */426 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {427 let nonce = await this.helper.chain.getNonce(donor.address);428 const wait = new WaitGroup(this.helper);429 const ss58Format = this.helper.chain.getChainProperties().ss58Format;430 const tokenNominal = this.helper.balance.getOneTokenNominal();431 const transactions = [];432 const accounts: IKeyringPair[] = [];433 for(const balance of balances) {434 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);435 accounts.push(recipient);436 if(balance !== 0n) {437 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);438 transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));439 nonce++;440 }441 }442443 await Promise.all(transactions).catch(_e => {});444445 //#region TODO remove this region, when nonce problem will be solved446 const checkBalances = async () => {447 let isSuccess = true;448 for(let i = 0; i < balances.length; i++) {449 const balance = await this.helper.balance.getSubstrate(accounts[i].address);450 if(balance !== balances[i] * tokenNominal) {451 isSuccess = false;452 break;453 }454 }455 return isSuccess;456 };457458 let accountsCreated = false;459 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;460 // checkBalances retry up to 5-50 blocks461 for(let index = 0; index < maxBlocksChecked; index++) {462 accountsCreated = await checkBalances();463 if(accountsCreated) break;464 await wait.newBlocks(1);465 }466467 if(!accountsCreated) throw Error('Accounts generation failed');468 //#endregion469470 return accounts;471 };472473 // TODO combine this method and createAccounts into one474 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {475 const createAsManyAsCan = async () => {476 let transactions: any = [];477 const accounts: IKeyringPair[] = [];478 let nonce = await this.helper.chain.getNonce(donor.address);479 const tokenNominal = this.helper.balance.getOneTokenNominal();480 const ss58Format = this.helper.chain.getChainProperties().ss58Format;481 for(let i = 0; i < accountsToCreate; i++) {482 if(i === 500) { // if there are too many accounts to create483 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled484 transactions = []; //485 nonce = await this.helper.chain.getNonce(donor.address); // update nonce486 }487 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);488 accounts.push(recipient);489 if(withBalance !== 0n) {490 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);491 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));492 nonce++;493 }494 }495496 const fullfilledAccounts = [];497 await Promise.allSettled(transactions);498 for(const account of accounts) {499 const accountBalance = await this.helper.balance.getSubstrate(account.address);500 if(accountBalance === withBalance * tokenNominal) {501 fullfilledAccounts.push(account);502 }503 }504 return fullfilledAccounts;505 };506507508 const crowd: IKeyringPair[] = [];509 // do up to 5 retries510 for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {511 const asManyAsCan = await createAsManyAsCan();512 crowd.push(...asManyAsCan);513 accountsToCreate -= asManyAsCan.length;514 }515516 if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);517518 return crowd;519 };520521 /**522 * Generates one account with zero balance523 * @returns the newly generated account524 * @example const account = await helper.arrange.createEmptyAccount();525 */526 createEmptyAccount = (): IKeyringPair => {527 const ss58Format = this.helper.chain.getChainProperties().ss58Format;528 return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);529 };530531 isDevNode = async () => {532 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();533 if(blockNumber == 0) {534 await this.helper.wait.newBlocks(1);535 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();536 }537 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);538 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);539 const findCreationDate = (block: any) => {540 const humanBlock = block.toHuman();541 let date;542 humanBlock.block.extrinsics.forEach((ext: any) => {543 if(ext.method.section === 'timestamp') {544 date = Number(ext.method.args.now.replaceAll(',', ''));545 }546 });547 return date;548 };549 const block1date = await findCreationDate(block1);550 const block2date = await findCreationDate(block2);551 if(block2date! - block1date! < 9000) return true;552 };553554 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {555 const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);556 let balance = await this.helper.balance.getSubstrate(address);557558 await promise();559560 balance -= await this.helper.balance.getSubstrate(address);561562 return balance;563 }564565 async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {566 const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);567568 const kvJson: {[key: string]: string} = {};569570 for(const kv of rawPovInfo.keyValues) {571 kvJson[kv.key.toHex()] = kv.value.toHex();572 }573574 const kvStr = JSON.stringify(kvJson);575576 const chainql = spawnSync(577 'chainql',578 [579 `--tla-code=data=${kvStr}`,580 '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,581 ],582 );583584 if(!chainql.stdout) {585 throw Error('unable to get an output from the `chainql`');586 }587588 return {589 proofSize: rawPovInfo.proofSize.toNumber(),590 compactProofSize: rawPovInfo.compactProofSize.toNumber(),591 compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),592 results: rawPovInfo.results,593 kv: JSON.parse(chainql.stdout.toString()),594 };595 }596597 calculatePalletAddress(palletId: any) {598 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));599 return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);600 }601602 makeScheduledIds(num: number): string[] {603 function makeId(slider: number) {604 const scheduledIdSize = 64;605 const hexId = slider.toString(16);606 const prefixSize = scheduledIdSize - hexId.length;607608 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;609610 return scheduledId;611 }612613 const ids = [];614 for(let i = 0; i < num; i++) {615 ids.push(makeId(this.scheduledIdSlider));616 this.scheduledIdSlider += 1;617 }618619 return ids;620 }621622 makeScheduledId(): string {623 return (this.makeScheduledIds(1))[0];624 }625626 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {627 const capture = new EventCapture(this.helper, eventSection, eventMethod);628 await capture.startCapture();629630 return capture;631 }632633 makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {634 return {635 V2: [636 {637 WithdrawAsset: [638 {639 id,640 fun: {641 Fungible: amount,642 },643 },644 ],645 },646 {647 BuyExecution: {648 fees: {649 id,650 fun: {651 Fungible: amount,652 },653 },654 weightLimit: 'Unlimited',655 },656 },657 {658 DepositAsset: {659 assets: {660 Wild: 'All',661 },662 maxAssets: 1,663 beneficiary: {664 parents: 0,665 interior: {666 X1: {667 AccountId32: {668 network: 'Any',669 id: beneficiary,670 },671 },672 },673 },674 },675 },676 ],677 };678 }679680 makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {681 return {682 V2: [683 {684 ReserveAssetDeposited: [685 {686 id,687 fun: {688 Fungible: amount,689 },690 },691 ],692 },693 {694 BuyExecution: {695 fees: {696 id,697 fun: {698 Fungible: amount,699 },700 },701 weightLimit: 'Unlimited',702 },703 },704 {705 DepositAsset: {706 assets: {707 Wild: 'All',708 },709 maxAssets: 1,710 beneficiary: {711 parents: 0,712 interior: {713 X1: {714 AccountId32: {715 network: 'Any',716 id: beneficiary,717 },718 },719 },720 },721 },722 },723 ],724 };725 }726}727728class MoonbeamAccountGroup {729 helper: MoonbeamHelper;730731 keyring: Keyring;732 _alithAccount: IKeyringPair;733 _baltatharAccount: IKeyringPair;734 _dorothyAccount: IKeyringPair;735736 constructor(helper: MoonbeamHelper) {737 this.helper = helper;738739 this.keyring = new Keyring({type: 'ethereum'});740 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';741 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';742 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';743744 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');745 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');746 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');747 }748749 alithAccount() {750 return this._alithAccount;751 }752753 baltatharAccount() {754 return this._baltatharAccount;755 }756757 dorothyAccount() {758 return this._dorothyAccount;759 }760761 create() {762 return this.keyring.addFromUri(mnemonicGenerate());763 }764}765766class MoonbeamFastDemocracyGroup {767 helper: DevMoonbeamHelper;768769 constructor(helper: DevMoonbeamHelper) {770 this.helper = helper;771 }772773 async executeProposal(proposalDesciption: string, encodedProposal: string) {774 const proposalHash = blake2AsHex(encodedProposal);775776 const alithAccount = this.helper.account.alithAccount();777 const baltatharAccount = this.helper.account.baltatharAccount();778 const dorothyAccount = this.helper.account.dorothyAccount();779780 const councilVotingThreshold = 2;781 const technicalCommitteeThreshold = 2;782 const fastTrackVotingPeriod = 3;783 const fastTrackDelayPeriod = 0;784785 console.log(`[democracy] executing '${proposalDesciption}' proposal`);786787 // >>> Propose external motion through council >>>788 console.log('\t* Propose external motion through council.......');789 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});790 const encodedMotion = externalMotion?.method.toHex() || '';791 const motionHash = blake2AsHex(encodedMotion);792 console.log('\t* Motion hash is %s', motionHash);793794 await this.helper.collective.council.propose(795 baltatharAccount,796 councilVotingThreshold,797 externalMotion,798 externalMotion.encodedLength,799 );800801 const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;802 await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);803 await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);804805 await this.helper.collective.council.close(806 dorothyAccount,807 motionHash,808 councilProposalIdx,809 {810 refTime: 1_000_000_000,811 proofSize: 1_000_000,812 },813 externalMotion.encodedLength,814 );815 console.log('\t* Propose external motion through council.......DONE');816 // <<< Propose external motion through council <<<817818 // >>> Fast track proposal through technical committee >>>819 console.log('\t* Fast track proposal through technical committee.......');820 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);821 const encodedFastTrack = fastTrack?.method.toHex() || '';822 const fastTrackHash = blake2AsHex(encodedFastTrack);823 console.log('\t* FastTrack hash is %s', fastTrackHash);824825 await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);826827 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;828 await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);829 await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);830831 await this.helper.collective.techCommittee.close(832 baltatharAccount,833 fastTrackHash,834 techProposalIdx,835 {836 refTime: 1_000_000_000,837 proofSize: 1_000_000,838 },839 fastTrack.encodedLength,840 );841 console.log('\t* Fast track proposal through technical committee.......DONE');842 // <<< Fast track proposal through technical committee <<<843844 const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);845 const referendumIndex = democracyStarted.referendumIndex;846847 // >>> Referendum voting >>>848 console.log(`\t* Referendum #${referendumIndex} voting.......`);849 await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {850 balance: 10_000_000_000_000_000_000n,851 vote: {aye: true, conviction: 1},852 });853 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);854 // <<< Referendum voting <<<855856 // Wait the proposal to pass857 await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);858859 await this.helper.wait.newBlocks(1);860861 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);862 }863}864865class WaitGroup {866 helper: ChainHelperBase;867868 constructor(helper: ChainHelperBase) {869 this.helper = helper;870 }871872 sleep(milliseconds: number) {873 return new Promise((resolve) => setTimeout(resolve, milliseconds));874 }875876 private async waitWithTimeout(promise: Promise<any>, timeout: number) {877 let isBlock = false;878 promise.then(() => isBlock = true).catch(() => isBlock = true);879 let totalTime = 0;880 const step = 100;881 while(!isBlock) {882 await this.sleep(step);883 totalTime += step;884 if(totalTime >= timeout) throw Error('Blocks production failed');885 }886 return promise;887 }888889 /**890 * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.891 * @param promise async operation to race against the timeout892 * @param timeoutMS time after which to time out893 * @param timeoutError error message to throw894 * @returns promise of the same type the operation had895 */896 withTimeout<T>(897 promise: Promise<T>,898 timeoutMS = 30000,899 timeoutError = 'The operation has timed out!',900 ): Promise<T> {901 const timeout = new Promise<never>((_, reject) => {902 setTimeout(() => {903 reject(new Error(timeoutError));904 }, timeoutMS);905 });906907 return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});908 }909910 /**911 * Wait for specified number of blocks912 * @param blocksCount number of blocks to wait913 * @returns914 */915 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {916 timeout = timeout ?? blocksCount * 60_000;917 // eslint-disable-next-line no-async-promise-executor918 const promise = new Promise<void>(async (resolve) => {919 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {920 if(blocksCount > 0) {921 blocksCount--;922 } else {923 unsubscribe();924 resolve();925 }926 });927 });928 await this.waitWithTimeout(promise, timeout);929 return promise;930 }931932 /**933 * Wait for the specified number of sessions to pass.934 * Only applicable if the Session pallet is turned on.935 * @param sessionCount number of sessions to wait936 * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks937 * @returns938 */939 async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {940 console.log(`Waiting for ${sessionCount} new session${sessionCount>1's'''}.`941 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');942943 const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;944 let currentSessionIndex = -1;945946 while(currentSessionIndex < expectedSessionIndex) {947 // eslint-disable-next-line no-async-promise-executor948 currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {949 await this.newBlocks(1);950 const res = await (this.helper as DevUniqueHelper).session.getIndex();951 resolve(res);952 }), blockTimeout, 'The chain has stopped producing blocks!');953 }954 }955956 async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {957 timeout = timeout ?? 30 * 60 * 1000;958 // eslint-disable-next-line no-async-promise-executor959 const promise = new Promise<void>(async (resolve) => {960 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {961 if(data.number.toNumber() >= blockNumber) {962 unsubscribe();963 resolve();964 }965 });966 });967 await this.waitWithTimeout(promise, timeout);968 return promise;969 }970971 async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {972 timeout = timeout ?? 30 * 60 * 1000;973 // eslint-disable-next-line no-async-promise-executor974 const promise = new Promise<void>(async (resolve) => {975 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {976 if(data.value.relayParentNumber.toNumber() >= blockNumber) {977 // @ts-ignore978 unsubscribe();979 resolve();980 }981 });982 });983 await this.waitWithTimeout(promise, timeout);984 return promise;985 }986987 noScheduledTasks() {988 const api = this.helper.getApi();989990 // eslint-disable-next-line no-async-promise-executor991 const promise = new Promise<void>(async resolve => {992 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {993 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();994995 if(areThereScheduledTasks.length == 0) {996 unsubscribe();997 resolve();998 }999 });1000 });10011002 return promise;1003 }10041005 parachainBlockMultiplesOf(val: bigint) {1006 // eslint-disable-next-line no-async-promise-executor1007 const promise = new Promise<void>(async resolve => {1008 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1009 if(data.number.toBigInt() % val == 0n) {1010 console.log(`from waiter: ${data.number.toBigInt()}`);1011 unsubscribe();1012 resolve();1013 }1014 });1015 });1016 return promise;1017 }10181019 event<T extends IEventHelper>(1020 maxBlocksToWait: number,1021 eventHelper: T,1022 filter: (_: any) => boolean = () => true,1023 ): any {1024 // eslint-disable-next-line no-async-promise-executor1025 const promise = new Promise<T | null>(async (resolve) => {1026 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1027 const blockNumber = header.number.toHuman();1028 const blockHash = header.hash;1029 const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1030 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;10311032 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);10331034 const apiAt = await this.helper.getApi().at(blockHash);1035 const eventRecords = (await apiAt.query.system.events()) as any;10361037 const neededEvent = eventRecords.toArray()1038 .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1039 .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1040 .find(filter);10411042 if(neededEvent) {1043 unsubscribe();1044 resolve(neededEvent);1045 } else if(maxBlocksToWait > 0) {1046 maxBlocksToWait--;1047 } else {1048 this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);1049 unsubscribe();1050 resolve(null);1051 }1052 });1053 });1054 return promise;1055 }10561057 async expectEvent<T extends IEventHelper>(1058 maxBlocksToWait: number,1059 eventHelper: T,1060 filter: (e: any) => boolean = () => true,1061 ) {1062 const e = await this.event(maxBlocksToWait, eventHelper, filter);1063 if(e == null) {1064 throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1065 } else {1066 return e;1067 }1068 }1069}10701071class SessionGroup {1072 helper: ChainHelperBase;10731074 constructor(helper: ChainHelperBase) {1075 this.helper = helper;1076 }10771078 //todo:collator documentation1079 async getIndex(): Promise<number> {1080 return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1081 }10821083 newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1084 return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1085 }10861087 setOwnKeys(signer: TSigner, key: string) {1088 return this.helper.executeExtrinsic(1089 signer,1090 'api.tx.session.setKeys',1091 [key, '0x0'],1092 true,1093 );1094 }10951096 setOwnKeysFromAddress(signer: TSigner) {1097 return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1098 }1099}11001101class TestUtilGroup {1102 helper: DevUniqueHelper;11031104 constructor(helper: DevUniqueHelper) {1105 this.helper = helper;1106 }11071108 async enable() {1109 if(this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {1110 return;1111 }11121113 const signer = this.helper.util.fromSeed('//Alice');1114 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1115 }11161117 async setTestValue(signer: TSigner, testVal: number) {1118 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1119 }11201121 async incTestValue(signer: TSigner) {1122 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1123 }11241125 async setTestValueAndRollback(signer: TSigner, testVal: number) {1126 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1127 }11281129 async testValue(blockIdx?: number) {1130 const api = blockIdx1131 ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1132 : this.helper.getApi();11331134 return (await api.query.testUtils.testValue()).toJSON();1135 }11361137 async justTakeFee(signer: TSigner) {1138 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1139 }11401141 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1142 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1143 }1144}11451146class EventCapture {1147 helper: DevUniqueHelper;1148 eventSection: string;1149 eventMethod: string;1150 events: EventRecord[] = [];1151 unsubscribe: VoidFn | null = null;11521153 constructor(1154 helper: DevUniqueHelper,1155 eventSection: string,1156 eventMethod: string,1157 ) {1158 this.helper = helper;1159 this.eventSection = eventSection;1160 this.eventMethod = eventMethod;1161 }11621163 async startCapture() {1164 this.stopCapture();1165 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1166 const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);11671168 this.events.push(...newEvents);1169 })) as any;1170 }11711172 stopCapture() {1173 if(this.unsubscribe !== null) {1174 this.unsubscribe();1175 }1176 }11771178 extractCapturedEvents() {1179 return this.events;1180 }1181}11821183class AdminGroup {1184 helper: UniqueHelper;11851186 constructor(helper: UniqueHelper) {1187 this.helper = helper;1188 }11891190 async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> {1191 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1192 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1193 staker: e.event.data[0].toString(),1194 stake: e.event.data[1].toBigInt(),1195 payout: e.event.data[2].toBigInt(),1196 }));1197 }1198}tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -44,15 +44,11 @@
MoonbeamAssetInfo,
DemocracyStandardAccountVote,
IEthCrossAccountId,
- CollectionFlag,
IPhasicEvent,
- DemocracySplitAccount,
} from './types';
import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';
import type {Vec} from '@polkadot/types-codec';
-import {FrameSupportPreimagesBounded, FrameSupportScheduleDispatchTime, FrameSystemEventRecord, PalletBalancesIdAmount, PalletDemocracyConviction, PalletDemocracyVoteAccountVote} from '@polkadot/types/lookup';
-import {arrayUnzip} from '@polkadot/util';
-import {Event} from './unique.dev';
+import {FrameSystemEventRecord, PalletDemocracyConviction} from '@polkadot/types/lookup';
export class CrossAccountId {
Substrate!: TSubstrateAccount;