From e4eea6c1f0ba083d16a5d20f67f6b6182493f1a9 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Sat, 06 Jan 2024 11:27:55 +0000 Subject: [PATCH] refactor: js-packages structure --- --- a/.github/workflows/xcm.yml +++ b/.github/workflows/xcm.yml @@ -368,9 +368,9 @@ yarn add mochawesome - name: Call HRMP initialization - working-directory: js-packages/tests + working-directory: js-packages/scripts run: | - yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm util/createHrmp.ts ${{matrix.network}} + yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm createHrmp.ts ${{matrix.network}} - name: Run XCM tests working-directory: js-packages/tests --- a/js-packages/package.json +++ b/js-packages/package.json @@ -26,8 +26,9 @@ "@types/node": "^20.8.10", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", - "@unique/opal-types": "workspace:*", - "@unique/playgrounds": "workspace:*", + "@unique-nft/opal-testnet-types": "workspace:*", + "@unique-nft/playgrounds": "workspace:*", + "@unique/test-utils": "workspace:*", "chai": "^4.3.10", "chai-subset": "^1.6.0", "eslint": "^8.53.0", @@ -59,6 +60,7 @@ "types", "playgrounds", "scripts", + "test-utils", "tests" ] } --- a/js-packages/playgrounds/package.json +++ b/js-packages/playgrounds/package.json @@ -1,18 +1,17 @@ { - "author": "", - "license": "SEE LICENSE IN ../../../LICENSE", - "description": "Playground scripts", + "author": "Unique Network", + "license": "Apache 2.0", + "description": "Helpers for Unique Network chain", "engines": { - "node": ">=16" + "node": ">=18" }, - "name": "@unique/playgrounds", + "name": "@unique-nft/playgrounds", "type": "module", "version": "1.0.0", "main": "unique.js", "dependencies": { "@polkadot/api": "10.10.1", "@polkadot/util": "^12.5.1", - "@polkadot/util-crypto": "^12.5.1", - "@unique/opal-types": "workspace:*" + "@polkadot/util-crypto": "^12.5.1" } } --- a/js-packages/playgrounds/types.xcm.ts +++ /dev/null @@ -1,29 +0,0 @@ -export interface AcalaAssetMetadata { - name: string, - symbol: string, - decimals: number, - minimalBalance: bigint, -} - -export interface MoonbeamAssetInfo { - location: any, - metadata: { - name: string, - symbol: string, - decimals: number, - isFrozen: boolean, - minimalBalance: bigint, - }, - existentialDeposit: bigint, - isSufficient: boolean, - unitsPerSecond: bigint, - numAssetsWeightHint: number, -} - -export interface DemocracyStandardAccountVote { - balance: bigint, - vote: { - aye: boolean, - conviction: number, - }, -} --- a/js-packages/playgrounds/unique.dev.ts +++ /dev/null @@ -1,1646 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -import '@unique/opal-types/augment-api.js'; -import '@unique/opal-types/augment-types.js'; -import '@unique/opal-types/types-lookup.js'; - -import {stringToU8a} from '@polkadot/util'; -import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto'; -import type {ChainHelperBaseConstructor, UniqueHelperConstructor} from './unique.js'; -import {UniqueHelper, ChainHelperBase, HelperGroup} from './unique.js'; -import {ApiPromise, Keyring, WsProvider} from '@polkadot/api'; -import * as defs from '@unique/opal-types/definitions.js'; -import type {IKeyringPair} from '@polkadot/types/types'; -import type {EventRecord} from '@polkadot/types/interfaces'; -import type {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types.js'; -import type {FrameSystemEventRecord, StagingXcmV2TraitsError, StagingXcmV3TraitsOutcome} from '@polkadot/types/lookup'; -import type {SignerOptions, VoidFn} from '@polkadot/api/types'; -import {spawnSync} from 'child_process'; -import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup, HydraDxHelper} from './unique.xcm.js'; -import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance.js'; -import type {ICollectiveGroup, IFellowshipGroup} from './unique.governance.js'; - -export class SilentLogger { - log(_msg: any, _level: any): void { } - level = { - ERROR: 'ERROR' as const, - WARNING: 'WARNING' as const, - INFO: 'INFO' as const, - }; -} - -export class SilentConsole { - // TODO: Remove, this is temporary: Filter unneeded API output - // (Jaco promised it will be removed in the next version) - consoleErr: any; - consoleLog: any; - consoleWarn: any; - - constructor() { - this.consoleErr = console.error; - this.consoleLog = console.log; - this.consoleWarn = console.warn; - } - - enable() { - const outFn = (printer: any) => (...args: any[]) => { - for(const arg of args) { - if(typeof arg !== 'string') - continue; - 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']; - const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false); - if(needToSkip || arg === 'Normal connection closure') - return; - } - printer(...args); - }; - - console.error = outFn(this.consoleErr.bind(console)); - console.log = outFn(this.consoleLog.bind(console)); - console.warn = outFn(this.consoleWarn.bind(console)); - } - - disable() { - console.error = this.consoleErr; - console.log = this.consoleLog; - console.warn = this.consoleWarn; - } -} - -export interface IEventHelper { - section(): string; - - method(): string; - - wrapEvent(data: any[]): any; -} - -// eslint-disable-next-line @typescript-eslint/naming-convention -function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) { - const helperClass = class implements IEventHelper { - wrapEvent: (data: any[]) => any; - _section: string; - _method: string; - - constructor() { - this.wrapEvent = wrapEvent; - this._section = section; - this._method = method; - } - - section(): string { - return this._section; - } - - method(): string { - return this._method; - } - - filter(txres: ITransactionResult) { - return txres.result.events.filter(e => e.event.section === section && e.event.method === method) - .map(e => this.wrapEvent(e.event.data)); - } - - find(txres: ITransactionResult) { - const e = txres.result.events.find(e => e.event.section === section && e.event.method === method); - return e ? this.wrapEvent(e.event.data) : null; - } - - expect(txres: ITransactionResult) { - const e = this.find(txres); - if(e) { - return e; - } else { - throw Error(`Expected event ${section}.${method}`); - } - } - }; - - return helperClass; -} - -function eventJsonData(data: any[], index: number) { - return data[index].toJSON() as T; -} - -function eventHumanData(data: any[], index: number) { - return data[index].toHuman(); -} - -function eventData(data: any[], index: number) { - return data[index] as T; -} - -// eslint-disable-next-line @typescript-eslint/naming-convention -function EventSection(section: string) { - return class Section { - static section = section; - - static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) { - const helperClass = EventHelper(Section.section, name, wrapEvent); - return new helperClass(); - } - }; -} - -function schedulerSection(schedulerInstance: string) { - return class extends EventSection(schedulerInstance) { - static Dispatched = this.Method('Dispatched', data => ({ - task: eventJsonData(data, 0), - id: eventHumanData(data, 1), - result: data[2], - })); - - static PriorityChanged = this.Method('PriorityChanged', data => ({ - task: eventJsonData(data, 0), - priority: eventJsonData(data, 1), - })); - }; -} - -export class Event { - static Democracy = class extends EventSection('democracy') { - static Proposed = this.Method('Proposed', data => ({ - proposalIndex: eventJsonData(data, 0), - })); - - static ExternalTabled = this.Method('ExternalTabled'); - - static Started = this.Method('Started', data => ({ - referendumIndex: eventJsonData(data, 0), - threshold: eventHumanData(data, 1), - })); - - static Voted = this.Method('Voted', data => ({ - voter: eventJsonData(data, 0), - referendumIndex: eventJsonData(data, 1), - vote: eventJsonData(data, 2), - })); - - static Passed = this.Method('Passed', data => ({ - referendumIndex: eventJsonData(data, 0), - })); - - static ProposalCanceled = this.Method('ProposalCanceled', data => ({ - propIndex: eventJsonData(data, 0), - })); - - static Cancelled = this.Method('Cancelled', data => ({ - propIndex: eventJsonData(data, 0), - })); - - static Vetoed = this.Method('Vetoed', data => ({ - who: eventHumanData(data, 0), - proposalHash: eventHumanData(data, 1), - until: eventJsonData(data, 1), - })); - }; - - static Council = class extends EventSection('council') { - static Proposed = this.Method('Proposed', data => ({ - account: eventHumanData(data, 0), - proposalIndex: eventJsonData(data, 1), - proposalHash: eventHumanData(data, 2), - threshold: eventJsonData(data, 3), - })); - static Closed = this.Method('Closed', data => ({ - proposalHash: eventHumanData(data, 0), - yes: eventJsonData(data, 1), - no: eventJsonData(data, 2), - })); - static Executed = this.Method('Executed', data => ({ - proposalHash: eventHumanData(data, 0), - })); - }; - - static TechnicalCommittee = class extends EventSection('technicalCommittee') { - static Proposed = this.Method('Proposed', data => ({ - account: eventHumanData(data, 0), - proposalIndex: eventJsonData(data, 1), - proposalHash: eventHumanData(data, 2), - threshold: eventJsonData(data, 3), - })); - static Closed = this.Method('Closed', data => ({ - proposalHash: eventHumanData(data, 0), - yes: eventJsonData(data, 1), - no: eventJsonData(data, 2), - })); - static Approved = this.Method('Approved', data => ({ - proposalHash: eventHumanData(data, 0), - })); - static Executed = this.Method('Executed', data => ({ - proposalHash: eventHumanData(data, 0), - result: eventHumanData(data, 1), - })); - }; - - static FellowshipReferenda = class extends EventSection('fellowshipReferenda') { - static Submitted = this.Method('Submitted', data => ({ - referendumIndex: eventJsonData(data, 0), - trackId: eventJsonData(data, 1), - proposal: eventJsonData(data, 2), - })); - - static Cancelled = this.Method('Cancelled', data => ({ - index: eventJsonData(data, 0), - tally: eventJsonData(data, 1), - })); - }; - - static UniqueScheduler = schedulerSection('uniqueScheduler'); - static Scheduler = schedulerSection('scheduler'); - - static XcmpQueue = class extends EventSection('xcmpQueue') { - static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({ - messageHash: eventJsonData(data, 0), - })); - - static Success = this.Method('Success', data => ({ - messageHash: eventJsonData(data, 0), - })); - - static Fail = this.Method('Fail', data => ({ - messageHash: eventJsonData(data, 0), - outcome: eventData(data, 2), - })); - }; - - static DmpQueue = class extends EventSection('dmpQueue') { - static ExecutedDownward = this.Method('ExecutedDownward', data => ({ - outcome: eventData(data, 2), - })); - }; -} - -// eslint-disable-next-line @typescript-eslint/naming-convention -export function SudoHelper(Base: T) { - return class extends Base { - constructor(...args: any[]) { - super(...args); - } - - override async executeExtrinsic( - sender: IKeyringPair, - extrinsic: string, - params: any[], - expectSuccess?: boolean, - options: Partial | null = null, - ): Promise { - const call = this.constructApiCall(extrinsic, params); - const result = await super.executeExtrinsic( - sender, - 'api.tx.sudo.sudo', - [call], - expectSuccess, - options, - ); - - if(result.status === 'Fail') return result; - - const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult; - if(data.isErr) { - if(data.asErr.isModule) { - const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule; - const metaError = super.getApi()?.registry.findMetaError(error); - throw new Error(`${metaError.section}.${metaError.name}`); - } else if(data.asErr.isToken) { - throw new Error(`Token: ${data.asErr.asToken}`); - } - // May be [object Object] in case of unhandled non-unit enum - throw new Error(`Misc: ${data.asErr.toHuman()}`); - } - return result; - } - override async executeExtrinsicUncheckedWeight( - sender: IKeyringPair, - extrinsic: string, - params: any[], - expectSuccess?: boolean, - options: Partial | null = null, - ): Promise { - const call = this.constructApiCall(extrinsic, params); - const result = await super.executeExtrinsic( - sender, - 'api.tx.sudo.sudoUncheckedWeight', - [call, {refTime: 0, proofSize: 0}], - expectSuccess, - options, - ); - - if(result.status === 'Fail') return result; - - const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult; - if(data.isErr) { - if(data.asErr.isModule) { - const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule; - const metaError = super.getApi()?.registry.findMetaError(error); - throw new Error(`${metaError.section}.${metaError.name}`); - } else if(data.asErr.isToken) { - throw new Error(`Token: ${data.asErr.asToken}`); - } - // May be [object Object] in case of unhandled non-unit enum - throw new Error(`Misc: ${data.asErr.toHuman()}`); - } - return result; - } - }; -} - -class SchedulerGroup extends HelperGroup { - constructor(helper: UniqueHelper) { - super(helper); - } - - cancelScheduled(signer: TSigner, scheduledId: string) { - return this.helper.executeExtrinsic( - signer, - 'api.tx.scheduler.cancelNamed', - [scheduledId], - true, - ); - } - - changePriority(signer: TSigner, scheduledId: string, priority: number) { - return this.helper.executeExtrinsic( - signer, - 'api.tx.scheduler.changeNamedPriority', - [scheduledId, priority], - true, - ); - } - - scheduleAt( - executionBlockNumber: number, - options: ISchedulerOptions = {}, - ) { - return this.schedule('schedule', executionBlockNumber, options); - } - - scheduleAfter( - blocksBeforeExecution: number, - options: ISchedulerOptions = {}, - ) { - return this.schedule('scheduleAfter', blocksBeforeExecution, options); - } - - schedule( - scheduleFn: 'schedule' | 'scheduleAfter', - blocksNum: number, - options: ISchedulerOptions = {}, - ) { - // eslint-disable-next-line @typescript-eslint/naming-convention - const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase); - return this.helper.clone(ScheduledHelperType, { - scheduleFn, - blocksNum, - options, - }) as T; - } -} - -class CollatorSelectionGroup extends HelperGroup { - //todo:collator documentation - addInvulnerable(signer: TSigner, address: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]); - } - - removeInvulnerable(signer: TSigner, address: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]); - } - - async getInvulnerables(): Promise { - return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman()); - } - - /** and also total max invulnerables */ - maxCollators(): number { - return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number); - } - - async getDesiredCollators(): Promise { - return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber(); - } - - setLicenseBond(signer: TSigner, amount: bigint) { - return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]); - } - - async getLicenseBond(): Promise { - return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt(); - } - - obtainLicense(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []); - } - - releaseLicense(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []); - } - - forceReleaseLicense(signer: TSigner, released: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]); - } - - async hasLicense(address: string): Promise { - return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt(); - } - - onboard(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []); - } - - offboard(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []); - } - - async getCandidates(): Promise { - return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman()); - } -} - -export class DevUniqueHelper extends UniqueHelper { - /** - * Arrange methods for tests - */ - arrange: ArrangeGroup; - wait: WaitGroup; - admin: AdminGroup; - session: SessionGroup; - testUtils: TestUtilGroup; - foreignAssets: ForeignAssetsGroup; - xcm: XcmGroup; - xTokens: XTokensGroup; - tokens: TokensGroup; - scheduler: SchedulerGroup; - collatorSelection: CollatorSelectionGroup; - council: ICollectiveGroup; - technicalCommittee: ICollectiveGroup; - fellowship: IFellowshipGroup; - democracy: DemocracyGroup; - - constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.helperBase = options.helperBase ?? DevUniqueHelper; - - super(logger, options); - this.arrange = new ArrangeGroup(this); - this.wait = new WaitGroup(this); - this.admin = new AdminGroup(this); - this.testUtils = new TestUtilGroup(this); - this.session = new SessionGroup(this); - this.foreignAssets = new ForeignAssetsGroup(this); - this.xcm = new XcmGroup(this, 'polkadotXcm'); - this.xTokens = new XTokensGroup(this); - this.tokens = new TokensGroup(this); - this.scheduler = new SchedulerGroup(this); - this.collatorSelection = new CollatorSelectionGroup(this); - this.council = { - collective: new CollectiveGroup(this, 'council'), - membership: new CollectiveMembershipGroup(this, 'councilMembership'), - }; - this.technicalCommittee = { - collective: new CollectiveGroup(this, 'technicalCommittee'), - membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'), - }; - this.fellowship = { - collective: new RankedCollectiveGroup(this, 'fellowshipCollective'), - referenda: new ReferendaGroup(this, 'fellowshipReferenda'), - }; - this.democracy = new DemocracyGroup(this); - } - - override async connect(wsEndpoint: string, _listeners?: any): Promise { - if(!wsEndpoint) throw new Error('wsEndpoint was not set'); - const wsProvider = new WsProvider(wsEndpoint); - this.api = new ApiPromise({ - provider: wsProvider, - signedExtensions: { - ContractHelpers: { - extrinsic: {}, - payload: {}, - }, - CheckMaintenance: { - extrinsic: {}, - payload: {}, - }, - DisableIdentityCalls: { - extrinsic: {}, - payload: {}, - }, - FakeTransactionFinalizer: { - extrinsic: {}, - payload: {}, - }, - }, - rpc: { - unique: defs.unique.rpc, - appPromotion: defs.appPromotion.rpc, - povinfo: defs.povinfo.rpc, - eth: { - feeHistory: { - description: 'Dummy', - params: [], - type: 'u8', - }, - maxPriorityFeePerGas: { - description: 'Dummy', - params: [], - type: 'u8', - }, - }, - }, - }); - await this.api.isReadyOrError; - this.network = await UniqueHelper.detectNetwork(this.api); - this.wsEndpoint = wsEndpoint; - } - getSudo() { - // eslint-disable-next-line @typescript-eslint/naming-convention - const SudoHelperType = SudoHelper(this.helperBase); - return this.clone(SudoHelperType) as T; - } -} - -export class DevRelayHelper extends RelayHelper { - wait: WaitGroup; - - constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.helperBase = options.helperBase ?? DevRelayHelper; - - super(logger, options); - this.wait = new WaitGroup(this); - } - - getSudo() { - // eslint-disable-next-line @typescript-eslint/naming-convention - const SudoHelperType = SudoHelper(this.helperBase); - return this.clone(SudoHelperType) as DevRelayHelper; - } -} - -export class DevWestmintHelper extends WestmintHelper { - wait: WaitGroup; - - constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.helperBase = options.helperBase ?? DevWestmintHelper; - - super(logger, options); - this.wait = new WaitGroup(this); - } -} - -export class DevStatemineHelper extends DevWestmintHelper {} - -export class DevStatemintHelper extends DevWestmintHelper {} - -export class DevMoonbeamHelper extends MoonbeamHelper { - account: MoonbeamAccountGroup; - wait: WaitGroup; - fastDemocracy: MoonbeamFastDemocracyGroup; - - constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.helperBase = options.helperBase ?? DevMoonbeamHelper; - options.notePreimagePallet = options.notePreimagePallet ?? 'preimage'; - - super(logger, options); - this.account = new MoonbeamAccountGroup(this); - this.wait = new WaitGroup(this); - this.fastDemocracy = new MoonbeamFastDemocracyGroup(this); - } -} - -export class DevMoonriverHelper extends DevMoonbeamHelper { - constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.notePreimagePallet = options.notePreimagePallet ?? 'preimage'; - super(logger, options); - } -} - -export class DevAstarHelper extends AstarHelper { - wait: WaitGroup; - - constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.helperBase = options.helperBase ?? DevAstarHelper; - - super(logger, options); - this.wait = new WaitGroup(this); - } - - getSudo() { - // eslint-disable-next-line @typescript-eslint/naming-convention - const SudoHelperType = SudoHelper(this.helperBase); - return this.clone(SudoHelperType) as T; - } -} - -export class DevShidenHelper extends DevAstarHelper { } - -export class DevAcalaHelper extends AcalaHelper { - wait: WaitGroup; - - constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.helperBase = options.helperBase ?? DevAcalaHelper; - - super(logger, options); - this.wait = new WaitGroup(this); - } - getSudo() { - // eslint-disable-next-line @typescript-eslint/naming-convention - const SudoHelperType = SudoHelper(this.helperBase); - return this.clone(SudoHelperType) as DevAcalaHelper; - } -} - -export class DevPolkadexHelper extends PolkadexHelper { - wait: WaitGroup; - constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.helperBase = options.helperBase ?? PolkadexHelper; - - super(logger, options); - this.wait = new WaitGroup(this); - } - - getSudo() { - // eslint-disable-next-line @typescript-eslint/naming-convention - const SudoHelperType = SudoHelper(this.helperBase); - return this.clone(SudoHelperType) as DevPolkadexHelper; - } -} - -export class DevHydraDxHelper extends HydraDxHelper { - wait: WaitGroup; - fastDemocracy: HydraFastDemocracyGroup; - - constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { - options.helperBase = options.helperBase ?? DevHydraDxHelper; - - super(logger, options); - - this.wait = new WaitGroup(this); - this.fastDemocracy = new HydraFastDemocracyGroup(this); - } -} - -export class DevKaruraHelper extends DevAcalaHelper {} - -export class ArrangeGroup { - helper: DevUniqueHelper; - - scheduledIdSlider = 0; - - constructor(helper: DevUniqueHelper) { - this.helper = helper; - } - - /** - * Generates accounts with the specified UNQ token balance - * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal. - * @param donor donor account for balances - * @returns array of newly created accounts - * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor); - */ - createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise => { - let nonce = await this.helper.chain.getNonce(donor.address); - const wait = new WaitGroup(this.helper); - const ss58Format = this.helper.chain.getChainProperties().ss58Format; - const tokenNominal = this.helper.balance.getOneTokenNominal(); - const transactions = []; - const accounts: IKeyringPair[] = []; - for(const balance of balances) { - const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format); - accounts.push(recipient); - if(balance !== 0n) { - const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, balance * tokenNominal]); - transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation')); - nonce++; - } - } - - await Promise.all(transactions).catch(_e => {}); - - //#region TODO remove this region, when nonce problem will be solved - const checkBalances = async () => { - let isSuccess = true; - for(let i = 0; i < balances.length; i++) { - const balance = await this.helper.balance.getSubstrate(accounts[i].address); - if(balance !== balances[i] * tokenNominal) { - isSuccess = false; - break; - } - } - return isSuccess; - }; - - let accountsCreated = false; - const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5; - // checkBalances retry up to 5-50 blocks - for(let index = 0; index < maxBlocksChecked; index++) { - accountsCreated = await checkBalances(); - if(accountsCreated) break; - await wait.newBlocks(1); - } - - if(!accountsCreated) throw Error('Accounts generation failed'); - //#endregion - - return accounts; - }; - - // TODO combine this method and createAccounts into one - createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise => { - const createAsManyAsCan = async () => { - let transactions: any = []; - const accounts: IKeyringPair[] = []; - let nonce = await this.helper.chain.getNonce(donor.address); - const tokenNominal = this.helper.balance.getOneTokenNominal(); - const ss58Format = this.helper.chain.getChainProperties().ss58Format; - for(let i = 0; i < accountsToCreate; i++) { - if(i === 500) { // if there are too many accounts to create - await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled - transactions = []; // - nonce = await this.helper.chain.getNonce(donor.address); // update nonce - } - const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format); - accounts.push(recipient); - if(withBalance !== 0n) { - const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, withBalance * tokenNominal]); - transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation')); - nonce++; - } - } - - const fullfilledAccounts = []; - await Promise.allSettled(transactions); - for(const account of accounts) { - const accountBalance = await this.helper.balance.getSubstrate(account.address); - if(accountBalance === withBalance * tokenNominal) { - fullfilledAccounts.push(account); - } - } - return fullfilledAccounts; - }; - - - const crowd: IKeyringPair[] = []; - // do up to 5 retries - for(let index = 0; index < 5 && accountsToCreate !== 0; index++) { - const asManyAsCan = await createAsManyAsCan(); - crowd.push(...asManyAsCan); - accountsToCreate -= asManyAsCan.length; - } - - if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`); - - return crowd; - }; - - /** - * Generates one account with zero balance - * @returns the newly generated account - * @example const account = await helper.arrange.createEmptyAccount(); - */ - createEmptyAccount = (): IKeyringPair => { - const ss58Format = this.helper.chain.getChainProperties().ss58Format; - return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format); - }; - - isDevNode = async () => { - let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON(); - if(blockNumber == 0) { - await this.helper.wait.newBlocks(1); - blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON(); - } - const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]); - const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]); - const findCreationDate = (block: any) => { - const humanBlock = block.toHuman(); - let date; - humanBlock.block.extrinsics.forEach((ext: any) => { - if(ext.method.section === 'timestamp') { - date = Number(ext.method.args.now.replaceAll(',', '')); - } - }); - return date; - }; - const block1date = await findCreationDate(block1); - const block2date = await findCreationDate(block2); - if(block2date! - block1date! < 9000) return true; - return false; - }; - - async calculcateFee(payer: ICrossAccountId, promise: () => Promise): Promise { - const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum); - let balance = await this.helper.balance.getSubstrate(address); - - await promise(); - - balance -= await this.helper.balance.getSubstrate(address); - - return balance; - } - - async calculatePoVInfo(txs: any[]): Promise { - const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]); - - const kvJson: {[key: string]: string} = {}; - - for(const kv of rawPovInfo.keyValues) { - kvJson[kv.key.toHex()] = kv.value.toHex(); - } - - const kvStr = JSON.stringify(kvJson); - - const chainql = spawnSync( - 'chainql', - [ - `--tla-code=data=${kvStr}`, - '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`, - ], - ); - - if(!chainql.stdout) { - throw Error('unable to get an output from the `chainql`'); - } - - return { - proofSize: rawPovInfo.proofSize.toNumber(), - compactProofSize: rawPovInfo.compactProofSize.toNumber(), - compressedProofSize: rawPovInfo.compressedProofSize.toNumber(), - results: rawPovInfo.results, - kv: JSON.parse(chainql.stdout.toString()), - }; - } - - calculatePalletAddress(palletId: any) { - const address = stringToU8a(('modl' + palletId).padEnd(32, '\0')); - return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format); - } - - makeScheduledIds(num: number): string[] { - function makeId(slider: number) { - const scheduledIdSize = 64; - const hexId = slider.toString(16); - const prefixSize = scheduledIdSize - hexId.length; - - const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId; - - return scheduledId; - } - - const ids = []; - for(let i = 0; i < num; i++) { - ids.push(makeId(this.scheduledIdSlider)); - this.scheduledIdSlider += 1; - } - - return ids; - } - - makeScheduledId(): string { - return (this.makeScheduledIds(1))[0]; - } - - async captureEvents(eventSection: string, eventMethod: string): Promise { - const capture = new EventCapture(this.helper, eventSection, eventMethod); - await capture.startCapture(); - - return capture; - } - - makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) { - return { - V2: [ - { - WithdrawAsset: [ - { - id, - fun: { - Fungible: amount, - }, - }, - ], - }, - { - BuyExecution: { - fees: { - id, - fun: { - Fungible: amount, - }, - }, - weightLimit: 'Unlimited', - }, - }, - { - DepositAsset: { - assets: { - Wild: 'All', - }, - maxAssets: 1, - beneficiary: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: beneficiary, - }, - }, - }, - }, - }, - }, - ], - }; - } - - makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) { - return { - V2: [ - { - ReserveAssetDeposited: [ - { - id, - fun: { - Fungible: amount, - }, - }, - ], - }, - { - BuyExecution: { - fees: { - id, - fun: { - Fungible: amount, - }, - }, - weightLimit: 'Unlimited', - }, - }, - { - DepositAsset: { - assets: { - Wild: 'All', - }, - maxAssets: 1, - beneficiary: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: beneficiary, - }, - }, - }, - }, - }, - }, - ], - }; - } - - makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) { - return { - V3: [ - { - UnpaidExecution: { - weightLimit: 'Unlimited', - checkOrigin: null, - }, - }, - { - Transact: { - originKind: 'Superuser', - requireWeightAtMost: { - refTime: info.weightMultiplier * 200000000, - proofSize: info.weightMultiplier * 3000, - }, - call: { - encoded: info.call, - }, - }, - }, - ], - }; - } -} - -class MoonbeamAccountGroup { - helper: MoonbeamHelper; - - keyring: Keyring; - _alithAccount: IKeyringPair; - _baltatharAccount: IKeyringPair; - _dorothyAccount: IKeyringPair; - - constructor(helper: MoonbeamHelper) { - this.helper = helper; - - this.keyring = new Keyring({type: 'ethereum'}); - const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133'; - const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b'; - const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68'; - - this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum'); - this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum'); - this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum'); - } - - alithAccount() { - return this._alithAccount; - } - - baltatharAccount() { - return this._baltatharAccount; - } - - dorothyAccount() { - return this._dorothyAccount; - } - - create() { - return this.keyring.addFromUri(mnemonicGenerate()); - } -} - -class MoonbeamFastDemocracyGroup { - helper: DevMoonbeamHelper; - - constructor(helper: DevMoonbeamHelper) { - this.helper = helper; - } - - async executeProposal(proposalDesciption: string, encodedProposal: string) { - const proposalHash = blake2AsHex(encodedProposal); - - const alithAccount = this.helper.account.alithAccount(); - const baltatharAccount = this.helper.account.baltatharAccount(); - const dorothyAccount = this.helper.account.dorothyAccount(); - - const councilVotingThreshold = 2; - const technicalCommitteeThreshold = 2; - const fastTrackVotingPeriod = 3; - const fastTrackDelayPeriod = 0; - - console.log(`[democracy] executing '${proposalDesciption}' proposal`); - - // >>> Propose external motion through council >>> - console.log('\t* Propose external motion through council.......'); - const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal}); - const encodedMotion = externalMotion?.method.toHex() || ''; - const motionHash = blake2AsHex(encodedMotion); - console.log('\t* Motion hash is %s', motionHash); - - await this.helper.collective.council.propose( - baltatharAccount, - councilVotingThreshold, - externalMotion, - externalMotion.encodedLength, - ); - - const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1; - await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true); - await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true); - - await this.helper.collective.council.close( - dorothyAccount, - motionHash, - councilProposalIdx, - { - refTime: 1_000_000_000, - proofSize: 1_000_000, - }, - externalMotion.encodedLength, - ); - console.log('\t* Propose external motion through council.......DONE'); - // <<< Propose external motion through council <<< - - // >>> Fast track proposal through technical committee >>> - console.log('\t* Fast track proposal through technical committee.......'); - const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod); - const encodedFastTrack = fastTrack?.method.toHex() || ''; - const fastTrackHash = blake2AsHex(encodedFastTrack); - console.log('\t* FastTrack hash is %s', fastTrackHash); - - await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength); - - const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1; - await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true); - await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true); - - await this.helper.collective.techCommittee.close( - baltatharAccount, - fastTrackHash, - techProposalIdx, - { - refTime: 1_000_000_000, - proofSize: 1_000_000, - }, - fastTrack.encodedLength, - ); - console.log('\t* Fast track proposal through technical committee.......DONE'); - // <<< Fast track proposal through technical committee <<< - - const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started); - const referendumIndex = democracyStarted.referendumIndex; - - // >>> Referendum voting >>> - console.log(`\t* Referendum #${referendumIndex} voting.......`); - await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, { - balance: 10_000_000_000_000_000_000n, - vote: {aye: true, conviction: 1}, - }); - console.log(`\t* Referendum #${referendumIndex} voting.......DONE`); - // <<< Referendum voting <<< - - // Wait the proposal to pass - await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex); - - await this.helper.wait.newBlocks(1); - - console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`); - } -} - -class HydraFastDemocracyGroup { - helper: DevHydraDxHelper; - - constructor(helper: DevHydraDxHelper) { - this.helper = helper; - } - - async executeProposal(proposalDesciption: string, encodedProposal: string) { - const proposalHash = blake2AsHex(encodedProposal); - const aliceAccount = this.helper.util.fromSeed('//Alice'); - const bobAccount = this.helper.util.fromSeed('//Bob'); - const eveAccount = this.helper.util.fromSeed('//Eve'); - - const councilVotingThreshold = 1; - const technicalCommitteeThreshold = 3; - const fastTrackVotingPeriod = 3; - const fastTrackDelayPeriod = 0; - - console.log(`[democracy] executing '${proposalDesciption}' proposal`); - - // >>> Propose external motion through council >>> - console.log('\t* Propose external motion through council.......'); - const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal}); - const encodedMotion = externalMotion?.method.toHex() || ''; - const motionHash = blake2AsHex(encodedMotion); - console.log('\t* Motion hash is %s', motionHash); - - await this.helper.collective.council.propose( - aliceAccount, - councilVotingThreshold, - externalMotion, - externalMotion.encodedLength, - ); - - console.log('\t* Propose external motion through council.......DONE'); - // <<< Propose external motion through council <<< - - // >>> Fast track proposal through technical committee >>> - console.log('\t* Fast track proposal through technical committee.......'); - const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod); - const encodedFastTrack = fastTrack?.method.toHex() || ''; - const fastTrackHash = blake2AsHex(encodedFastTrack); - console.log('\t* FastTrack hash is %s', fastTrackHash); - - await this.helper.collective.techCommittee.propose(aliceAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength); - - const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1; - await this.helper.collective.techCommittee.vote(aliceAccount, fastTrackHash, techProposalIdx, true); - await this.helper.collective.techCommittee.vote(bobAccount, fastTrackHash, techProposalIdx, true); - await this.helper.collective.techCommittee.vote(eveAccount, fastTrackHash, techProposalIdx, true); - - await this.helper.collective.techCommittee.close( - bobAccount, - fastTrackHash, - techProposalIdx, - { - refTime: 1_000_000_000, - proofSize: 1_000_000, - }, - fastTrack.encodedLength, - ); - console.log('\t* Fast track proposal through technical committee.......DONE'); - // <<< Fast track proposal through technical committee <<< - - const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started); - const referendumIndex = democracyStarted.referendumIndex; - - // >>> Referendum voting >>> - console.log(`\t* Referendum #${referendumIndex} voting.......`); - await this.helper.democracy.referendumVote(eveAccount, referendumIndex, { - balance: 10_000_000_000_000_000_000n, - vote: {aye: true, conviction: 1}, - }); - console.log(`\t* Referendum #${referendumIndex} voting.......DONE`); - // <<< Referendum voting <<< - - // Wait the proposal to pass - await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex); - - await this.helper.wait.newBlocks(1); - - console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`); - } -} - -class WaitGroup { - helper: ChainHelperBase; - - constructor(helper: ChainHelperBase) { - this.helper = helper; - } - - sleep(milliseconds: number) { - return new Promise((resolve) => setTimeout(resolve, milliseconds)); - } - - private async waitWithTimeout(promise: Promise, timeout: number) { - let isBlock = false; - promise.then(() => isBlock = true).catch(() => isBlock = true); - let totalTime = 0; - const step = 100; - while(!isBlock) { - await this.sleep(step); - totalTime += step; - if(totalTime >= timeout) throw Error('Blocks production failed'); - } - return promise; - } - - /** - * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout. - * @param promise async operation to race against the timeout - * @param timeoutMS time after which to time out - * @param timeoutError error message to throw - * @returns promise of the same type the operation had - */ - withTimeout( - promise: Promise, - timeoutMS = 30000, - timeoutError = 'The operation has timed out!', - ): Promise { - const timeout = new Promise((_, reject) => { - setTimeout(() => { - reject(new Error(timeoutError)); - }, timeoutMS); - }); - - return Promise.race([promise, timeout]).catch(e => {throw new Error(e);}); - } - - /** - * Wait for specified number of blocks - * @param blocksCount number of blocks to wait - * @returns - */ - async newBlocks(blocksCount = 1, timeout?: number): Promise { - timeout = timeout ?? blocksCount * 60_000; - // eslint-disable-next-line no-async-promise-executor - const promise = new Promise(async (resolve) => { - const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => { - if(blocksCount > 0) { - blocksCount--; - } else { - unsubscribe(); - resolve(); - } - }); - }); - await this.waitWithTimeout(promise, timeout); - return promise; - } - - /** - * Wait for the specified number of sessions to pass. - * Only applicable if the Session pallet is turned on. - * @param sessionCount number of sessions to wait - * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks - * @returns - */ - async newSessions(sessionCount = 1, blockTimeout = 60000): Promise { - console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.` - + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.'); - - const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount; - let currentSessionIndex = -1; - - while(currentSessionIndex < expectedSessionIndex) { - // eslint-disable-next-line no-async-promise-executor - currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => { - await this.newBlocks(1); - const res = await (this.helper as DevUniqueHelper).session.getIndex(); - resolve(res); - }), blockTimeout, 'The chain has stopped producing blocks!'); - } - } - - async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) { - timeout = timeout ?? 30 * 60 * 1000; - // eslint-disable-next-line no-async-promise-executor - const promise = new Promise(async (resolve) => { - const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => { - if(data.number.toNumber() >= blockNumber) { - unsubscribe(); - resolve(); - } - }); - }); - await this.waitWithTimeout(promise, timeout); - return promise; - } - - async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) { - timeout = timeout ?? 30 * 60 * 1000; - // eslint-disable-next-line no-async-promise-executor - const promise = new Promise(async (resolve) => { - const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => { - if(data.value.relayParentNumber.toNumber() >= blockNumber) { - // @ts-ignore - unsubscribe(); - resolve(); - } - }); - }); - await this.waitWithTimeout(promise, timeout); - return promise; - } - - noScheduledTasks() { - const api = this.helper.getApi(); - - // eslint-disable-next-line no-async-promise-executor - const promise = new Promise(async resolve => { - const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => { - const areThereScheduledTasks = await api.query.scheduler.lookup.entries(); - - if(areThereScheduledTasks.length == 0) { - unsubscribe(); - resolve(); - } - }); - }); - - return promise; - } - - parachainBlockMultiplesOf(val: bigint) { - // eslint-disable-next-line no-async-promise-executor - const promise = new Promise(async resolve => { - const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => { - if(data.number.toBigInt() % val == 0n) { - console.log(`from waiter: ${data.number.toBigInt()}`); - unsubscribe(); - resolve(); - } - }); - }); - return promise; - } - - event( - maxBlocksToWait: number, - eventHelper: T, - filter: (_: any) => boolean = () => true, - ): any { - // eslint-disable-next-line no-async-promise-executor - const promise = new Promise(async (resolve) => { - const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => { - const blockNumber = header.number.toJSON(); - const blockHash = header.hash; - const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`; - const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`; - - this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`); - - const apiAt = await this.helper.getApi().at(blockHash); - const eventRecords = (await apiAt.query.system.events()) as any; - - const neededEvent = eventRecords.toArray() - .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method()) - .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data)) - .find(filter); - - if(neededEvent) { - unsubscribe(); - resolve(neededEvent); - } else if(maxBlocksToWait > 0) { - maxBlocksToWait--; - } else { - this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found. - The wait lasted until block ${blockNumber} inclusive`); - unsubscribe(); - resolve(null); - } - }); - }); - return promise; - } - - async expectEvent( - maxBlocksToWait: number, - eventHelper: T, - filter: (e: any) => boolean = () => true, - ) { - const e = await this.event(maxBlocksToWait, eventHelper, filter); - if(e == null) { - throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`); - } else { - return e; - } - } -} - -class SessionGroup { - helper: ChainHelperBase; - - constructor(helper: ChainHelperBase) { - this.helper = helper; - } - - //todo:collator documentation - async getIndex(): Promise { - return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber(); - } - - newSessions(sessionCount = 1, blockTimeout = 24000): Promise { - return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout); - } - - setOwnKeys(signer: TSigner, key: string) { - return this.helper.executeExtrinsic( - signer, - 'api.tx.session.setKeys', - [key, '0x0'], - true, - ); - } - - setOwnKeysFromAddress(signer: TSigner) { - return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex')); - } -} - -class TestUtilGroup { - helper: DevUniqueHelper; - - constructor(helper: DevUniqueHelper) { - this.helper = helper; - } - - async enable(testUtilsPalletName: string) { - if(this.helper.fetchMissingPalletNames([testUtilsPalletName]).length != 0) { - return; - } - - const signer = this.helper.util.fromSeed('//Alice'); - await this.helper.getSudo().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true); - } - - async setTestValue(signer: TSigner, testVal: number) { - await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true); - } - - async incTestValue(signer: TSigner) { - await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true); - } - - async setTestValueAndRollback(signer: TSigner, testVal: number) { - await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true); - } - - async testValue(blockIdx?: number) { - const api = blockIdx - ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx])) - : this.helper.getApi(); - - return (await api.query.testUtils.testValue()).toJSON(); - } - - async justTakeFee(signer: TSigner) { - await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true); - } - - async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) { - await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true); - } -} - -class EventCapture { - helper: DevUniqueHelper; - eventSection: string; - eventMethod: string; - events: EventRecord[] = []; - unsubscribe: VoidFn | null = null; - - constructor( - helper: DevUniqueHelper, - eventSection: string, - eventMethod: string, - ) { - this.helper = helper; - this.eventSection = eventSection; - this.eventMethod = eventMethod; - } - - async startCapture() { - this.stopCapture(); - this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => { - const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod); - - this.events.push(...newEvents); - })) as any; - } - - stopCapture() { - if(this.unsubscribe !== null) { - this.unsubscribe(); - } - } - - extractCapturedEvents() { - return this.events; - } -} - -class AdminGroup { - helper: UniqueHelper; - - constructor(helper: UniqueHelper) { - this.helper = helper; - } - - async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> { - const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true); - return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({ - staker: e.event.data[0].toString(), - stake: e.event.data[1].toBigInt(), - payout: e.event.data[2].toBigInt(), - })); - } -} - -// eslint-disable-next-line @typescript-eslint/naming-convention -function ScheduledUniqueHelper(Base: T) { - return class extends Base { - scheduleFn: 'schedule' | 'scheduleAfter'; - blocksNum: number; - options: ISchedulerOptions; - - constructor(...args: any[]) { - const logger = args[0] as ILogger; - const options = args[1] as { - scheduleFn: 'schedule' | 'scheduleAfter', - blocksNum: number, - options: ISchedulerOptions - }; - - super(logger); - - this.scheduleFn = options.scheduleFn; - this.blocksNum = options.blocksNum; - this.options = options.options; - } - - override executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise { - const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams); - - const mandatorySchedArgs = [ - this.blocksNum, - this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null, - this.options.priority ?? null, - scheduledTx, - ]; - - let schedArgs; - let scheduleFn; - - if(this.options.scheduledId) { - schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs]; - - if(this.scheduleFn == 'schedule') { - scheduleFn = 'scheduleNamed'; - } else if(this.scheduleFn == 'scheduleAfter') { - scheduleFn = 'scheduleNamedAfter'; - } - } else { - schedArgs = mandatorySchedArgs; - scheduleFn = this.scheduleFn; - } - - const extrinsic = 'api.tx.scheduler.' + scheduleFn; - - return super.executeExtrinsic( - sender, - extrinsic as any, - schedArgs, - expectSuccess, - ); - } - }; -} --- a/js-packages/playgrounds/unique.governance.ts +++ /dev/null @@ -1,531 +0,0 @@ -import {blake2AsHex} from '@polkadot/util-crypto'; -import type {PalletDemocracyConviction} from '@polkadot/types/lookup'; -import type {IPhasicEvent, TSigner} from './types.js'; -import {HelperGroup, UniqueHelper} from './unique.js'; - -export class CollectiveGroup extends HelperGroup { - /** - * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee' - */ - private collective: string; - - constructor(helper: UniqueHelper, collective: string) { - super(helper); - this.collective = collective; - } - - /** - * Check the result of a proposal execution for the success of the underlying proposed extrinsic. - * @param events events of the proposal execution - * @returns proposal hash - */ - private checkExecutedEvent(events: IPhasicEvent[]): string { - const executionEvents = events.filter(x => - x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted')); - - if(executionEvents.length != 1) { - if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0) - throw new Error(`Disapproved by ${this.collective}`); - else - throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`); - } - - const result = (executionEvents[0].event.data as any).result; - - if(result.isErr) { - if(result.asErr.isModule) { - const error = result.asErr.asModule; - const metaError = this.helper.getApi()?.registry.findMetaError(error); - throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`); - } else { - throw new Error('Proposal execution failed with ' + result.asErr.toHuman()); - } - } - - return (executionEvents[0].event.data as any).proposalHash; - } - - /** - * Returns an array of members' addresses. - */ - async getMembers() { - return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman(); - } - - /** - * Returns the optional address of the prime member of the collective. - */ - async getPrimeMember() { - return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman(); - } - - /** - * Returns an array of proposal hashes that are currently active for this collective. - */ - async getProposals() { - return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman(); - } - - /** - * Returns the call originally encoded under the specified hash. - * @param hash h256-encoded proposal - * @returns the optional call that the proposal hash stands for. - */ - async getProposalCallOf(hash: string) { - return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman(); - } - - /** - * Returns the total number of proposals so far. - */ - async getTotalProposalsCount() { - return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber(); - } - - /** - * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately. - * @param signer keyring of the proposer - * @param proposal constructed call to be executed if the proposal is successful - * @param voteThreshold minimal number of votes for the proposal to be verified and executed - * @param lengthBound byte length of the encoded call - * @returns promise of extrinsic execution and its result - */ - async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) { - return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]); - } - - /** - * Casts a vote to either approve or reject a proposal. - * @param signer keyring of the voter - * @param proposalHash hash of the proposal to be voted for - * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors - * @param approve aye or nay - * @returns promise of extrinsic execution and its result - */ - vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]); - } - - /** - * Executes a call immediately as a member of the collective. Needed for the Member origin. - * @param signer keyring of the executor member - * @param proposal constructed call to be executed by the member - * @param lengthBound byte length of the encoded call - * @returns promise of extrinsic execution - */ - async execute(signer: TSigner, proposal: any, lengthBound = 10000) { - const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]); - this.checkExecutedEvent(result.result.events); - return result; - } - - /** - * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing. - * @param signer keyring of the executor. Can be absolutely anyone. - * @param proposalHash hash of the proposal to close - * @param proposalIndex index of the proposal generated on its creation - * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call. - * @param lengthBound byte length of the encoded call - * @returns promise of extrinsic execution and its result - */ - async close( - signer: TSigner, - proposalHash: string, - proposalIndex: number, - weightBound: [number, number] | any = [20_000_000_000, 1000_000], - lengthBound = 10_000, - ) { - const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [ - proposalHash, - proposalIndex, - weightBound, - lengthBound, - ]); - this.checkExecutedEvent(result.result.events); - return result; - } - - /** - * Shut down a proposal, regardless of its current state. - * @param signer keyring of the disapprover. Must be root - * @param proposalHash hash of the proposal to close - * @returns promise of extrinsic execution and its result - */ - disapproveProposal(signer: TSigner, proposalHash: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]); - } -} - -export class CollectiveMembershipGroup extends HelperGroup { - /** - * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership' - */ - private membership: string; - - constructor(helper: UniqueHelper, membership: string) { - super(helper); - this.membership = membership; - } - - /** - * Returns an array of members' addresses according to the membership pallet's perception. - * Note that it does not recognize the original pallet's members set with `setMembers()`. - */ - async getMembers() { - return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman(); - } - - /** - * Returns the optional address of the prime member of the collective. - */ - async getPrimeMember() { - return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman(); - } - - /** - * Add a member to the collective. - * @param signer keyring of the setter. Must be root - * @param member address of the member to add - * @returns promise of extrinsic execution and its result - */ - addMember(signer: TSigner, member: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]); - } - - addMemberCall(member: string) { - return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]); - } - - /** - * Remove a member from the collective. - * @param signer keyring of the setter. Must be root - * @param member address of the member to remove - * @returns promise of extrinsic execution and its result - */ - removeMember(signer: TSigner, member: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]); - } - - removeMemberCall(member: string) { - return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]); - } - - /** - * Set members of the collective to the given list of addresses. - * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) - * @param members addresses of the members to set - * @returns promise of extrinsic execution and its result - */ - resetMembers(signer: TSigner, members: string[]) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]); - } - - /** - * Set the collective's prime member to the given address. - * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) - * @param prime address of the prime member of the collective - * @returns promise of extrinsic execution and its result - */ - setPrime(signer: TSigner, prime: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]); - } - - setPrimeCall(member: string) { - return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]); - } - - /** - * Remove the collective's prime member. - * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) - * @returns promise of extrinsic execution and its result - */ - clearPrime(signer: TSigner) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []); - } - - clearPrimeCall() { - return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []); - } -} - -export class RankedCollectiveGroup extends HelperGroup { - /** - * Pallet name to make an API call to. Examples: 'FellowshipCollective' - */ - private collective: string; - - constructor(helper: UniqueHelper, collective: string) { - super(helper); - this.collective = collective; - } - - addMember(signer: TSigner, newMember: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]); - } - - addMemberCall(newMember: string) { - return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]); - } - - removeMember(signer: TSigner, member: string, minRank: number) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]); - } - - removeMemberCall(newMember: string, minRank: number) { - return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]); - } - - promote(signer: TSigner, member: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]); - } - - promoteCall(member: string) { - return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]); - } - - demote(signer: TSigner, member: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]); - } - - demoteCall(newMember: string) { - return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]); - } - - vote(signer: TSigner, pollIndex: number, aye: boolean) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]); - } - - async getMembers() { - return (await this.helper.getApi().query.fellowshipCollective.members.keys()) - .map((key) => key.args[0].toString()); - } - - async getMemberRank(member: string) { - return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank; - } -} - -export class ReferendaGroup extends HelperGroup { - /** - * Pallet name to make an API call to. Examples: 'FellowshipReferenda' - */ - private referenda: string; - - constructor(helper: UniqueHelper, referenda: string) { - super(helper); - this.referenda = referenda; - } - - submit( - signer: TSigner, - proposalOrigin: string, - proposal: any, - enactmentMoment: any, - ) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [ - {Origins: proposalOrigin}, - proposal, - enactmentMoment, - ]); - } - - placeDecisionDeposit(signer: TSigner, referendumIndex: number) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]); - } - - cancel(signer: TSigner, referendumIndex: number) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]); - } - - cancelCall(referendumIndex: number) { - return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]); - } - - async referendumInfo(referendumIndex: number) { - return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON(); - } - - async enactmentEventId(referendumIndex: number) { - const api = await this.helper.getApi(); - - const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a(); - return blake2AsHex(bytes, 256); - } -} - -export interface IFellowshipGroup { - collective: RankedCollectiveGroup; - referenda: ReferendaGroup; -} - -export interface ICollectiveGroup { - collective: CollectiveGroup; - membership: CollectiveMembershipGroup; -} - -export class DemocracyGroup extends HelperGroup { - // todo displace proposal into types? - propose(signer: TSigner, call: any, deposit: bigint) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]); - } - - proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]); - } - - proposeCall(call: any, deposit: bigint) { - return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]); - } - - second(signer: TSigner, proposalIndex: number) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]); - } - - externalPropose(signer: TSigner, proposalCall: any) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeMajority(signer: TSigner, proposalCall: any) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeDefault(signer: TSigner, proposalCall: any) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]); - } - - externalProposeCall(proposalCall: any) { - return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeMajorityCall(proposalCall: any) { - return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeDefaultCall(proposalCall: any) { - return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeDefaultWithPreimageCall(preimage: string) { - return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]); - } - - // ... and blacklist external proposal hash. - vetoExternal(signer: TSigner, proposalHash: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]); - } - - vetoExternalCall(proposalHash: string) { - return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]); - } - - blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]); - } - - blacklistCall(proposalHash: string, referendumIndex: number | null = null) { - return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]); - } - - // proposal. CancelProposalOrigin (root or all techcom) - cancelProposal(signer: TSigner, proposalIndex: number) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]); - } - - cancelProposalCall(proposalIndex: number) { - return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]); - } - - clearPublicProposals(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []); - } - - fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); - } - - fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) { - return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); - } - - // referendum. CancellationOrigin (TechCom member) - emergencyCancel(signer: TSigner, referendumIndex: number) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]); - } - - emergencyCancelCall(referendumIndex: number) { - return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]); - } - - vote(signer: TSigner, referendumIndex: number, vote: any) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]); - } - - removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) { - if(targetAccount) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]); - } else { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]); - } - } - - unlock(signer: TSigner, targetAccount: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]); - } - - delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]); - } - - undelegate(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []); - } - - async referendumInfo(referendumIndex: number) { - return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON(); - } - - async publicProposals() { - return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON(); - } - - async findPublicProposal(proposalIndex: number) { - const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex); - - return proposalInfo ? proposalInfo[1] : null; - } - - async expectPublicProposal(proposalIndex: number) { - const proposal = await this.findPublicProposal(proposalIndex); - - if(proposal) { - return proposal; - } else { - throw Error(`Proposal #${proposalIndex} is expected to exist`); - } - } - - async getExternalProposal() { - return (await this.helper.callRpc('api.query.democracy.nextExternal', [])); - } - - async expectExternalProposal() { - const proposal = await this.getExternalProposal(); - - if(proposal) { - return proposal; - } else { - throw Error('An external proposal is expected to exist'); - } - } - - /* setMetadata? */ - - /* todo? - referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true); - }*/ -} --- a/js-packages/playgrounds/unique.xcm.ts +++ /dev/null @@ -1,449 +0,0 @@ -import {ApiPromise, WsProvider} from '@polkadot/api'; -import type {IKeyringPair} from '@polkadot/types/types'; -import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from './unique.js'; -import type {ILogger, TSigner, TSubstrateAccount} from './types.js'; -import type {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.xcm.js'; - - -export class XcmChainHelper extends ChainHelperBase { - override async connect(wsEndpoint: string, _listeners?: any): Promise { - const wsProvider = new WsProvider(wsEndpoint); - this.api = new ApiPromise({ - provider: wsProvider, - }); - await this.api.isReadyOrError; - this.network = await UniqueHelper.detectNetwork(this.api); - } -} - -class AcalaAssetRegistryGroup extends HelperGroup { - async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) { - await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true); - } -} - -class MoonbeamAssetManagerGroup extends HelperGroup { - makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) { - const apiPrefix = 'api.tx.assetManager.'; - - const registerTx = this.helper.constructApiCall( - apiPrefix + 'registerForeignAsset', - [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient], - ); - - const setUnitsTx = this.helper.constructApiCall( - apiPrefix + 'setAssetUnitsPerSecond', - [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint], - ); - - const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]); - const encodedProposal = batchCall?.method.toHex() || ''; - return encodedProposal; - } - - async assetTypeId(location: any) { - return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]); - } -} - -class MoonbeamDemocracyGroup extends HelperGroup { - notePreimagePallet: string; - - constructor(helper: MoonbeamHelper, options: { [key: string]: any } = {}) { - super(helper); - this.notePreimagePallet = options.notePreimagePallet; - } - - async notePreimage(signer: TSigner, encodedProposal: string) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true); - } - - externalProposeMajority(proposal: any) { - return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]); - } - - fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) { - return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); - } - - async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) { - await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true); - } -} - -class DemocracyGroup extends HelperGroup { - notePreimagePallet: string; - - constructor(helper: T, options: { [key: string]: any } = {}) { - super(helper); - this.notePreimagePallet = options.notePreimagePallet; - } - - async notePreimage(signer: TSigner, encodedProposal: string) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true); - } - - externalProposeMajority(proposal: any) { - return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]); - } - - fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) { - return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); - } - - async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) { - await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true); - } -} - -class MoonbeamCollectiveGroup extends HelperGroup { - collective: string; - - constructor(helper: MoonbeamHelper, collective: string) { - super(helper); - - this.collective = collective; - } - - async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true); - } - - async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true); - } - - async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true); - } - - async proposalCount() { - return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])); - } -} - -class CollectiveGroup extends HelperGroup { - collective: string; - - constructor(helper: T, palletName: string) { - super(helper); - - this.collective = palletName; - } - - async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true); - } - - async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true); - } - - async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true); - } - - async proposalCount() { - return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])); - } -} -class PolkadexXcmHelperGroup extends HelperGroup { - async whitelistToken(signer: TSigner, assetId: any) { - await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true); - } -} - -export class ForeignAssetsGroup extends HelperGroup { - async register(signer: TSigner, assetId: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) { - await this.helper.executeExtrinsic( - signer, - 'api.tx.foreignAssets.forceRegisterForeignAsset', - [{V3: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode], - true, - ); - } - - async foreignCollectionId(assetId: any) { - return (await this.helper.callRpc('api.query.foreignAssets.foreignAssetToCollection', [assetId])).toJSON(); - } -} - -export class XcmGroup extends HelperGroup { - palletName: string; - - constructor(helper: T, palletName: string) { - super(helper); - - this.palletName = palletName; - } - - async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true); - } - - async setSafeXcmVersion(signer: TSigner, version: number) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true); - } - - async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true); - } - - async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) { - const destinationContent = { - parents: 0, - interior: { - X1: { - Parachain: destinationParaId, - }, - }, - }; - - const beneficiaryContent = { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: targetAccount, - }, - }, - }, - }; - - const assetsContent = [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: amount, - }, - }, - ]; - - let destination; - let beneficiary; - let assets; - - if(xcmVersion == 2) { - destination = {V1: destinationContent}; - beneficiary = {V1: beneficiaryContent}; - assets = {V1: assetsContent}; - - } else if(xcmVersion == 3) { - destination = {V2: destinationContent}; - beneficiary = {V2: beneficiaryContent}; - assets = {V2: assetsContent}; - - } else { - throw Error('Unknown XCM version: ' + xcmVersion); - } - - const feeAssetItem = 0; - - await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem); - } - - async send(signer: IKeyringPair, destination: any, message: any) { - await this.helper.executeExtrinsic( - signer, - `api.tx.${this.palletName}.send`, - [ - destination, - message, - ], - true, - ); - } -} - -export class XTokensGroup extends HelperGroup { - async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) { - await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true); - } - - async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) { - await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true); - } - - async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) { - await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true); - } -} - - - -export class TokensGroup extends HelperGroup { - async accounts(address: string, currencyId: any) { - const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any; - return BigInt(free); - } -} - -export class AssetsGroup extends HelperGroup { - async create(signer: TSigner, assetId: number | bigint, admin: string, minimalBalance: bigint) { - await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true); - } - - async forceCreate(signer: TSigner, assetId: number | bigint, admin: string, minimalBalance: bigint, isSufficient = true) { - await this.helper.executeExtrinsic(signer, 'api.tx.assets.forceCreate', [assetId, admin, isSufficient, minimalBalance], true); - } - - async setMetadata(signer: TSigner, assetId: number | bigint, name: string, symbol: string, decimals: number) { - await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true); - } - - async mint(signer: TSigner, assetId: number | bigint, beneficiary: string, amount: bigint) { - await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true); - } - - async assetInfo(assetId: number | bigint) { - return (await this.helper.callRpc('api.query.assets.asset', [assetId])).toJSON(); - } - - async account(assetId: string | number | bigint, address: string) { - const accountAsset = ( - await this.helper.callRpc('api.query.assets.account', [assetId, address]) - ).toJSON()! as any; - - if(accountAsset !== null) { - return BigInt(accountAsset['balance']); - } else { - return null; - } - } -} - -export class RelayHelper extends XcmChainHelper { - balance: SubstrateBalanceGroup; - xcm: XcmGroup; - - constructor(logger?: ILogger, options: { [key: string]: any } = {}) { - super(logger, options.helperBase ?? RelayHelper); - - this.balance = new SubstrateBalanceGroup(this); - this.xcm = new XcmGroup(this, 'xcmPallet'); - } -} - -export class WestmintHelper extends XcmChainHelper { - balance: SubstrateBalanceGroup; - xcm: XcmGroup; - assets: AssetsGroup; - xTokens: XTokensGroup; - - constructor(logger?: ILogger, options: { [key: string]: any } = {}) { - super(logger, options.helperBase ?? WestmintHelper); - - this.balance = new SubstrateBalanceGroup(this); - this.xcm = new XcmGroup(this, 'polkadotXcm'); - this.assets = new AssetsGroup(this); - this.xTokens = new XTokensGroup(this); - } -} - -export class MoonbeamHelper extends XcmChainHelper { - balance: EthereumBalanceGroup; - assetManager: MoonbeamAssetManagerGroup; - assets: AssetsGroup; - xTokens: XTokensGroup; - democracy: MoonbeamDemocracyGroup; - collective: { - council: MoonbeamCollectiveGroup, - techCommittee: MoonbeamCollectiveGroup, - }; - - constructor(logger?: ILogger, options: { [key: string]: any } = {}) { - super(logger, options.helperBase ?? MoonbeamHelper); - - this.balance = new EthereumBalanceGroup(this); - this.assetManager = new MoonbeamAssetManagerGroup(this); - this.assets = new AssetsGroup(this); - this.xTokens = new XTokensGroup(this); - this.democracy = new MoonbeamDemocracyGroup(this, options); - this.collective = { - council: new MoonbeamCollectiveGroup(this, 'councilCollective'), - techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'), - }; - } -} - -export class AstarHelper extends XcmChainHelper { - balance: SubstrateBalanceGroup; - assets: AssetsGroup; - xcm: XcmGroup; - - constructor(logger?: ILogger, options: { [key: string]: any } = {}) { - super(logger, options.helperBase ?? AstarHelper); - - this.balance = new SubstrateBalanceGroup(this); - this.assets = new AssetsGroup(this); - this.xcm = new XcmGroup(this, 'polkadotXcm'); - } -} - -export class AcalaHelper extends XcmChainHelper { - balance: SubstrateBalanceGroup; - assetRegistry: AcalaAssetRegistryGroup; - xTokens: XTokensGroup; - tokens: TokensGroup; - xcm: XcmGroup; - - constructor(logger?: ILogger, options: { [key: string]: any } = {}) { - super(logger, options.helperBase ?? AcalaHelper); - - this.balance = new SubstrateBalanceGroup(this); - this.assetRegistry = new AcalaAssetRegistryGroup(this); - this.xTokens = new XTokensGroup(this); - this.tokens = new TokensGroup(this); - this.xcm = new XcmGroup(this, 'polkadotXcm'); - } -} - -export class PolkadexHelper extends XcmChainHelper { - assets: AssetsGroup; - balance: SubstrateBalanceGroup; - xTokens: XTokensGroup; - xcm: XcmGroup; - xcmHelper: PolkadexXcmHelperGroup; - - constructor(logger?: ILogger, options: { [key: string]: any } = {}) { - super(logger, options.helperBase ?? PolkadexHelper); - - this.assets = new AssetsGroup(this); - this.balance = new SubstrateBalanceGroup(this); - this.xTokens = new XTokensGroup(this); - this.xcm = new XcmGroup(this, 'polkadotXcm'); - this.xcmHelper = new PolkadexXcmHelperGroup(this); - } -} - -export class HydraDxHelper extends XcmChainHelper { - balance: SubstrateBalanceGroup; - xcm: XcmGroup; - xTokens: XTokensGroup; - democracy: DemocracyGroup; - collective: { - council: CollectiveGroup, - techCommittee: CollectiveGroup, - }; - - constructor(logger?: ILogger, options: { [key: string]: any } = {}) { - super(logger, options.helperBase ?? HydraDxHelper); - options.notePreimagePallet = options.notePreimagePallet ?? 'preimage'; - - this.balance = new SubstrateBalanceGroup(this); - this.xcm = new XcmGroup(this, 'polkadotXcm'); - this.xTokens = new XTokensGroup(this); - this.democracy = new DemocracyGroup(this, options); - this.collective = { - council: new CollectiveGroup(this, 'council'), - techCommittee: new CollectiveGroup(this, 'technicalCommittee'), - }; - } -} - --- /dev/null +++ b/js-packages/scripts/authorizeEnactUpgrade.ts @@ -0,0 +1,19 @@ +import {readFile} from 'fs/promises'; +import {u8aToHex} from '@polkadot/util'; +import {usingPlaygrounds} from '@unique/test-utils/util.js'; +import {blake2AsHex} from '@polkadot/util-crypto'; + + +const codePath = process.argv[2]; +if(!codePath) throw new Error('missing code path argument'); + +const code = await readFile(codePath); + +await usingPlaygrounds(async (helper, privateKey) => { + const alice = await privateKey('//Alice'); + const hex = blake2AsHex(code); + await helper.getSudo().executeExtrinsic(alice, 'api.tx.parachainSystem.authorizeUpgrade', [hex, true]); + await helper.getSudo().executeExtrinsicUncheckedWeight(alice, 'api.tx.parachainSystem.enactAuthorizedUpgrade', [u8aToHex(code)]); +}); +// We miss disconnect/unref somewhere. +process.exit(0); --- a/js-packages/scripts/benchmarks/mintFee/index.ts +++ b/js-packages/scripts/benchmarks/mintFee/index.ts @@ -1,13 +1,13 @@ import {usingEthPlaygrounds} from '@unique/tests/eth/util/index.js'; import {EthUniqueHelper} from '@unique/tests/eth/util/playgrounds/unique.dev.js'; import {readFile} from 'fs/promises'; -import type {ICrossAccountId} from '@unique/playgrounds/types.js'; +import type {ICrossAccountId} from '@unique-nft/playgrounds/types.js'; import type {IKeyringPair} from '@polkadot/types/types'; -import {UniqueNFTCollection} from '@unique/playgrounds/unique.js'; +import {UniqueNFTCollection} from '@unique-nft/playgrounds/unique.js'; import {Contract} from 'web3-eth-contract'; import {createObjectCsvWriter} from 'csv-writer'; import {convertToTokens, createCollectionForBenchmarks, PERMISSIONS, PROPERTIES} from '../utils/common.js'; -import {makeNames} from '@unique/tests/util/index.js'; +import {makeNames} from '@unique/test-utils/util.js'; import type {ContractImports} from '@unique/tests/eth/util/playgrounds/types.js'; const {dirname} = makeNames(import.meta.url); --- a/js-packages/scripts/benchmarks/nesting/index.ts +++ b/js-packages/scripts/benchmarks/nesting/index.ts @@ -4,7 +4,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {Contract} from 'web3-eth-contract'; import {convertToTokens} from '../utils/common.js'; -import {makeNames} from '@unique/tests/util/index.js'; +import {makeNames} from '@unique/test-utils/util.js'; import type {ContractImports} from '@unique/tests/eth/util/playgrounds/types.js'; import type {RMRKNestableMintable} from './ABIGEN/index.js'; --- a/js-packages/scripts/benchmarks/opsFee/index.ts +++ b/js-packages/scripts/benchmarks/opsFee/index.ts @@ -3,13 +3,13 @@ import {readFile} from 'fs/promises'; import {CollectionLimitField, CreateCollectionData, TokenPermissionField} from '@unique/tests/eth/util/playgrounds/types.js'; import type {IKeyringPair} from '@polkadot/types/types'; -import {UniqueFTCollection, UniqueNFTCollection} from '@unique/playgrounds/unique.js'; +import {UniqueFTCollection, UniqueNFTCollection} from '@unique-nft/playgrounds/unique.js'; import {Contract} from 'web3-eth-contract'; import {createObjectCsvWriter} from 'csv-writer'; import {FunctionFeeVM} from '../utils/types.js'; import type {IFunctionFee} from '../utils/types.js'; import {convertToTokens, createCollectionForBenchmarks, PERMISSIONS, PROPERTIES, SUBS_PROPERTIES} from '../utils/common.js'; -import {makeNames} from '@unique/tests/util/index.js'; +import {makeNames} from '@unique/test-utils/util.js'; const {dirname} = makeNames(import.meta.url); --- a/js-packages/scripts/benchmarks/utils/common.ts +++ b/js-packages/scripts/benchmarks/utils/common.ts @@ -1,6 +1,6 @@ import {EthUniqueHelper} from '@unique/tests/eth/util/playgrounds/unique.dev.js'; -import {UniqueNFTCollection, UniqueRFTCollection} from '@unique/playgrounds/unique.js'; -import type {ITokenPropertyPermission, TCollectionMode} from '@unique/playgrounds/types.js'; +import {UniqueNFTCollection, UniqueRFTCollection} from '@unique-nft/playgrounds/unique.js'; +import type {ITokenPropertyPermission, TCollectionMode} from '@unique-nft/playgrounds/types.js'; import type {IKeyringPair} from '@polkadot/types/types'; export const PROPERTIES = Array(40) --- a/js-packages/scripts/calibrateApply.ts +++ b/js-packages/scripts/calibrateApply.ts @@ -1,6 +1,6 @@ import {readFile, writeFile} from 'fs/promises'; import path from 'path'; -import {makeNames, usingPlaygrounds} from '@unique/tests/util/index.js'; +import {makeNames, usingPlaygrounds} from '@unique/test-utils/util.js'; const {dirname} = makeNames(import.meta.url); --- /dev/null +++ b/js-packages/scripts/createHrmp.ts @@ -0,0 +1,37 @@ +import {usingPlaygrounds} from '@unique/test-utils/util.js'; +import config from '../tests/config.js'; + +const profile = process.argv[2]; +if(!profile) throw new Error('missing profile/relay argument'); + +await usingPlaygrounds(async (helper, privateKey) => { + const bidirOpen = async (a: number, b: number) => { + console.log(`Opening ${a} <=> ${b}`); + await helper.getSudo().executeExtrinsic(alice, 'api.tx.hrmp.forceOpenHrmpChannel', [a, b, 8, 512]); + await helper.getSudo().executeExtrinsic(alice, 'api.tx.hrmp.forceOpenHrmpChannel', [b, a, 8, 512]); + }; + const alice = await privateKey('//Alice'); + switch (profile) { + case 'opal': + await bidirOpen(1001, 1002); + break; + case 'quartz': + await bidirOpen(1001, 1002); + await bidirOpen(1001, 1003); + await bidirOpen(1001, 1004); + await bidirOpen(1001, 1005); + break; + case 'unique': + await bidirOpen(1001, 1002); + await bidirOpen(1001, 1003); + await bidirOpen(1001, 1004); + await bidirOpen(1001, 1005); + await bidirOpen(1001, 1006); + await bidirOpen(1001, 1007); + break; + default: + throw new Error(`unknown hrmp config profile: ${profile}`); + } +}, config.relayUrl); +// We miss disconnect/unref somewhere. +process.exit(0); --- a/js-packages/scripts/generateEnv.ts +++ b/js-packages/scripts/generateEnv.ts @@ -1,7 +1,7 @@ import {ApiPromise, WsProvider} from '@polkadot/api'; import {readFile} from 'fs/promises'; import {join} from 'path'; -import {makeNames} from '@unique/tests/util/index.js'; +import {makeNames} from '@unique/test-utils/util.js'; const {dirname} = makeNames(import.meta.url); --- /dev/null +++ b/js-packages/scripts/identitySetter.ts @@ -0,0 +1,229 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 +// +// Pulls identities and sub-identities from a chain and then makes a preimage to later force upload them into another. +// Only changed or previously non-existent data are inserted. +// +// Usage: `yarn setIdentities [relay WS URL] [parachain WS URL] [user key] +// Example: `yarn setIdentities wss://polkadot-rpc.dwellir.com ws://localhost:9944 escape pattern miracle train sudden cart adapt embark wedding alien lamp mesh` + +import {encodeAddress} from '@polkadot/keyring'; +import type {IKeyringPair} from '@polkadot/types/types'; +import {usingPlaygrounds, Pallets} from '@unique/test-utils/util.js'; +import {ChainHelperBase} from '@unique-nft/playgrounds/unique.js'; + +const relayUrl = process.argv[2] ?? 'ws://localhost:9844'; +const paraUrl = process.argv[3] ?? 'ws://localhost:9944'; +const key = process.argv.length > 4 ? process.argv.slice(4).join(' ') : '//Alice'; + +export function extractAccountId(key: any): string { + return (key as any).toHuman()[0]; +} + +export function extractIdentityInfo(value: any): object { + const heart = (value as any).unwrap(); + const identity = heart.toJSON(); + identity.judgements = heart.judgements.toHuman(); + return identity; +} + +export function extractIdentity(key: any, value: any): [string, any] { + return [extractAccountId(key), extractIdentityInfo(value)]; +} + +export async function getIdentities(helper: ChainHelperBase, noneCasePredicate?: (key: any, value: any) => void) { + const identities: [string, any][] = []; + for(const [key, v] of await helper.getApi().query.identity.identityOf.entries()) { + const value = v as any; + if(value.isNone) { + if(noneCasePredicate) noneCasePredicate(key, value); + continue; + } + identities.push(extractIdentity(key, value)); + } + return identities; +} + +// whether the existing chain data is more important than the coming +function isCurrentChainDataPriority(helper: ChainHelperBase, currentChainId: number | undefined, relayChainId: number) { + if(!currentChainId) return false; + // information has come from local chain, and is automatically superior + if(currentChainId == helper.chain.getChainProperties().ss58Format) return true; + // the lower the id, the more important it is (Polkadot has ss58 prefix = 0, Kusama has ss58 prefix = 2) + return currentChainId < relayChainId; +} + +// construct an object with all data necessary for insertion from storage query results +export function constructSubInfo(identityAccount: string, subQuery: any, supers: any[], ss58?: number): [string, any] { + const deposit = subQuery.toJSON()[0]; + const subIdentities = subQuery.toHuman()[1]; + subIdentities.map((sub: string) => supers.find((sup: any) => sup[0] === sub)); + + return [ + encodeAddress(identityAccount, ss58), [ + deposit, + subIdentities.map((sub: string): [string, object] | null => { + const sup = supers.find((sup: any) => sup[0] === sub); + if(!sup) { + console.error(`Error: Could not find info on super for \nsub-identity account\t${sub} of \nsuper account \t\t${identityAccount}, skipping.`); + return null; + } + return [encodeAddress(sub, ss58), sup[1].toJSON()[1]]; + }).filter((x: any) => x), + ], + ]; +} + +export async function getSubs(helper: ChainHelperBase) { + return (await helper.getApi().query.identity.subsOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]); +} + +export async function getSupers(helper: ChainHelperBase) { + return (await helper.getApi().query.identity.superOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]); +} + +async function uploadPreimage(helper: ChainHelperBase, preimageMaker: IKeyringPair, preimage: string) { + try { + await helper.executeExtrinsic(preimageMaker, 'api.tx.preimage.notePreimage', [preimage]); + } catch (e: any) { + if(e.message.includes('AlreadyNoted')) { + console.warn('Warning: The same preimage already exists on the chain. Nothing was uploaded.'); + } else { + console.error(e); + } + } +} + +// The utility for pulling identity and sub-identity data +const forceInsertIdentities = async (): Promise => { + let relaySS58Prefix = 0; + const identitiesOnRelay: any[] = []; + const subsOnRelay: any[] = []; + const identitiesToRemove: string[] = []; + await usingPlaygrounds(async helper => { + try { + relaySS58Prefix = helper.chain.getChainProperties().ss58Format; + // iterate over every identity + for(const [key, value] of await getIdentities(helper, (key, _value) => identitiesToRemove.push((key as any).toHuman()[0]))) { + // if any of the judgements resulted in a good confirmed outcome, keep this identity + let knownGood = false, reasonable = false; + for(const [_id, judgement] of value.judgements) { + if(judgement == 'KnownGood') knownGood = true; + if(judgement == 'Reasonable') reasonable = true; + } + if(!(reasonable || knownGood)) continue; + // replace the registrator id with the relay chain's ss58 format + value.judgements = [[helper.chain.getChainProperties().ss58Format, knownGood ? 'KnownGood' : 'Reasonable']]; + identitiesOnRelay.push([key, value]); + } + + const sublessIdentities = [...identitiesOnRelay]; + const supersOfSubs = await getSupers(helper); + + // iterate over every sub-identity + for(const [key, value] of await getSubs(helper)) { + // only get subs of the identities interesting to us + const identityIndex = sublessIdentities.findIndex((x: any) => x[0] == key); + if(identityIndex == -1) continue; + sublessIdentities.splice(identityIndex, 1); + subsOnRelay.push(constructSubInfo(key, value, supersOfSubs)); + } + + // mark the rest of sub-identities for deletion with empty arrays + /*for(const [account, _identity] of sublessIdentities) { + subsOnRelay.push([account, ['0', []]]); + }*/ + } catch (error) { + console.error(error); + throw Error('Error during fetching identities'); + } + }, relayUrl); + + await usingPlaygrounds(async (helper, privateKey) => { + if(helper.fetchMissingPalletNames([Pallets.Identity]).length != 0) console.error('pallet-identity is not included in parachain.'); + if(helper.fetchMissingPalletNames([Pallets.Preimage]).length != 0) console.error('pallet-preimage is not included in parachain.'); + + try { + const preimageMaker = await privateKey(key); + const ss58Format = helper.chain.getChainProperties().ss58Format; + const paraIdentities = await getIdentities(helper); + const identitiesToAdd: any[] = []; + const paraAccountsRegistrators: {[name: string]: number} = {}; + + // cross-reference every account for changes + for(const [key, value] of identitiesOnRelay) { + const encodedKey = encodeAddress(key, ss58Format); + + // only update if the identity info does not exist or is changed + const identity = paraIdentities.find(i => i[0] === encodedKey); + if(identity) { + const registratorId = identity[1].judgements[0][0]; + paraAccountsRegistrators[encodedKey] = registratorId; + if(isCurrentChainDataPriority(helper, registratorId, value.judgements[0][0]) + || JSON.stringify(value.info) === JSON.stringify(identity[1].info)) { + continue; + } + } + + identitiesToAdd.push([key, value]); + // exercise caution - in case we have an identity and the realy doesn't, it might mean one of two things: + // 1) it was deleted on the relay; + // 2) it is our own identity, we don't want to delete it. + // identitiesToRemove.push((key as any).toHuman()[0]); + } + + if(identitiesToRemove.length != 0) + await uploadPreimage( + helper, + preimageMaker, + helper.constructApiCall('api.tx.identity.forceRemoveIdentities', [identitiesToRemove]).method.toHex(), + ); + if(identitiesToAdd.length != 0) + await uploadPreimage( + helper, + preimageMaker, + helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex(), + ); + + console.log(`Tried to push ${identitiesToAdd.length} identities` + + ` and found ${identitiesToRemove.length} identities for potential removal.` + + ` Currently there are ${(await helper.getApi().query.identity.identityOf.keys()).length} identities on the chain.`); + + // fill sub-identities + const paraSubs = await getSubs(helper); + const supersOfSubs = await getSupers(helper); + const subsToUpdate: any[] = []; + + for(const [key, value] of subsOnRelay) { + const encodedKey = encodeAddress(key, ss58Format); + const sub = paraSubs.find(i => i[0] === encodedKey); + if(sub) { + // only update if the sub-identity info does not exist or is changed + if(isCurrentChainDataPriority(helper, paraAccountsRegistrators[encodedKey], relaySS58Prefix) + || JSON.stringify(value) === JSON.stringify(constructSubInfo(sub[0], sub[1], supersOfSubs)[1])) { + continue; + } + } else if(value[1].length == 0) + continue; + + subsToUpdate.push([key, value]); + } + + if(subsToUpdate.length != 0) + await uploadPreimage( + helper, + preimageMaker, + helper.constructApiCall('api.tx.identity.forceSetSubs', [subsToUpdate]).method.toHex(), + ); + + console.log(`Also tried to push ${subsToUpdate.length} identities with their sub-identities.` + + ` Currently there are ${(await helper.getApi().query.identity.subsOf.keys()).length} identities with subs.`); + } catch (error) { + console.error(error); + throw Error('Error during setting identities'); + } + }, paraUrl); +}; + +if(process.argv[1] === module.filename) + forceInsertIdentities().catch(() => process.exit(1)); --- /dev/null +++ b/js-packages/scripts/relayIdentitiesChecker.ts @@ -0,0 +1,114 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 +// +// Checks and reports the differences between identities and sub-identities on two chains. +// +// Usage: `yarn checkRelayIdentities [relay-1 WS URL] [relay-2 WS URL]` +// Example: `yarn checkRelayIdentities wss://polkadot-rpc.dwellir.com wss://kusama-rpc.dwellir.com` + +import {encodeAddress} from '@polkadot/keyring'; +import {usingPlaygrounds} from '@unique/test-utils/util.js'; +import {getIdentities, getSubs, getSupers, constructSubInfo} from './identitySetter.js'; + +const relay1Url = process.argv[2] ?? 'ws://localhost:9844'; +const relay2Url = process.argv[3] ?? 'ws://localhost:9844'; + +async function pullIdentities(relayUrl: string): Promise<[any[], any[]]> { + const identities: any[] = []; + const subs: any[] = []; + + await usingPlaygrounds(async helper => { + try { + // iterate over every identity + for(const [key, value] of await getIdentities(helper)) { + // if any of the judgements resulted in a good confirmed outcome, keep this identity + if(value.toHuman().judgements.filter((x: any) => x[1] == 'Reasonable' || x[1] == 'KnownGood').length == 0) continue; + identities.push([key, value]); + } + + const supersOfSubs = await getSupers(helper); + + // iterate over every sub-identity + for(const [key, value] of await getSubs(helper)) { + // only get subs of the identities interesting to us + if(identities.find((x: any) => x[0] == key) == -1) continue; + subs.push(constructSubInfo(key, value, supersOfSubs)); + } + } catch (error) { + console.error(error); + throw Error(`Error during fetching identities on ${relayUrl}`); + } + }, relayUrl); + + return [identities, subs]; +} + +// The utility for pulling identity and sub-identity data +const checkRelayIdentities = async (): Promise => { + const [identitiesOnRelay1, subIdentitiesOnRelay1] = await pullIdentities(relay1Url); + const [identitiesOnRelay2, subIdentitiesOnRelay2] = await pullIdentities(relay2Url); + + console.log('identities counts:\t', identitiesOnRelay1.length, identitiesOnRelay2.length); + console.log('sub-identities counts:\t', subIdentitiesOnRelay1.length, subIdentitiesOnRelay2.length); + console.log(); + + try { + const matchingAddresses: string[] = []; + const inequalIdentities: {[name: string]: [any, any]} = {}; + + for(const [key1, value1] of identitiesOnRelay1) { + const address = encodeAddress(key1); + const identity2 = identitiesOnRelay2.find(([key2, _value2]) => address === encodeAddress(key2)); + if(!identity2) continue; + matchingAddresses.push(address); + + //const [[key2, value2]] = identitiesOnRelay2.splice(index2, 1); + const [_key2, value2] = identity2; + if(JSON.stringify(value1.info) === JSON.stringify(value2.info)) continue; + inequalIdentities[address] = [value1, value2]; + } + + /*for (const [v1, v2] of Object.values(inequalIdentities)) { + console.log(v1.toHuman().info); + console.log(); + console.log(v2.toHuman().info); + await new Promise(resolve => setTimeout(resolve, 4000)); + }*/ + + console.log(`Accounts with identities on both relays:\t${matchingAddresses.length}`); + console.log(`Sub-identities with conflicting information:\t${Object.entries(inequalIdentities).length}`); + console.log(); + + const inequalSubIdentities = []; + let matchesFound = 0; + for(const address of matchingAddresses) { + const sub1 = subIdentitiesOnRelay1.find(([key1, _value1]) => address === encodeAddress(key1)); + if(!sub1) continue; + const sub2 = subIdentitiesOnRelay2.find(([key2, _value2]) => address === encodeAddress(key2)); + if(!sub2) continue; + + const [value1, value2] = [sub1[1], sub2[1]]; + matchesFound++; + + if(JSON.stringify(value1[1]) === JSON.stringify(value2[1])) { + continue; + } + inequalSubIdentities.push([value1, value2]); + } + + /*for (const [v1, v2] of inequalSubIdentities) { + console.log(v1[1]); + console.log(); + console.log(v2[1]); + await new Promise(resolve => setTimeout(resolve, 300)); + }*/ + console.log(`Accounts with sub-identities on both relays:\t${matchesFound}`); + console.log(`Of them, those with conflicting sub-identities:\t${inequalSubIdentities.length}`); + } catch (error) { + console.error(error); + throw Error('Error during comparison'); + } +}; + +if(process.argv[1] === module.filename) + checkRelayIdentities().catch(() => process.exit(1)); --- /dev/null +++ b/js-packages/scripts/setCode.ts @@ -0,0 +1,15 @@ +import {readFile} from 'fs/promises'; +import {u8aToHex} from '@polkadot/util'; +import {usingPlaygrounds} from '@unique/test-utils/util.js'; + +const codePath = process.argv[2]; +if(!codePath) throw new Error('missing code path argument'); + +const code = await readFile(codePath); + +await usingPlaygrounds(async (helper, privateKey) => { + const alice = await privateKey('//Alice'); + await helper.getSudo().executeExtrinsicUncheckedWeight(alice, 'api.tx.system.setCode', [u8aToHex(code)]); +}); +// We miss disconnect/unref somewhere. +process.exit(0); --- a/js-packages/scripts/transfer.nload.ts +++ b/js-packages/scripts/transfer.nload.ts @@ -17,8 +17,8 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import os from 'os'; import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds} from '@unique/tests/util/index.js'; -import {UniqueHelper} from '@unique/playgrounds/unique.js'; +import {usingPlaygrounds} from '@unique/test-utils/util.js'; +import {UniqueHelper} from '@unique-nft/playgrounds/unique.js'; import * as notReallyCluster from 'cluster'; // https://github.com/nodejs/node/issues/42271#issuecomment-1063415346 const cluster = notReallyCluster as unknown as notReallyCluster.Cluster; --- /dev/null +++ b/js-packages/test-utils/globalSetup.ts @@ -0,0 +1,119 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +import { + usingPlaygrounds, Pallets, DONOR_FUNDING, MINIMUM_DONOR_FUND, LOCKING_PERIOD, UNLOCKING_PERIOD, makeNames, +} from './util.js'; +import * as path from 'path'; +import {promises as fs} from 'fs'; + +const {dirname} = makeNames(import.meta.url); + +// This function should be called before running test suites. +const globalSetup = async (): Promise => { + await usingPlaygrounds(async (helper, privateKey) => { + try { + // 1. Wait node producing blocks + await helper.wait.newBlocks(1, 600_000); + + // 2. Create donors for test files + await fundFilenamesWithRetries(3) + .then((result) => { + if(!result) throw Error('Some problems with fundFilenamesWithRetries'); + }); + + // 3. Configure App Promotion + const missingPallets = helper.fetchMissingPalletNames([Pallets.AppPromotion]); + if(missingPallets.length === 0) { + const superuser = await privateKey('//Alice'); + const palletAddress = helper.arrange.calculatePalletAddress('appstake'); + const palletAdmin = await privateKey('//PromotionAdmin'); + const api = helper.getApi(); + await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}))); + const nominal = helper.balance.getOneTokenNominal(); + await helper.balance.transferToSubstrate(superuser, palletAdmin.address, 10000n * nominal); + await helper.balance.transferToSubstrate(superuser, palletAddress, 10000n * nominal); + await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [api.tx.configuration + .setAppPromotionConfigurationOverride({ + recalculationInterval: LOCKING_PERIOD, + pendingInterval: UNLOCKING_PERIOD})], true); + } + } catch (error) { + console.error(error); + throw Error('Error during globalSetup'); + } + }); +}; + +async function getFiles(rootPath: string): Promise { + const files = await fs.readdir(rootPath, {withFileTypes: true}); + const filenames: string[] = []; + for(const entry of files) { + const res = path.resolve(rootPath, entry.name); + if(entry.isDirectory()) { + filenames.push(...await getFiles(res)); + } else { + filenames.push(res); + } + } + return filenames; +} + +const fundFilenames = async () => { + await usingPlaygrounds(async (helper, privateKey) => { + const oneToken = helper.balance.getOneTokenNominal(); + const alice = await privateKey('//Alice'); + const nonce = await helper.chain.getNonce(alice.address); + const filenames = await getFiles(path.resolve(dirname, '..')); + + // batching is actually undesireable, it takes away the time while all the transactions actually succeed + const batchSize = 300; + let balanceGrantedCounter = 0; + for(let b = 0; b < filenames.length; b += batchSize) { + const tx: Promise[] = []; + let batchBalanceGrantedCounter = 0; + for(let i = 0; batchBalanceGrantedCounter < batchSize && b + i < filenames.length; i++) { + const f = filenames[b + i]; + if(!f.endsWith('.test.ts') && !f.endsWith('seqtest.ts') || f.includes('.outdated')) continue; + const account = await privateKey({filename: f, ignoreFundsPresence: true}); + const aliceBalance = await helper.balance.getSubstrate(account.address); + + if(aliceBalance < MINIMUM_DONOR_FUND * oneToken) { + tx.push(helper.executeExtrinsic( + alice, + 'api.tx.balances.transferKeepAlive', + [account.address, DONOR_FUNDING * oneToken], + true, + {nonce: nonce + balanceGrantedCounter++}, + ).then(() => true).catch(() => {console.error(`Transaction to ${path.basename(f)} registered as failed. Strange.`); return false;})); + batchBalanceGrantedCounter++; + } + } + + if(tx.length > 0) { + console.log(`Granting funds to ${batchBalanceGrantedCounter} filename accounts.`); + const result = await Promise.all(tx); + if(result && result.lastIndexOf(false) > -1) throw new Error('The transactions actually probably succeeded, should check the balances.'); + } + } + + if(balanceGrantedCounter == 0) console.log('No account needs additional funding.'); + }); +}; + +const fundFilenamesWithRetries = (retriesLeft: number): Promise => { + if(retriesLeft <= 0) return Promise.resolve(false); + return fundFilenames() + .then(() => Promise.resolve(true)) + .catch(e => { + console.error(e); + console.error(`Some transactions might have failed. ${retriesLeft > 1 ? 'Retrying...' : 'Something is wrong.'}\n`); + return fundFilenamesWithRetries(--retriesLeft); + }); +}; + +globalSetup().catch(e => { + console.error('Setup error'); + console.error(e); + process.exit(1); +}); --- /dev/null +++ b/js-packages/test-utils/governance.ts @@ -0,0 +1,531 @@ +import {blake2AsHex} from '@polkadot/util-crypto'; +import type {PalletDemocracyConviction} from '@polkadot/types/lookup'; +import type {IPhasicEvent, TSigner} from '@unique-nft/playgrounds/types.js'; +import {HelperGroup, UniqueHelper} from '@unique-nft/playgrounds/unique.js'; + +export class CollectiveGroup extends HelperGroup { + /** + * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee' + */ + private collective: string; + + constructor(helper: UniqueHelper, collective: string) { + super(helper); + this.collective = collective; + } + + /** + * Check the result of a proposal execution for the success of the underlying proposed extrinsic. + * @param events events of the proposal execution + * @returns proposal hash + */ + private checkExecutedEvent(events: IPhasicEvent[]): string { + const executionEvents = events.filter(x => + x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted')); + + if(executionEvents.length != 1) { + if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0) + throw new Error(`Disapproved by ${this.collective}`); + else + throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`); + } + + const result = (executionEvents[0].event.data as any).result; + + if(result.isErr) { + if(result.asErr.isModule) { + const error = result.asErr.asModule; + const metaError = this.helper.getApi()?.registry.findMetaError(error); + throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`); + } else { + throw new Error('Proposal execution failed with ' + result.asErr.toHuman()); + } + } + + return (executionEvents[0].event.data as any).proposalHash; + } + + /** + * Returns an array of members' addresses. + */ + async getMembers() { + return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman(); + } + + /** + * Returns the optional address of the prime member of the collective. + */ + async getPrimeMember() { + return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman(); + } + + /** + * Returns an array of proposal hashes that are currently active for this collective. + */ + async getProposals() { + return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman(); + } + + /** + * Returns the call originally encoded under the specified hash. + * @param hash h256-encoded proposal + * @returns the optional call that the proposal hash stands for. + */ + async getProposalCallOf(hash: string) { + return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman(); + } + + /** + * Returns the total number of proposals so far. + */ + async getTotalProposalsCount() { + return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber(); + } + + /** + * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately. + * @param signer keyring of the proposer + * @param proposal constructed call to be executed if the proposal is successful + * @param voteThreshold minimal number of votes for the proposal to be verified and executed + * @param lengthBound byte length of the encoded call + * @returns promise of extrinsic execution and its result + */ + async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) { + return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]); + } + + /** + * Casts a vote to either approve or reject a proposal. + * @param signer keyring of the voter + * @param proposalHash hash of the proposal to be voted for + * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors + * @param approve aye or nay + * @returns promise of extrinsic execution and its result + */ + vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]); + } + + /** + * Executes a call immediately as a member of the collective. Needed for the Member origin. + * @param signer keyring of the executor member + * @param proposal constructed call to be executed by the member + * @param lengthBound byte length of the encoded call + * @returns promise of extrinsic execution + */ + async execute(signer: TSigner, proposal: any, lengthBound = 10000) { + const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]); + this.checkExecutedEvent(result.result.events); + return result; + } + + /** + * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing. + * @param signer keyring of the executor. Can be absolutely anyone. + * @param proposalHash hash of the proposal to close + * @param proposalIndex index of the proposal generated on its creation + * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call. + * @param lengthBound byte length of the encoded call + * @returns promise of extrinsic execution and its result + */ + async close( + signer: TSigner, + proposalHash: string, + proposalIndex: number, + weightBound: [number, number] | any = [20_000_000_000, 1000_000], + lengthBound = 10_000, + ) { + const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [ + proposalHash, + proposalIndex, + weightBound, + lengthBound, + ]); + this.checkExecutedEvent(result.result.events); + return result; + } + + /** + * Shut down a proposal, regardless of its current state. + * @param signer keyring of the disapprover. Must be root + * @param proposalHash hash of the proposal to close + * @returns promise of extrinsic execution and its result + */ + disapproveProposal(signer: TSigner, proposalHash: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]); + } +} + +export class CollectiveMembershipGroup extends HelperGroup { + /** + * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership' + */ + private membership: string; + + constructor(helper: UniqueHelper, membership: string) { + super(helper); + this.membership = membership; + } + + /** + * Returns an array of members' addresses according to the membership pallet's perception. + * Note that it does not recognize the original pallet's members set with `setMembers()`. + */ + async getMembers() { + return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman(); + } + + /** + * Returns the optional address of the prime member of the collective. + */ + async getPrimeMember() { + return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman(); + } + + /** + * Add a member to the collective. + * @param signer keyring of the setter. Must be root + * @param member address of the member to add + * @returns promise of extrinsic execution and its result + */ + addMember(signer: TSigner, member: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]); + } + + addMemberCall(member: string) { + return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]); + } + + /** + * Remove a member from the collective. + * @param signer keyring of the setter. Must be root + * @param member address of the member to remove + * @returns promise of extrinsic execution and its result + */ + removeMember(signer: TSigner, member: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]); + } + + removeMemberCall(member: string) { + return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]); + } + + /** + * Set members of the collective to the given list of addresses. + * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) + * @param members addresses of the members to set + * @returns promise of extrinsic execution and its result + */ + resetMembers(signer: TSigner, members: string[]) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]); + } + + /** + * Set the collective's prime member to the given address. + * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) + * @param prime address of the prime member of the collective + * @returns promise of extrinsic execution and its result + */ + setPrime(signer: TSigner, prime: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]); + } + + setPrimeCall(member: string) { + return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]); + } + + /** + * Remove the collective's prime member. + * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) + * @returns promise of extrinsic execution and its result + */ + clearPrime(signer: TSigner) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []); + } + + clearPrimeCall() { + return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []); + } +} + +export class RankedCollectiveGroup extends HelperGroup { + /** + * Pallet name to make an API call to. Examples: 'FellowshipCollective' + */ + private collective: string; + + constructor(helper: UniqueHelper, collective: string) { + super(helper); + this.collective = collective; + } + + addMember(signer: TSigner, newMember: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]); + } + + addMemberCall(newMember: string) { + return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]); + } + + removeMember(signer: TSigner, member: string, minRank: number) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]); + } + + removeMemberCall(newMember: string, minRank: number) { + return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]); + } + + promote(signer: TSigner, member: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]); + } + + promoteCall(member: string) { + return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]); + } + + demote(signer: TSigner, member: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]); + } + + demoteCall(newMember: string) { + return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]); + } + + vote(signer: TSigner, pollIndex: number, aye: boolean) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]); + } + + async getMembers() { + return (await this.helper.getApi().query.fellowshipCollective.members.keys()) + .map((key) => key.args[0].toString()); + } + + async getMemberRank(member: string) { + return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank; + } +} + +export class ReferendaGroup extends HelperGroup { + /** + * Pallet name to make an API call to. Examples: 'FellowshipReferenda' + */ + private referenda: string; + + constructor(helper: UniqueHelper, referenda: string) { + super(helper); + this.referenda = referenda; + } + + submit( + signer: TSigner, + proposalOrigin: string, + proposal: any, + enactmentMoment: any, + ) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [ + {Origins: proposalOrigin}, + proposal, + enactmentMoment, + ]); + } + + placeDecisionDeposit(signer: TSigner, referendumIndex: number) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]); + } + + cancel(signer: TSigner, referendumIndex: number) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]); + } + + cancelCall(referendumIndex: number) { + return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]); + } + + async referendumInfo(referendumIndex: number) { + return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON(); + } + + async enactmentEventId(referendumIndex: number) { + const api = await this.helper.getApi(); + + const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a(); + return blake2AsHex(bytes, 256); + } +} + +export interface IFellowshipGroup { + collective: RankedCollectiveGroup; + referenda: ReferendaGroup; +} + +export interface ICollectiveGroup { + collective: CollectiveGroup; + membership: CollectiveMembershipGroup; +} + +export class DemocracyGroup extends HelperGroup { + // todo displace proposal into types? + propose(signer: TSigner, call: any, deposit: bigint) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]); + } + + proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]); + } + + proposeCall(call: any, deposit: bigint) { + return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]); + } + + second(signer: TSigner, proposalIndex: number) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]); + } + + externalPropose(signer: TSigner, proposalCall: any) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeMajority(signer: TSigner, proposalCall: any) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeDefault(signer: TSigner, proposalCall: any) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]); + } + + externalProposeCall(proposalCall: any) { + return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeMajorityCall(proposalCall: any) { + return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeDefaultCall(proposalCall: any) { + return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeDefaultWithPreimageCall(preimage: string) { + return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]); + } + + // ... and blacklist external proposal hash. + vetoExternal(signer: TSigner, proposalHash: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]); + } + + vetoExternalCall(proposalHash: string) { + return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]); + } + + blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]); + } + + blacklistCall(proposalHash: string, referendumIndex: number | null = null) { + return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]); + } + + // proposal. CancelProposalOrigin (root or all techcom) + cancelProposal(signer: TSigner, proposalIndex: number) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]); + } + + cancelProposalCall(proposalIndex: number) { + return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]); + } + + clearPublicProposals(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []); + } + + fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); + } + + fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) { + return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); + } + + // referendum. CancellationOrigin (TechCom member) + emergencyCancel(signer: TSigner, referendumIndex: number) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]); + } + + emergencyCancelCall(referendumIndex: number) { + return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]); + } + + vote(signer: TSigner, referendumIndex: number, vote: any) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]); + } + + removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) { + if(targetAccount) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]); + } else { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]); + } + } + + unlock(signer: TSigner, targetAccount: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]); + } + + delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]); + } + + undelegate(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []); + } + + async referendumInfo(referendumIndex: number) { + return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON(); + } + + async publicProposals() { + return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON(); + } + + async findPublicProposal(proposalIndex: number) { + const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex); + + return proposalInfo ? proposalInfo[1] : null; + } + + async expectPublicProposal(proposalIndex: number) { + const proposal = await this.findPublicProposal(proposalIndex); + + if(proposal) { + return proposal; + } else { + throw Error(`Proposal #${proposalIndex} is expected to exist`); + } + } + + async getExternalProposal() { + return (await this.helper.callRpc('api.query.democracy.nextExternal', [])); + } + + async expectExternalProposal() { + const proposal = await this.getExternalProposal(); + + if(proposal) { + return proposal; + } else { + throw Error('An external proposal is expected to exist'); + } + } + + /* setMetadata? */ + + /* todo? + referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true); + }*/ +} --- /dev/null +++ b/js-packages/test-utils/index.ts @@ -0,0 +1,1646 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +import '@unique-nft/opal-testnet-types/augment-api.js'; +import '@unique-nft/opal-testnet-types/augment-types.js'; +import '@unique-nft/opal-testnet-types/types-lookup.js'; + +import {stringToU8a} from '@polkadot/util'; +import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto'; +import type {ChainHelperBaseConstructor, UniqueHelperConstructor} from '@unique-nft/playgrounds/unique.js'; +import {UniqueHelper, ChainHelperBase, HelperGroup} from '@unique-nft/playgrounds/unique.js'; +import {ApiPromise, Keyring, WsProvider} from '@polkadot/api'; +import * as defs from '@unique-nft/opal-testnet-types/definitions.js'; +import type {IKeyringPair} from '@polkadot/types/types'; +import type {EventRecord} from '@polkadot/types/interfaces'; +import type {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from '@unique-nft/playgrounds/types.js'; +import type {FrameSystemEventRecord, StagingXcmV2TraitsError, StagingXcmV3TraitsOutcome} from '@polkadot/types/lookup'; +import type {SignerOptions, VoidFn} from '@polkadot/api/types'; +import {spawnSync} from 'child_process'; +import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup, HydraDxHelper} from './xcm/index.js'; +import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './governance.js'; +import type {ICollectiveGroup, IFellowshipGroup} from './governance.js'; + +export class SilentLogger { + log(_msg: any, _level: any): void { } + level = { + ERROR: 'ERROR' as const, + WARNING: 'WARNING' as const, + INFO: 'INFO' as const, + }; +} + +export class SilentConsole { + // TODO: Remove, this is temporary: Filter unneeded API output + // (Jaco promised it will be removed in the next version) + consoleErr: any; + consoleLog: any; + consoleWarn: any; + + constructor() { + this.consoleErr = console.error; + this.consoleLog = console.log; + this.consoleWarn = console.warn; + } + + enable() { + const outFn = (printer: any) => (...args: any[]) => { + for(const arg of args) { + if(typeof arg !== 'string') + continue; + 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']; + const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false); + if(needToSkip || arg === 'Normal connection closure') + return; + } + printer(...args); + }; + + console.error = outFn(this.consoleErr.bind(console)); + console.log = outFn(this.consoleLog.bind(console)); + console.warn = outFn(this.consoleWarn.bind(console)); + } + + disable() { + console.error = this.consoleErr; + console.log = this.consoleLog; + console.warn = this.consoleWarn; + } +} + +export interface IEventHelper { + section(): string; + + method(): string; + + wrapEvent(data: any[]): any; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) { + const helperClass = class implements IEventHelper { + wrapEvent: (data: any[]) => any; + _section: string; + _method: string; + + constructor() { + this.wrapEvent = wrapEvent; + this._section = section; + this._method = method; + } + + section(): string { + return this._section; + } + + method(): string { + return this._method; + } + + filter(txres: ITransactionResult) { + return txres.result.events.filter(e => e.event.section === section && e.event.method === method) + .map(e => this.wrapEvent(e.event.data)); + } + + find(txres: ITransactionResult) { + const e = txres.result.events.find(e => e.event.section === section && e.event.method === method); + return e ? this.wrapEvent(e.event.data) : null; + } + + expect(txres: ITransactionResult) { + const e = this.find(txres); + if(e) { + return e; + } else { + throw Error(`Expected event ${section}.${method}`); + } + } + }; + + return helperClass; +} + +function eventJsonData(data: any[], index: number) { + return data[index].toJSON() as T; +} + +function eventHumanData(data: any[], index: number) { + return data[index].toHuman(); +} + +function eventData(data: any[], index: number) { + return data[index] as T; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +function EventSection(section: string) { + return class Section { + static section = section; + + static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) { + const helperClass = EventHelper(Section.section, name, wrapEvent); + return new helperClass(); + } + }; +} + +function schedulerSection(schedulerInstance: string) { + return class extends EventSection(schedulerInstance) { + static Dispatched = this.Method('Dispatched', data => ({ + task: eventJsonData(data, 0), + id: eventHumanData(data, 1), + result: data[2], + })); + + static PriorityChanged = this.Method('PriorityChanged', data => ({ + task: eventJsonData(data, 0), + priority: eventJsonData(data, 1), + })); + }; +} + +export class Event { + static Democracy = class extends EventSection('democracy') { + static Proposed = this.Method('Proposed', data => ({ + proposalIndex: eventJsonData(data, 0), + })); + + static ExternalTabled = this.Method('ExternalTabled'); + + static Started = this.Method('Started', data => ({ + referendumIndex: eventJsonData(data, 0), + threshold: eventHumanData(data, 1), + })); + + static Voted = this.Method('Voted', data => ({ + voter: eventJsonData(data, 0), + referendumIndex: eventJsonData(data, 1), + vote: eventJsonData(data, 2), + })); + + static Passed = this.Method('Passed', data => ({ + referendumIndex: eventJsonData(data, 0), + })); + + static ProposalCanceled = this.Method('ProposalCanceled', data => ({ + propIndex: eventJsonData(data, 0), + })); + + static Cancelled = this.Method('Cancelled', data => ({ + propIndex: eventJsonData(data, 0), + })); + + static Vetoed = this.Method('Vetoed', data => ({ + who: eventHumanData(data, 0), + proposalHash: eventHumanData(data, 1), + until: eventJsonData(data, 1), + })); + }; + + static Council = class extends EventSection('council') { + static Proposed = this.Method('Proposed', data => ({ + account: eventHumanData(data, 0), + proposalIndex: eventJsonData(data, 1), + proposalHash: eventHumanData(data, 2), + threshold: eventJsonData(data, 3), + })); + static Closed = this.Method('Closed', data => ({ + proposalHash: eventHumanData(data, 0), + yes: eventJsonData(data, 1), + no: eventJsonData(data, 2), + })); + static Executed = this.Method('Executed', data => ({ + proposalHash: eventHumanData(data, 0), + })); + }; + + static TechnicalCommittee = class extends EventSection('technicalCommittee') { + static Proposed = this.Method('Proposed', data => ({ + account: eventHumanData(data, 0), + proposalIndex: eventJsonData(data, 1), + proposalHash: eventHumanData(data, 2), + threshold: eventJsonData(data, 3), + })); + static Closed = this.Method('Closed', data => ({ + proposalHash: eventHumanData(data, 0), + yes: eventJsonData(data, 1), + no: eventJsonData(data, 2), + })); + static Approved = this.Method('Approved', data => ({ + proposalHash: eventHumanData(data, 0), + })); + static Executed = this.Method('Executed', data => ({ + proposalHash: eventHumanData(data, 0), + result: eventHumanData(data, 1), + })); + }; + + static FellowshipReferenda = class extends EventSection('fellowshipReferenda') { + static Submitted = this.Method('Submitted', data => ({ + referendumIndex: eventJsonData(data, 0), + trackId: eventJsonData(data, 1), + proposal: eventJsonData(data, 2), + })); + + static Cancelled = this.Method('Cancelled', data => ({ + index: eventJsonData(data, 0), + tally: eventJsonData(data, 1), + })); + }; + + static UniqueScheduler = schedulerSection('uniqueScheduler'); + static Scheduler = schedulerSection('scheduler'); + + static XcmpQueue = class extends EventSection('xcmpQueue') { + static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({ + messageHash: eventJsonData(data, 0), + })); + + static Success = this.Method('Success', data => ({ + messageHash: eventJsonData(data, 0), + })); + + static Fail = this.Method('Fail', data => ({ + messageHash: eventJsonData(data, 0), + outcome: eventData(data, 2), + })); + }; + + static DmpQueue = class extends EventSection('dmpQueue') { + static ExecutedDownward = this.Method('ExecutedDownward', data => ({ + outcome: eventData(data, 2), + })); + }; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +export function SudoHelper(Base: T) { + return class extends Base { + constructor(...args: any[]) { + super(...args); + } + + override async executeExtrinsic( + sender: IKeyringPair, + extrinsic: string, + params: any[], + expectSuccess?: boolean, + options: Partial | null = null, + ): Promise { + const call = this.constructApiCall(extrinsic, params); + const result = await super.executeExtrinsic( + sender, + 'api.tx.sudo.sudo', + [call], + expectSuccess, + options, + ); + + if(result.status === 'Fail') return result; + + const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult; + if(data.isErr) { + if(data.asErr.isModule) { + const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule; + const metaError = super.getApi()?.registry.findMetaError(error); + throw new Error(`${metaError.section}.${metaError.name}`); + } else if(data.asErr.isToken) { + throw new Error(`Token: ${data.asErr.asToken}`); + } + // May be [object Object] in case of unhandled non-unit enum + throw new Error(`Misc: ${data.asErr.toHuman()}`); + } + return result; + } + override async executeExtrinsicUncheckedWeight( + sender: IKeyringPair, + extrinsic: string, + params: any[], + expectSuccess?: boolean, + options: Partial | null = null, + ): Promise { + const call = this.constructApiCall(extrinsic, params); + const result = await super.executeExtrinsic( + sender, + 'api.tx.sudo.sudoUncheckedWeight', + [call, {refTime: 0, proofSize: 0}], + expectSuccess, + options, + ); + + if(result.status === 'Fail') return result; + + const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult; + if(data.isErr) { + if(data.asErr.isModule) { + const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule; + const metaError = super.getApi()?.registry.findMetaError(error); + throw new Error(`${metaError.section}.${metaError.name}`); + } else if(data.asErr.isToken) { + throw new Error(`Token: ${data.asErr.asToken}`); + } + // May be [object Object] in case of unhandled non-unit enum + throw new Error(`Misc: ${data.asErr.toHuman()}`); + } + return result; + } + }; +} + +class SchedulerGroup extends HelperGroup { + constructor(helper: UniqueHelper) { + super(helper); + } + + cancelScheduled(signer: TSigner, scheduledId: string) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.scheduler.cancelNamed', + [scheduledId], + true, + ); + } + + changePriority(signer: TSigner, scheduledId: string, priority: number) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.scheduler.changeNamedPriority', + [scheduledId, priority], + true, + ); + } + + scheduleAt( + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + return this.schedule('schedule', executionBlockNumber, options); + } + + scheduleAfter( + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + return this.schedule('scheduleAfter', blocksBeforeExecution, options); + } + + schedule( + scheduleFn: 'schedule' | 'scheduleAfter', + blocksNum: number, + options: ISchedulerOptions = {}, + ) { + // eslint-disable-next-line @typescript-eslint/naming-convention + const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase); + return this.helper.clone(ScheduledHelperType, { + scheduleFn, + blocksNum, + options, + }) as T; + } +} + +class CollatorSelectionGroup extends HelperGroup { + //todo:collator documentation + addInvulnerable(signer: TSigner, address: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]); + } + + removeInvulnerable(signer: TSigner, address: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]); + } + + async getInvulnerables(): Promise { + return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman()); + } + + /** and also total max invulnerables */ + maxCollators(): number { + return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number); + } + + async getDesiredCollators(): Promise { + return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber(); + } + + setLicenseBond(signer: TSigner, amount: bigint) { + return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]); + } + + async getLicenseBond(): Promise { + return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt(); + } + + obtainLicense(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []); + } + + releaseLicense(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []); + } + + forceReleaseLicense(signer: TSigner, released: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]); + } + + async hasLicense(address: string): Promise { + return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt(); + } + + onboard(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []); + } + + offboard(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []); + } + + async getCandidates(): Promise { + return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman()); + } +} + +export class DevUniqueHelper extends UniqueHelper { + /** + * Arrange methods for tests + */ + arrange: ArrangeGroup; + wait: WaitGroup; + admin: AdminGroup; + session: SessionGroup; + testUtils: TestUtilGroup; + foreignAssets: ForeignAssetsGroup; + xcm: XcmGroup; + xTokens: XTokensGroup; + tokens: TokensGroup; + scheduler: SchedulerGroup; + collatorSelection: CollatorSelectionGroup; + council: ICollectiveGroup; + technicalCommittee: ICollectiveGroup; + fellowship: IFellowshipGroup; + democracy: DemocracyGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevUniqueHelper; + + super(logger, options); + this.arrange = new ArrangeGroup(this); + this.wait = new WaitGroup(this); + this.admin = new AdminGroup(this); + this.testUtils = new TestUtilGroup(this); + this.session = new SessionGroup(this); + this.foreignAssets = new ForeignAssetsGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); + this.xTokens = new XTokensGroup(this); + this.tokens = new TokensGroup(this); + this.scheduler = new SchedulerGroup(this); + this.collatorSelection = new CollatorSelectionGroup(this); + this.council = { + collective: new CollectiveGroup(this, 'council'), + membership: new CollectiveMembershipGroup(this, 'councilMembership'), + }; + this.technicalCommittee = { + collective: new CollectiveGroup(this, 'technicalCommittee'), + membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'), + }; + this.fellowship = { + collective: new RankedCollectiveGroup(this, 'fellowshipCollective'), + referenda: new ReferendaGroup(this, 'fellowshipReferenda'), + }; + this.democracy = new DemocracyGroup(this); + } + + override async connect(wsEndpoint: string, _listeners?: any): Promise { + if(!wsEndpoint) throw new Error('wsEndpoint was not set'); + const wsProvider = new WsProvider(wsEndpoint); + this.api = new ApiPromise({ + provider: wsProvider, + signedExtensions: { + ContractHelpers: { + extrinsic: {}, + payload: {}, + }, + CheckMaintenance: { + extrinsic: {}, + payload: {}, + }, + DisableIdentityCalls: { + extrinsic: {}, + payload: {}, + }, + FakeTransactionFinalizer: { + extrinsic: {}, + payload: {}, + }, + }, + rpc: { + unique: defs.unique.rpc, + appPromotion: defs.appPromotion.rpc, + povinfo: defs.povinfo.rpc, + eth: { + feeHistory: { + description: 'Dummy', + params: [], + type: 'u8', + }, + maxPriorityFeePerGas: { + description: 'Dummy', + params: [], + type: 'u8', + }, + }, + }, + }); + await this.api.isReadyOrError; + this.network = await UniqueHelper.detectNetwork(this.api); + this.wsEndpoint = wsEndpoint; + } + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoHelper(this.helperBase); + return this.clone(SudoHelperType) as T; + } +} + +export class DevRelayHelper extends RelayHelper { + wait: WaitGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevRelayHelper; + + super(logger, options); + this.wait = new WaitGroup(this); + } + + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoHelper(this.helperBase); + return this.clone(SudoHelperType) as DevRelayHelper; + } +} + +export class DevWestmintHelper extends WestmintHelper { + wait: WaitGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevWestmintHelper; + + super(logger, options); + this.wait = new WaitGroup(this); + } +} + +export class DevStatemineHelper extends DevWestmintHelper {} + +export class DevStatemintHelper extends DevWestmintHelper {} + +export class DevMoonbeamHelper extends MoonbeamHelper { + account: MoonbeamAccountGroup; + wait: WaitGroup; + fastDemocracy: MoonbeamFastDemocracyGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevMoonbeamHelper; + options.notePreimagePallet = options.notePreimagePallet ?? 'preimage'; + + super(logger, options); + this.account = new MoonbeamAccountGroup(this); + this.wait = new WaitGroup(this); + this.fastDemocracy = new MoonbeamFastDemocracyGroup(this); + } +} + +export class DevMoonriverHelper extends DevMoonbeamHelper { + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.notePreimagePallet = options.notePreimagePallet ?? 'preimage'; + super(logger, options); + } +} + +export class DevAstarHelper extends AstarHelper { + wait: WaitGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevAstarHelper; + + super(logger, options); + this.wait = new WaitGroup(this); + } + + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoHelper(this.helperBase); + return this.clone(SudoHelperType) as T; + } +} + +export class DevShidenHelper extends DevAstarHelper { } + +export class DevAcalaHelper extends AcalaHelper { + wait: WaitGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevAcalaHelper; + + super(logger, options); + this.wait = new WaitGroup(this); + } + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoHelper(this.helperBase); + return this.clone(SudoHelperType) as DevAcalaHelper; + } +} + +export class DevPolkadexHelper extends PolkadexHelper { + wait: WaitGroup; + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? PolkadexHelper; + + super(logger, options); + this.wait = new WaitGroup(this); + } + + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoHelper(this.helperBase); + return this.clone(SudoHelperType) as DevPolkadexHelper; + } +} + +export class DevHydraDxHelper extends HydraDxHelper { + wait: WaitGroup; + fastDemocracy: HydraFastDemocracyGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevHydraDxHelper; + + super(logger, options); + + this.wait = new WaitGroup(this); + this.fastDemocracy = new HydraFastDemocracyGroup(this); + } +} + +export class DevKaruraHelper extends DevAcalaHelper {} + +export class ArrangeGroup { + helper: DevUniqueHelper; + + scheduledIdSlider = 0; + + constructor(helper: DevUniqueHelper) { + this.helper = helper; + } + + /** + * Generates accounts with the specified UNQ token balance + * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal. + * @param donor donor account for balances + * @returns array of newly created accounts + * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor); + */ + createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise => { + let nonce = await this.helper.chain.getNonce(donor.address); + const wait = new WaitGroup(this.helper); + const ss58Format = this.helper.chain.getChainProperties().ss58Format; + const tokenNominal = this.helper.balance.getOneTokenNominal(); + const transactions = []; + const accounts: IKeyringPair[] = []; + for(const balance of balances) { + const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format); + accounts.push(recipient); + if(balance !== 0n) { + const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, balance * tokenNominal]); + transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation')); + nonce++; + } + } + + await Promise.all(transactions).catch(_e => {}); + + //#region TODO remove this region, when nonce problem will be solved + const checkBalances = async () => { + let isSuccess = true; + for(let i = 0; i < balances.length; i++) { + const balance = await this.helper.balance.getSubstrate(accounts[i].address); + if(balance !== balances[i] * tokenNominal) { + isSuccess = false; + break; + } + } + return isSuccess; + }; + + let accountsCreated = false; + const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5; + // checkBalances retry up to 5-50 blocks + for(let index = 0; index < maxBlocksChecked; index++) { + accountsCreated = await checkBalances(); + if(accountsCreated) break; + await wait.newBlocks(1); + } + + if(!accountsCreated) throw Error('Accounts generation failed'); + //#endregion + + return accounts; + }; + + // TODO combine this method and createAccounts into one + createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise => { + const createAsManyAsCan = async () => { + let transactions: any = []; + const accounts: IKeyringPair[] = []; + let nonce = await this.helper.chain.getNonce(donor.address); + const tokenNominal = this.helper.balance.getOneTokenNominal(); + const ss58Format = this.helper.chain.getChainProperties().ss58Format; + for(let i = 0; i < accountsToCreate; i++) { + if(i === 500) { // if there are too many accounts to create + await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled + transactions = []; // + nonce = await this.helper.chain.getNonce(donor.address); // update nonce + } + const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format); + accounts.push(recipient); + if(withBalance !== 0n) { + const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, withBalance * tokenNominal]); + transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation')); + nonce++; + } + } + + const fullfilledAccounts = []; + await Promise.allSettled(transactions); + for(const account of accounts) { + const accountBalance = await this.helper.balance.getSubstrate(account.address); + if(accountBalance === withBalance * tokenNominal) { + fullfilledAccounts.push(account); + } + } + return fullfilledAccounts; + }; + + + const crowd: IKeyringPair[] = []; + // do up to 5 retries + for(let index = 0; index < 5 && accountsToCreate !== 0; index++) { + const asManyAsCan = await createAsManyAsCan(); + crowd.push(...asManyAsCan); + accountsToCreate -= asManyAsCan.length; + } + + if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`); + + return crowd; + }; + + /** + * Generates one account with zero balance + * @returns the newly generated account + * @example const account = await helper.arrange.createEmptyAccount(); + */ + createEmptyAccount = (): IKeyringPair => { + const ss58Format = this.helper.chain.getChainProperties().ss58Format; + return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format); + }; + + isDevNode = async () => { + let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON(); + if(blockNumber == 0) { + await this.helper.wait.newBlocks(1); + blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON(); + } + const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]); + const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]); + const findCreationDate = (block: any) => { + const humanBlock = block.toHuman(); + let date; + humanBlock.block.extrinsics.forEach((ext: any) => { + if(ext.method.section === 'timestamp') { + date = Number(ext.method.args.now.replaceAll(',', '')); + } + }); + return date; + }; + const block1date = await findCreationDate(block1); + const block2date = await findCreationDate(block2); + if(block2date! - block1date! < 9000) return true; + return false; + }; + + async calculcateFee(payer: ICrossAccountId, promise: () => Promise): Promise { + const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum); + let balance = await this.helper.balance.getSubstrate(address); + + await promise(); + + balance -= await this.helper.balance.getSubstrate(address); + + return balance; + } + + async calculatePoVInfo(txs: any[]): Promise { + const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]); + + const kvJson: {[key: string]: string} = {}; + + for(const kv of rawPovInfo.keyValues) { + kvJson[kv.key.toHex()] = kv.value.toHex(); + } + + const kvStr = JSON.stringify(kvJson); + + const chainql = spawnSync( + 'chainql', + [ + `--tla-code=data=${kvStr}`, + '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`, + ], + ); + + if(!chainql.stdout) { + throw Error('unable to get an output from the `chainql`'); + } + + return { + proofSize: rawPovInfo.proofSize.toNumber(), + compactProofSize: rawPovInfo.compactProofSize.toNumber(), + compressedProofSize: rawPovInfo.compressedProofSize.toNumber(), + results: rawPovInfo.results, + kv: JSON.parse(chainql.stdout.toString()), + }; + } + + calculatePalletAddress(palletId: any) { + const address = stringToU8a(('modl' + palletId).padEnd(32, '\0')); + return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format); + } + + makeScheduledIds(num: number): string[] { + function makeId(slider: number) { + const scheduledIdSize = 64; + const hexId = slider.toString(16); + const prefixSize = scheduledIdSize - hexId.length; + + const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId; + + return scheduledId; + } + + const ids = []; + for(let i = 0; i < num; i++) { + ids.push(makeId(this.scheduledIdSlider)); + this.scheduledIdSlider += 1; + } + + return ids; + } + + makeScheduledId(): string { + return (this.makeScheduledIds(1))[0]; + } + + async captureEvents(eventSection: string, eventMethod: string): Promise { + const capture = new EventCapture(this.helper, eventSection, eventMethod); + await capture.startCapture(); + + return capture; + } + + makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) { + return { + V2: [ + { + WithdrawAsset: [ + { + id, + fun: { + Fungible: amount, + }, + }, + ], + }, + { + BuyExecution: { + fees: { + id, + fun: { + Fungible: amount, + }, + }, + weightLimit: 'Unlimited', + }, + }, + { + DepositAsset: { + assets: { + Wild: 'All', + }, + maxAssets: 1, + beneficiary: { + parents: 0, + interior: { + X1: { + AccountId32: { + network: 'Any', + id: beneficiary, + }, + }, + }, + }, + }, + }, + ], + }; + } + + makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) { + return { + V2: [ + { + ReserveAssetDeposited: [ + { + id, + fun: { + Fungible: amount, + }, + }, + ], + }, + { + BuyExecution: { + fees: { + id, + fun: { + Fungible: amount, + }, + }, + weightLimit: 'Unlimited', + }, + }, + { + DepositAsset: { + assets: { + Wild: 'All', + }, + maxAssets: 1, + beneficiary: { + parents: 0, + interior: { + X1: { + AccountId32: { + network: 'Any', + id: beneficiary, + }, + }, + }, + }, + }, + }, + ], + }; + } + + makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) { + return { + V3: [ + { + UnpaidExecution: { + weightLimit: 'Unlimited', + checkOrigin: null, + }, + }, + { + Transact: { + originKind: 'Superuser', + requireWeightAtMost: { + refTime: info.weightMultiplier * 200000000, + proofSize: info.weightMultiplier * 3000, + }, + call: { + encoded: info.call, + }, + }, + }, + ], + }; + } +} + +class MoonbeamAccountGroup { + helper: MoonbeamHelper; + + keyring: Keyring; + _alithAccount: IKeyringPair; + _baltatharAccount: IKeyringPair; + _dorothyAccount: IKeyringPair; + + constructor(helper: MoonbeamHelper) { + this.helper = helper; + + this.keyring = new Keyring({type: 'ethereum'}); + const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133'; + const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b'; + const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68'; + + this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum'); + this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum'); + this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum'); + } + + alithAccount() { + return this._alithAccount; + } + + baltatharAccount() { + return this._baltatharAccount; + } + + dorothyAccount() { + return this._dorothyAccount; + } + + create() { + return this.keyring.addFromUri(mnemonicGenerate()); + } +} + +class MoonbeamFastDemocracyGroup { + helper: DevMoonbeamHelper; + + constructor(helper: DevMoonbeamHelper) { + this.helper = helper; + } + + async executeProposal(proposalDesciption: string, encodedProposal: string) { + const proposalHash = blake2AsHex(encodedProposal); + + const alithAccount = this.helper.account.alithAccount(); + const baltatharAccount = this.helper.account.baltatharAccount(); + const dorothyAccount = this.helper.account.dorothyAccount(); + + const councilVotingThreshold = 2; + const technicalCommitteeThreshold = 2; + const fastTrackVotingPeriod = 3; + const fastTrackDelayPeriod = 0; + + console.log(`[democracy] executing '${proposalDesciption}' proposal`); + + // >>> Propose external motion through council >>> + console.log('\t* Propose external motion through council.......'); + const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal}); + const encodedMotion = externalMotion?.method.toHex() || ''; + const motionHash = blake2AsHex(encodedMotion); + console.log('\t* Motion hash is %s', motionHash); + + await this.helper.collective.council.propose( + baltatharAccount, + councilVotingThreshold, + externalMotion, + externalMotion.encodedLength, + ); + + const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1; + await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true); + await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true); + + await this.helper.collective.council.close( + dorothyAccount, + motionHash, + councilProposalIdx, + { + refTime: 1_000_000_000, + proofSize: 1_000_000, + }, + externalMotion.encodedLength, + ); + console.log('\t* Propose external motion through council.......DONE'); + // <<< Propose external motion through council <<< + + // >>> Fast track proposal through technical committee >>> + console.log('\t* Fast track proposal through technical committee.......'); + const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod); + const encodedFastTrack = fastTrack?.method.toHex() || ''; + const fastTrackHash = blake2AsHex(encodedFastTrack); + console.log('\t* FastTrack hash is %s', fastTrackHash); + + await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength); + + const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1; + await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true); + await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true); + + await this.helper.collective.techCommittee.close( + baltatharAccount, + fastTrackHash, + techProposalIdx, + { + refTime: 1_000_000_000, + proofSize: 1_000_000, + }, + fastTrack.encodedLength, + ); + console.log('\t* Fast track proposal through technical committee.......DONE'); + // <<< Fast track proposal through technical committee <<< + + const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started); + const referendumIndex = democracyStarted.referendumIndex; + + // >>> Referendum voting >>> + console.log(`\t* Referendum #${referendumIndex} voting.......`); + await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, { + balance: 10_000_000_000_000_000_000n, + vote: {aye: true, conviction: 1}, + }); + console.log(`\t* Referendum #${referendumIndex} voting.......DONE`); + // <<< Referendum voting <<< + + // Wait the proposal to pass + await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex); + + await this.helper.wait.newBlocks(1); + + console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`); + } +} + +class HydraFastDemocracyGroup { + helper: DevHydraDxHelper; + + constructor(helper: DevHydraDxHelper) { + this.helper = helper; + } + + async executeProposal(proposalDesciption: string, encodedProposal: string) { + const proposalHash = blake2AsHex(encodedProposal); + const aliceAccount = this.helper.util.fromSeed('//Alice'); + const bobAccount = this.helper.util.fromSeed('//Bob'); + const eveAccount = this.helper.util.fromSeed('//Eve'); + + const councilVotingThreshold = 1; + const technicalCommitteeThreshold = 3; + const fastTrackVotingPeriod = 3; + const fastTrackDelayPeriod = 0; + + console.log(`[democracy] executing '${proposalDesciption}' proposal`); + + // >>> Propose external motion through council >>> + console.log('\t* Propose external motion through council.......'); + const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal}); + const encodedMotion = externalMotion?.method.toHex() || ''; + const motionHash = blake2AsHex(encodedMotion); + console.log('\t* Motion hash is %s', motionHash); + + await this.helper.collective.council.propose( + aliceAccount, + councilVotingThreshold, + externalMotion, + externalMotion.encodedLength, + ); + + console.log('\t* Propose external motion through council.......DONE'); + // <<< Propose external motion through council <<< + + // >>> Fast track proposal through technical committee >>> + console.log('\t* Fast track proposal through technical committee.......'); + const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod); + const encodedFastTrack = fastTrack?.method.toHex() || ''; + const fastTrackHash = blake2AsHex(encodedFastTrack); + console.log('\t* FastTrack hash is %s', fastTrackHash); + + await this.helper.collective.techCommittee.propose(aliceAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength); + + const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1; + await this.helper.collective.techCommittee.vote(aliceAccount, fastTrackHash, techProposalIdx, true); + await this.helper.collective.techCommittee.vote(bobAccount, fastTrackHash, techProposalIdx, true); + await this.helper.collective.techCommittee.vote(eveAccount, fastTrackHash, techProposalIdx, true); + + await this.helper.collective.techCommittee.close( + bobAccount, + fastTrackHash, + techProposalIdx, + { + refTime: 1_000_000_000, + proofSize: 1_000_000, + }, + fastTrack.encodedLength, + ); + console.log('\t* Fast track proposal through technical committee.......DONE'); + // <<< Fast track proposal through technical committee <<< + + const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started); + const referendumIndex = democracyStarted.referendumIndex; + + // >>> Referendum voting >>> + console.log(`\t* Referendum #${referendumIndex} voting.......`); + await this.helper.democracy.referendumVote(eveAccount, referendumIndex, { + balance: 10_000_000_000_000_000_000n, + vote: {aye: true, conviction: 1}, + }); + console.log(`\t* Referendum #${referendumIndex} voting.......DONE`); + // <<< Referendum voting <<< + + // Wait the proposal to pass + await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex); + + await this.helper.wait.newBlocks(1); + + console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`); + } +} + +class WaitGroup { + helper: ChainHelperBase; + + constructor(helper: ChainHelperBase) { + this.helper = helper; + } + + sleep(milliseconds: number) { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); + } + + private async waitWithTimeout(promise: Promise, timeout: number) { + let isBlock = false; + promise.then(() => isBlock = true).catch(() => isBlock = true); + let totalTime = 0; + const step = 100; + while(!isBlock) { + await this.sleep(step); + totalTime += step; + if(totalTime >= timeout) throw Error('Blocks production failed'); + } + return promise; + } + + /** + * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout. + * @param promise async operation to race against the timeout + * @param timeoutMS time after which to time out + * @param timeoutError error message to throw + * @returns promise of the same type the operation had + */ + withTimeout( + promise: Promise, + timeoutMS = 30000, + timeoutError = 'The operation has timed out!', + ): Promise { + const timeout = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(timeoutError)); + }, timeoutMS); + }); + + return Promise.race([promise, timeout]).catch(e => {throw new Error(e);}); + } + + /** + * Wait for specified number of blocks + * @param blocksCount number of blocks to wait + * @returns + */ + async newBlocks(blocksCount = 1, timeout?: number): Promise { + timeout = timeout ?? blocksCount * 60_000; + // eslint-disable-next-line no-async-promise-executor + const promise = new Promise(async (resolve) => { + const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => { + if(blocksCount > 0) { + blocksCount--; + } else { + unsubscribe(); + resolve(); + } + }); + }); + await this.waitWithTimeout(promise, timeout); + return promise; + } + + /** + * Wait for the specified number of sessions to pass. + * Only applicable if the Session pallet is turned on. + * @param sessionCount number of sessions to wait + * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks + * @returns + */ + async newSessions(sessionCount = 1, blockTimeout = 60000): Promise { + console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.` + + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.'); + + const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount; + let currentSessionIndex = -1; + + while(currentSessionIndex < expectedSessionIndex) { + // eslint-disable-next-line no-async-promise-executor + currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => { + await this.newBlocks(1); + const res = await (this.helper as DevUniqueHelper).session.getIndex(); + resolve(res); + }), blockTimeout, 'The chain has stopped producing blocks!'); + } + } + + async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) { + timeout = timeout ?? 30 * 60 * 1000; + // eslint-disable-next-line no-async-promise-executor + const promise = new Promise(async (resolve) => { + const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => { + if(data.number.toNumber() >= blockNumber) { + unsubscribe(); + resolve(); + } + }); + }); + await this.waitWithTimeout(promise, timeout); + return promise; + } + + async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) { + timeout = timeout ?? 30 * 60 * 1000; + // eslint-disable-next-line no-async-promise-executor + const promise = new Promise(async (resolve) => { + const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => { + if(data.value.relayParentNumber.toNumber() >= blockNumber) { + // @ts-ignore + unsubscribe(); + resolve(); + } + }); + }); + await this.waitWithTimeout(promise, timeout); + return promise; + } + + noScheduledTasks() { + const api = this.helper.getApi(); + + // eslint-disable-next-line no-async-promise-executor + const promise = new Promise(async resolve => { + const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => { + const areThereScheduledTasks = await api.query.scheduler.lookup.entries(); + + if(areThereScheduledTasks.length == 0) { + unsubscribe(); + resolve(); + } + }); + }); + + return promise; + } + + parachainBlockMultiplesOf(val: bigint) { + // eslint-disable-next-line no-async-promise-executor + const promise = new Promise(async resolve => { + const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => { + if(data.number.toBigInt() % val == 0n) { + console.log(`from waiter: ${data.number.toBigInt()}`); + unsubscribe(); + resolve(); + } + }); + }); + return promise; + } + + event( + maxBlocksToWait: number, + eventHelper: T, + filter: (_: any) => boolean = () => true, + ): any { + // eslint-disable-next-line no-async-promise-executor + const promise = new Promise(async (resolve) => { + const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => { + const blockNumber = header.number.toJSON(); + const blockHash = header.hash; + const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`; + const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`; + + this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`); + + const apiAt = await this.helper.getApi().at(blockHash); + const eventRecords = (await apiAt.query.system.events()) as any; + + const neededEvent = eventRecords.toArray() + .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method()) + .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data)) + .find(filter); + + if(neededEvent) { + unsubscribe(); + resolve(neededEvent); + } else if(maxBlocksToWait > 0) { + maxBlocksToWait--; + } else { + this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found. + The wait lasted until block ${blockNumber} inclusive`); + unsubscribe(); + resolve(null); + } + }); + }); + return promise; + } + + async expectEvent( + maxBlocksToWait: number, + eventHelper: T, + filter: (e: any) => boolean = () => true, + ) { + const e = await this.event(maxBlocksToWait, eventHelper, filter); + if(e == null) { + throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`); + } else { + return e; + } + } +} + +class SessionGroup { + helper: ChainHelperBase; + + constructor(helper: ChainHelperBase) { + this.helper = helper; + } + + //todo:collator documentation + async getIndex(): Promise { + return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber(); + } + + newSessions(sessionCount = 1, blockTimeout = 24000): Promise { + return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout); + } + + setOwnKeys(signer: TSigner, key: string) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.session.setKeys', + [key, '0x0'], + true, + ); + } + + setOwnKeysFromAddress(signer: TSigner) { + return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex')); + } +} + +class TestUtilGroup { + helper: DevUniqueHelper; + + constructor(helper: DevUniqueHelper) { + this.helper = helper; + } + + async enable(testUtilsPalletName: string) { + if(this.helper.fetchMissingPalletNames([testUtilsPalletName]).length != 0) { + return; + } + + const signer = this.helper.util.fromSeed('//Alice'); + await this.helper.getSudo().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true); + } + + async setTestValue(signer: TSigner, testVal: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true); + } + + async incTestValue(signer: TSigner) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true); + } + + async setTestValueAndRollback(signer: TSigner, testVal: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true); + } + + async testValue(blockIdx?: number) { + const api = blockIdx + ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx])) + : this.helper.getApi(); + + return (await api.query.testUtils.testValue()).toJSON(); + } + + async justTakeFee(signer: TSigner) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true); + } + + async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true); + } +} + +class EventCapture { + helper: DevUniqueHelper; + eventSection: string; + eventMethod: string; + events: EventRecord[] = []; + unsubscribe: VoidFn | null = null; + + constructor( + helper: DevUniqueHelper, + eventSection: string, + eventMethod: string, + ) { + this.helper = helper; + this.eventSection = eventSection; + this.eventMethod = eventMethod; + } + + async startCapture() { + this.stopCapture(); + this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => { + const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod); + + this.events.push(...newEvents); + })) as any; + } + + stopCapture() { + if(this.unsubscribe !== null) { + this.unsubscribe(); + } + } + + extractCapturedEvents() { + return this.events; + } +} + +class AdminGroup { + helper: UniqueHelper; + + constructor(helper: UniqueHelper) { + this.helper = helper; + } + + async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> { + const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true); + return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({ + staker: e.event.data[0].toString(), + stake: e.event.data[1].toBigInt(), + payout: e.event.data[2].toBigInt(), + })); + } +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +function ScheduledUniqueHelper(Base: T) { + return class extends Base { + scheduleFn: 'schedule' | 'scheduleAfter'; + blocksNum: number; + options: ISchedulerOptions; + + constructor(...args: any[]) { + const logger = args[0] as ILogger; + const options = args[1] as { + scheduleFn: 'schedule' | 'scheduleAfter', + blocksNum: number, + options: ISchedulerOptions + }; + + super(logger); + + this.scheduleFn = options.scheduleFn; + this.blocksNum = options.blocksNum; + this.options = options.options; + } + + override executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise { + const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams); + + const mandatorySchedArgs = [ + this.blocksNum, + this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null, + this.options.priority ?? null, + scheduledTx, + ]; + + let schedArgs; + let scheduleFn; + + if(this.options.scheduledId) { + schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs]; + + if(this.scheduleFn == 'schedule') { + scheduleFn = 'scheduleNamed'; + } else if(this.scheduleFn == 'scheduleAfter') { + scheduleFn = 'scheduleNamedAfter'; + } + } else { + schedArgs = mandatorySchedArgs; + scheduleFn = this.scheduleFn; + } + + const extrinsic = 'api.tx.scheduler.' + scheduleFn; + + return super.executeExtrinsic( + sender, + extrinsic as any, + schedArgs, + expectSuccess, + ); + } + }; +} --- /dev/null +++ b/js-packages/test-utils/package.json @@ -0,0 +1,18 @@ +{ + "author": "", + "license": "SEE LICENSE IN ../../../LICENSE", + "description": "Playground scripts", + "engines": { + "node": ">=16" + }, + "name": "@unique/test-utils", + "type": "module", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "@polkadot/api": "10.10.1", + "@polkadot/util": "^12.5.1", + "@polkadot/util-crypto": "^12.5.1", + "@unique-nft/opal-testnet-types": "workspace:*" + } +} --- /dev/null +++ b/js-packages/test-utils/util.ts @@ -0,0 +1,223 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +import * as path from 'path'; +import * as crypto from 'crypto'; +import type {IKeyringPair} from '@polkadot/types/types/interfaces'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import chaiSubset from 'chai-subset'; +import {Context} from 'mocha'; +import config from '../tests/config.js'; +import {ChainHelperBase} from '@unique-nft/playgrounds/unique.js'; +import type {ILogger} from '@unique-nft/playgrounds/types.js'; +import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper, DevHydraDxHelper} from '@unique/test-utils'; +import {dirname} from 'path'; +import {fileURLToPath} from 'url'; + +chai.config.truncateThreshold = 0; +chai.use(chaiAsPromised); +chai.use(chaiSubset); +export const expect = chai.expect; + +const getTestHash = (filename: string) => crypto.createHash('md5').update(filename).digest('hex'); + +export const getTestSeed = (filename: string) => `//Alice+${getTestHash(filename)}`; + +async function usingPlaygroundsGeneral( + helperType: new (logger: ILogger) => T, + url: string, + code: (helper: T, privateKey: (seed: string | { filename?: string, url?: string, ignoreFundsPresence?: boolean }) => Promise) => Promise, +): Promise { + const silentConsole = new SilentConsole(); + silentConsole.enable(); + + const helper = new helperType(new SilentLogger()); + let result; + try { + await helper.connect(url); + const ss58Format = helper.chain.getChainProperties().ss58Format; + const privateKey = async (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => { + if(typeof seed === 'string') { + return helper.util.fromSeed(seed, ss58Format); + } + if(seed.url) { + const {filename} = makeNames(seed.url); + seed.filename = filename; + } else if(seed.filename) { + // Pass + } else { + throw new Error('no url nor filename set'); + } + const actualSeed = getTestSeed(seed.filename); + let account = helper.util.fromSeed(actualSeed, ss58Format); + // here's to hoping that no + if(!seed.ignoreFundsPresence && ((helper as any)['balance'] == undefined || await (helper as any).balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND)) { + console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`); + account = helper.util.fromSeed('//Alice', ss58Format); + } + return account; + }; + result = await code(helper, privateKey); + } + finally { + await helper.disconnect(); + silentConsole.disable(); + } + return result as any as R; +} + +export const usingPlaygrounds = (code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => Promise) => Promise, url: string = config.substrateUrl) => usingPlaygroundsGeneral(DevUniqueHelper, url, code); + +export const usingWestmintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevWestmintHelper, url, code); + +export const usingStateminePlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevWestmintHelper, url, code); + +export const usingStatemintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevWestmintHelper, url, code); + +export const usingRelayPlaygrounds = (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevRelayHelper, url, code); + +export const usingAcalaPlaygrounds = (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevAcalaHelper, url, code); + +export const usingKaruraPlaygrounds = (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevAcalaHelper, url, code); + +export const usingMoonbeamPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevMoonbeamHelper, url, code); + +export const usingMoonriverPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevMoonriverHelper, url, code); + +export const usingAstarPlaygrounds = (url: string, code: (helper: DevAstarHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevAstarHelper, url, code); + +export const usingShidenPlaygrounds = (url: string, code: (helper: DevShidenHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevShidenHelper, url, code); + +export const usingPolkadexPlaygrounds = (url: string, code: (helper: DevPolkadexHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevPolkadexHelper, url, code); + +export const usingHydraDxPlaygrounds = (url: string, code: (helper: DevHydraDxHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevHydraDxHelper, url, code); + +export const MINIMUM_DONOR_FUND = 4_000_000n; +export const DONOR_FUNDING = 4_000_000n; + +// App-promotion periods: +export const LOCKING_PERIOD = 12n; // 12 blocks of relay +export const UNLOCKING_PERIOD = 6n; // 6 blocks of parachain + +// Native contracts +export const COLLECTION_HELPER = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f'; +export const CONTRACT_HELPER = '0x842899ECF380553E8a4de75bF534cdf6fBF64049'; + +export enum Pallets { + Inflation = 'inflation', + ReFungible = 'refungible', + Fungible = 'fungible', + NFT = 'nonfungible', + Scheduler = 'scheduler', + UniqueScheduler = 'uniqueScheduler', + AppPromotion = 'apppromotion', + CollatorSelection = 'collatorselection', + Session = 'session', + Identity = 'identity', + Democracy = 'democracy', + Council = 'council', + //CouncilMembership = 'councilmembership', + TechnicalCommittee = 'technicalcommittee', + Fellowship = 'fellowshipcollective', + Preimage = 'preimage', + Maintenance = 'maintenance', + TestUtils = 'testutils', +} + +export function requirePalletsOrSkip(test: Context, helper: DevUniqueHelper, requiredPallets: readonly string[]) { + const missingPallets = helper.fetchMissingPalletNames(requiredPallets); + + if(missingPallets.length > 0) { + const skipMsg = `\tSkipping test '${test.test?.title}'.\n\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; + console.warn('\x1b[38:5:208m%s\x1b[0m', skipMsg); + test.skip(); + } +} + +export function itSub(name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) { + (opts.only ? it.only : + opts.skip ? it.skip : it)(name, async function () { + await usingPlaygrounds(async (helper, privateKey) => { + if(opts.requiredPallets) { + requirePalletsOrSkip(this, helper, opts.requiredPallets); + } + + await cb({helper, privateKey}); + }); + }); +} +export function itSubIfWithPallet(name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) { + return itSub(name, cb, {requiredPallets: required, ...opts}); +} +itSub.only = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSub(name, cb, {only: true}); +itSub.skip = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSub(name, cb, {skip: true}); + +itSubIfWithPallet.only = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSubIfWithPallet(name, required, cb, {only: true}); +itSubIfWithPallet.skip = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSubIfWithPallet(name, required, cb, {skip: true}); +itSub.ifWithPallets = itSubIfWithPallet; + +export type SchedKind = 'anon' | 'named'; + +export function itSched( + name: string, + cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any, + opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}, +) { + itSub(name + ' (anonymous scheduling)', (apis) => cb('anon', apis), opts); + itSub(name + ' (named scheduling)', (apis) => cb('named', apis), opts); +} +itSched.only = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSched(name, cb, {only: true}); +itSched.skip = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSched(name, cb, {skip: true}); +itSched.ifWithPallets = itSchedIfWithPallets; + +function itSchedIfWithPallets(name: string, required: string[], cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) { + return itSched(name, cb, {requiredPallets: required, ...opts}); +} + +export function describeXCM(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) { + (process.env.RUN_XCM_TESTS && !opts.skip + ? describe + : describe.skip)(title, fn); +} + +describeXCM.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeXCM(name, fn, {skip: true}); + +export function describeGov(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) { + (process.env.RUN_GOV_TESTS && !opts.skip + ? describe + : describe.skip)(title, fn); +} + +describeGov.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeGov(name, fn, {skip: true}); + +export function sizeOfInt(i: number) { + if(i < 0 || i > 0xffffffff) throw new Error('out of range'); + if(i < 0b11_1111) { + return 1; + } else if(i < 0b11_1111_1111_1111) { + return 2; + } else if(i < 0b11_1111_1111_1111_1111_1111_1111_1111) { + return 4; + } else { + return 5; + } +} + +const UTF8_ENCODER = new TextEncoder(); +export function sizeOfEncodedStr(v: string) { + const encoded = UTF8_ENCODER.encode(v); + return sizeOfInt(encoded.length) + encoded.length; +} + +export function sizeOfProperty(prop: {key: string, value: string}) { + return sizeOfEncodedStr(prop.key) + sizeOfEncodedStr(prop.value); +} + +export function makeNames(url: string) { + const filename = fileURLToPath(url); + return { + filename, + dirname: dirname(filename), + }; +} --- /dev/null +++ b/js-packages/test-utils/xcm/index.ts @@ -0,0 +1,449 @@ +import {ApiPromise, WsProvider} from '@polkadot/api'; +import type {IKeyringPair} from '@polkadot/types/types'; +import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from '@unique-nft/playgrounds/unique.js'; +import type {ILogger, TSigner} from '@unique-nft/playgrounds/types.js'; +import type {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.js'; + + +export class XcmChainHelper extends ChainHelperBase { + override async connect(wsEndpoint: string, _listeners?: any): Promise { + const wsProvider = new WsProvider(wsEndpoint); + this.api = new ApiPromise({ + provider: wsProvider, + }); + await this.api.isReadyOrError; + this.network = await UniqueHelper.detectNetwork(this.api); + } +} + +class AcalaAssetRegistryGroup extends HelperGroup { + async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) { + await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true); + } +} + +class MoonbeamAssetManagerGroup extends HelperGroup { + makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) { + const apiPrefix = 'api.tx.assetManager.'; + + const registerTx = this.helper.constructApiCall( + apiPrefix + 'registerForeignAsset', + [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient], + ); + + const setUnitsTx = this.helper.constructApiCall( + apiPrefix + 'setAssetUnitsPerSecond', + [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint], + ); + + const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]); + const encodedProposal = batchCall?.method.toHex() || ''; + return encodedProposal; + } + + async assetTypeId(location: any) { + return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]); + } +} + +class MoonbeamDemocracyGroup extends HelperGroup { + notePreimagePallet: string; + + constructor(helper: MoonbeamHelper, options: { [key: string]: any } = {}) { + super(helper); + this.notePreimagePallet = options.notePreimagePallet; + } + + async notePreimage(signer: TSigner, encodedProposal: string) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true); + } + + externalProposeMajority(proposal: any) { + return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]); + } + + fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) { + return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); + } + + async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) { + await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true); + } +} + +class DemocracyGroup extends HelperGroup { + notePreimagePallet: string; + + constructor(helper: T, options: { [key: string]: any } = {}) { + super(helper); + this.notePreimagePallet = options.notePreimagePallet; + } + + async notePreimage(signer: TSigner, encodedProposal: string) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true); + } + + externalProposeMajority(proposal: any) { + return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]); + } + + fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) { + return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); + } + + async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) { + await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true); + } +} + +class MoonbeamCollectiveGroup extends HelperGroup { + collective: string; + + constructor(helper: MoonbeamHelper, collective: string) { + super(helper); + + this.collective = collective; + } + + async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true); + } + + async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true); + } + + async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true); + } + + async proposalCount() { + return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])); + } +} + +class CollectiveGroup extends HelperGroup { + collective: string; + + constructor(helper: T, palletName: string) { + super(helper); + + this.collective = palletName; + } + + async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true); + } + + async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true); + } + + async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true); + } + + async proposalCount() { + return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])); + } +} +class PolkadexXcmHelperGroup extends HelperGroup { + async whitelistToken(signer: TSigner, assetId: any) { + await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true); + } +} + +export class ForeignAssetsGroup extends HelperGroup { + async register(signer: TSigner, assetId: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) { + await this.helper.executeExtrinsic( + signer, + 'api.tx.foreignAssets.forceRegisterForeignAsset', + [{V3: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode], + true, + ); + } + + async foreignCollectionId(assetId: any) { + return (await this.helper.callRpc('api.query.foreignAssets.foreignAssetToCollection', [assetId])).toJSON(); + } +} + +export class XcmGroup extends HelperGroup { + palletName: string; + + constructor(helper: T, palletName: string) { + super(helper); + + this.palletName = palletName; + } + + async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true); + } + + async setSafeXcmVersion(signer: TSigner, version: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true); + } + + async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true); + } + + async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) { + const destinationContent = { + parents: 0, + interior: { + X1: { + Parachain: destinationParaId, + }, + }, + }; + + const beneficiaryContent = { + parents: 0, + interior: { + X1: { + AccountId32: { + network: 'Any', + id: targetAccount, + }, + }, + }, + }; + + const assetsContent = [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: amount, + }, + }, + ]; + + let destination; + let beneficiary; + let assets; + + if(xcmVersion == 2) { + destination = {V1: destinationContent}; + beneficiary = {V1: beneficiaryContent}; + assets = {V1: assetsContent}; + + } else if(xcmVersion == 3) { + destination = {V2: destinationContent}; + beneficiary = {V2: beneficiaryContent}; + assets = {V2: assetsContent}; + + } else { + throw Error('Unknown XCM version: ' + xcmVersion); + } + + const feeAssetItem = 0; + + await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem); + } + + async send(signer: IKeyringPair, destination: any, message: any) { + await this.helper.executeExtrinsic( + signer, + `api.tx.${this.palletName}.send`, + [ + destination, + message, + ], + true, + ); + } +} + +export class XTokensGroup extends HelperGroup { + async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) { + await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true); + } + + async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) { + await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true); + } + + async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) { + await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true); + } +} + + + +export class TokensGroup extends HelperGroup { + async accounts(address: string, currencyId: any) { + const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any; + return BigInt(free); + } +} + +export class AssetsGroup extends HelperGroup { + async create(signer: TSigner, assetId: number | bigint, admin: string, minimalBalance: bigint) { + await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true); + } + + async forceCreate(signer: TSigner, assetId: number | bigint, admin: string, minimalBalance: bigint, isSufficient = true) { + await this.helper.executeExtrinsic(signer, 'api.tx.assets.forceCreate', [assetId, admin, isSufficient, minimalBalance], true); + } + + async setMetadata(signer: TSigner, assetId: number | bigint, name: string, symbol: string, decimals: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true); + } + + async mint(signer: TSigner, assetId: number | bigint, beneficiary: string, amount: bigint) { + await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true); + } + + async assetInfo(assetId: number | bigint) { + return (await this.helper.callRpc('api.query.assets.asset', [assetId])).toJSON(); + } + + async account(assetId: string | number | bigint, address: string) { + const accountAsset = ( + await this.helper.callRpc('api.query.assets.account', [assetId, address]) + ).toJSON()! as any; + + if(accountAsset !== null) { + return BigInt(accountAsset['balance']); + } else { + return null; + } + } +} + +export class RelayHelper extends XcmChainHelper { + balance: SubstrateBalanceGroup; + xcm: XcmGroup; + + constructor(logger?: ILogger, options: { [key: string]: any } = {}) { + super(logger, options.helperBase ?? RelayHelper); + + this.balance = new SubstrateBalanceGroup(this); + this.xcm = new XcmGroup(this, 'xcmPallet'); + } +} + +export class WestmintHelper extends XcmChainHelper { + balance: SubstrateBalanceGroup; + xcm: XcmGroup; + assets: AssetsGroup; + xTokens: XTokensGroup; + + constructor(logger?: ILogger, options: { [key: string]: any } = {}) { + super(logger, options.helperBase ?? WestmintHelper); + + this.balance = new SubstrateBalanceGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); + this.assets = new AssetsGroup(this); + this.xTokens = new XTokensGroup(this); + } +} + +export class MoonbeamHelper extends XcmChainHelper { + balance: EthereumBalanceGroup; + assetManager: MoonbeamAssetManagerGroup; + assets: AssetsGroup; + xTokens: XTokensGroup; + democracy: MoonbeamDemocracyGroup; + collective: { + council: MoonbeamCollectiveGroup, + techCommittee: MoonbeamCollectiveGroup, + }; + + constructor(logger?: ILogger, options: { [key: string]: any } = {}) { + super(logger, options.helperBase ?? MoonbeamHelper); + + this.balance = new EthereumBalanceGroup(this); + this.assetManager = new MoonbeamAssetManagerGroup(this); + this.assets = new AssetsGroup(this); + this.xTokens = new XTokensGroup(this); + this.democracy = new MoonbeamDemocracyGroup(this, options); + this.collective = { + council: new MoonbeamCollectiveGroup(this, 'councilCollective'), + techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'), + }; + } +} + +export class AstarHelper extends XcmChainHelper { + balance: SubstrateBalanceGroup; + assets: AssetsGroup; + xcm: XcmGroup; + + constructor(logger?: ILogger, options: { [key: string]: any } = {}) { + super(logger, options.helperBase ?? AstarHelper); + + this.balance = new SubstrateBalanceGroup(this); + this.assets = new AssetsGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); + } +} + +export class AcalaHelper extends XcmChainHelper { + balance: SubstrateBalanceGroup; + assetRegistry: AcalaAssetRegistryGroup; + xTokens: XTokensGroup; + tokens: TokensGroup; + xcm: XcmGroup; + + constructor(logger?: ILogger, options: { [key: string]: any } = {}) { + super(logger, options.helperBase ?? AcalaHelper); + + this.balance = new SubstrateBalanceGroup(this); + this.assetRegistry = new AcalaAssetRegistryGroup(this); + this.xTokens = new XTokensGroup(this); + this.tokens = new TokensGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); + } +} + +export class PolkadexHelper extends XcmChainHelper { + assets: AssetsGroup; + balance: SubstrateBalanceGroup; + xTokens: XTokensGroup; + xcm: XcmGroup; + xcmHelper: PolkadexXcmHelperGroup; + + constructor(logger?: ILogger, options: { [key: string]: any } = {}) { + super(logger, options.helperBase ?? PolkadexHelper); + + this.assets = new AssetsGroup(this); + this.balance = new SubstrateBalanceGroup(this); + this.xTokens = new XTokensGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); + this.xcmHelper = new PolkadexXcmHelperGroup(this); + } +} + +export class HydraDxHelper extends XcmChainHelper { + balance: SubstrateBalanceGroup; + xcm: XcmGroup; + xTokens: XTokensGroup; + democracy: DemocracyGroup; + collective: { + council: CollectiveGroup, + techCommittee: CollectiveGroup, + }; + + constructor(logger?: ILogger, options: { [key: string]: any } = {}) { + super(logger, options.helperBase ?? HydraDxHelper); + options.notePreimagePallet = options.notePreimagePallet ?? 'preimage'; + + this.balance = new SubstrateBalanceGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); + this.xTokens = new XTokensGroup(this); + this.democracy = new DemocracyGroup(this, options); + this.collective = { + council: new CollectiveGroup(this, 'council'), + techCommittee: new CollectiveGroup(this, 'technicalCommittee'), + }; + } +} + --- /dev/null +++ b/js-packages/test-utils/xcm/types.ts @@ -0,0 +1,29 @@ +export interface AcalaAssetMetadata { + name: string, + symbol: string, + decimals: number, + minimalBalance: bigint, +} + +export interface MoonbeamAssetInfo { + location: any, + metadata: { + name: string, + symbol: string, + decimals: number, + isFrozen: boolean, + minimalBalance: bigint, + }, + existentialDeposit: bigint, + isSufficient: boolean, + unitsPerSecond: bigint, + numAssetsWeightHint: number, +} + +export interface DemocracyStandardAccountVote { + balance: bigint, + vote: { + aye: boolean, + conviction: number, + }, +} --- a/js-packages/tests/addCollectionAdmin.test.ts +++ b/js-packages/tests/addCollectionAdmin.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => { let donor: IKeyringPair; --- a/js-packages/tests/adminTransferAndBurn.test.ts +++ b/js-packages/tests/adminTransferAndBurn.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub} from './util/index.js'; +import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js'; describe('Integration Test: ownerCanTransfer allows admins to use only transferFrom/burnFrom:', () => { let alice: IKeyringPair; --- a/js-packages/tests/allowLists.test.ts +++ b/js-packages/tests/allowLists.test.ts @@ -15,9 +15,9 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub} from './util/index.js'; -import type {ICollectionPermissions} from '@unique/playgrounds/types.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js'; +import type {ICollectionPermissions} from '@unique-nft/playgrounds/types.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; describe('Integration Test ext. Allow list tests', () => { let alice: IKeyringPair; --- a/js-packages/tests/apiConsts.test.ts +++ b/js-packages/tests/apiConsts.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import {ApiPromise} from '@polkadot/api'; -import {usingPlaygrounds, itSub, expect, COLLECTION_HELPER, CONTRACT_HELPER} from './util/index.js'; +import {usingPlaygrounds, itSub, expect, COLLECTION_HELPER, CONTRACT_HELPER} from '@unique/test-utils/util.js'; const MAX_COLLECTION_DESCRIPTION_LENGTH = 256n; --- a/js-packages/tests/approve.test.ts +++ b/js-packages/tests/approve.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, usingPlaygrounds} from './util/index.js'; -import {CrossAccountId} from '@unique/playgrounds/unique.js'; +import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js'; +import {CrossAccountId} from '@unique-nft/playgrounds/unique.js'; --- a/js-packages/tests/burnItem.test.ts +++ b/js-packages/tests/burnItem.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, usingPlaygrounds} from './util/index.js'; +import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js'; describe('integration test: ext. burnItem():', () => { --- a/js-packages/tests/change-collection-owner.test.ts +++ b/js-packages/tests/change-collection-owner.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; describe('Integration Test changeCollectionOwner(collection_id, new_owner):', () => { let alice: IKeyringPair; --- a/js-packages/tests/confirmSponsorship.test.ts +++ b/js-packages/tests/confirmSponsorship.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub, Pallets} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, expect, itSub, Pallets} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; async function setSponsorHelper(collection: any, signer: IKeyringPair, sponsorAddress: string) { await collection.setSponsor(signer, sponsorAddress); --- a/js-packages/tests/connection.test.ts +++ b/js-packages/tests/connection.test.ts @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {itSub, expect, usingPlaygrounds} from './util/index.js'; +import {itSub, expect, usingPlaygrounds} from '@unique/test-utils/util.js'; describe('Connection smoke test', () => { itSub('Connection can be established', async ({helper}) => { --- a/js-packages/tests/createCollection.test.ts +++ b/js-packages/tests/createCollection.test.ts @@ -15,10 +15,10 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub, Pallets} from './util/index.js'; -import {CollectionFlag} from '@unique/playgrounds/types.js'; -import type {ICollectionCreationOptions, IProperty} from '@unique/playgrounds/types.js'; -import {UniqueHelper} from '@unique/playgrounds/unique.js'; +import {usingPlaygrounds, expect, itSub, Pallets} from '@unique/test-utils/util.js'; +import {CollectionFlag} from '@unique-nft/playgrounds/types.js'; +import type {ICollectionCreationOptions, IProperty} from '@unique-nft/playgrounds/types.js'; +import {UniqueHelper} from '@unique-nft/playgrounds/unique.js'; async function mintCollectionHelper(helper: UniqueHelper, signer: IKeyringPair, options: ICollectionCreationOptions, type?: 'nft' | 'fungible' | 'refungible') { let collection; --- a/js-packages/tests/createItem.test.ts +++ b/js-packages/tests/createItem.test.ts @@ -15,9 +15,9 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub, Pallets} from './util/index.js'; -import type {IProperty, ICrossAccountId} from '@unique/playgrounds/types.js'; -import {UniqueHelper} from '@unique/playgrounds/unique.js'; +import {usingPlaygrounds, expect, itSub, Pallets} from '@unique/test-utils/util.js'; +import type {IProperty, ICrossAccountId} from '@unique-nft/playgrounds/types.js'; +import {UniqueHelper} from '@unique-nft/playgrounds/unique.js'; async function mintTokenHelper(helper: UniqueHelper, collection: any, signer: IKeyringPair, owner: ICrossAccountId, type: 'nft' | 'fungible' | 'refungible'='nft', properties?: IProperty[]) { let token; --- a/js-packages/tests/createMultipleItems.test.ts +++ b/js-packages/tests/createMultipleItems.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, Pallets, itSub} from './util/index.js'; +import {usingPlaygrounds, expect, Pallets, itSub} from '@unique/test-utils/util.js'; describe('Integration Test createMultipleItems(collection_id, owner, items_data):', () => { let alice: IKeyringPair; --- a/js-packages/tests/createMultipleItemsEx.test.ts +++ b/js-packages/tests/createMultipleItemsEx.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, Pallets, itSub} from './util/index.js'; -import type {IProperty} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, expect, Pallets, itSub} from '@unique/test-utils/util.js'; +import type {IProperty} from '@unique-nft/playgrounds/types.js'; describe('Integration Test: createMultipleItemsEx', () => { let alice: IKeyringPair; --- a/js-packages/tests/creditFeesToTreasury.seqtest.ts +++ b/js-packages/tests/creditFeesToTreasury.seqtest.ts @@ -16,7 +16,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {ApiPromise} from '@polkadot/api'; -import {usingPlaygrounds, expect, itSub} from './util/index.js'; +import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js'; import type {u32} from '@polkadot/types-codec'; const TREASURY = '5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z'; --- a/js-packages/tests/destroyCollection.test.ts +++ b/js-packages/tests/destroyCollection.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, expect, usingPlaygrounds, Pallets} from './util/index.js'; +import {itSub, expect, usingPlaygrounds, Pallets} from '@unique/test-utils/util.js'; describe('integration test: ext. destroyCollection():', () => { let alice: IKeyringPair; --- a/js-packages/tests/enableDisableTransfer.test.ts +++ b/js-packages/tests/enableDisableTransfer.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from './util/index.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; describe('Enable/Disable Transfers', () => { let alice: IKeyringPair; --- a/js-packages/tests/eth/allowlist.test.ts +++ b/js-packages/tests/eth/allowlist.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {Pallets} from '../util/index.js'; +import {Pallets} from '@unique/test-utils/util.js'; import {itEth, usingEthPlaygrounds, expect, SponsoringMode} from './util/index.js'; import {CreateCollectionData} from './util/playgrounds/types.js'; --- a/js-packages/tests/eth/collectionAdmin.test.ts +++ b/js-packages/tests/eth/collectionAdmin.test.ts @@ -15,8 +15,8 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {expect} from 'chai'; -import {Pallets} from '../util/index.js'; -import type {IEthCrossAccountId} from '@unique/playgrounds/types.js'; +import {Pallets} from '@unique/test-utils/util.js'; +import type {IEthCrossAccountId} from '@unique-nft/playgrounds/types.js'; import {usingEthPlaygrounds, itEth} from './util/index.js'; import {EthUniqueHelper} from './util/playgrounds/unique.dev.js'; import {CreateCollectionData} from './util/playgrounds/types.js'; --- a/js-packages/tests/eth/collectionHelperAddress.test.ts +++ b/js-packages/tests/eth/collectionHelperAddress.test.ts @@ -16,7 +16,7 @@ import {itEth, usingEthPlaygrounds, expect} from './util/index.js'; import type {IKeyringPair} from '@polkadot/types/types'; -import {COLLECTION_HELPER, Pallets} from '../util/index.js'; +import {COLLECTION_HELPER, Pallets} from '@unique/test-utils/util.js'; describe('[eth]CollectionHelperAddress test: ERC20/ERC721 ', () => { let donor: IKeyringPair; --- a/js-packages/tests/eth/collectionLimits.test.ts +++ b/js-packages/tests/eth/collectionLimits.test.ts @@ -1,5 +1,5 @@ import type {IKeyringPair} from '@polkadot/types/types'; -import {Pallets} from '../util/index.js'; +import {Pallets} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; import {CollectionLimitField, CreateCollectionData} from './util/playgrounds/types.js'; --- a/js-packages/tests/eth/collectionProperties.test.ts +++ b/js-packages/tests/eth/collectionProperties.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import {itEth, usingEthPlaygrounds, expect} from './util/index.js'; -import {Pallets} from '../util/index.js'; -import type {IProperty, ITokenPropertyPermission} from '@unique/playgrounds/types.js'; +import {Pallets} from '@unique/test-utils/util.js'; +import type {IProperty, ITokenPropertyPermission} from '@unique-nft/playgrounds/types.js'; import type {IKeyringPair} from '@polkadot/types/types'; describe('EVM collection properties', () => { --- a/js-packages/tests/eth/collectionSponsoring.test.ts +++ b/js-packages/tests/eth/collectionSponsoring.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../util/index.js'; +import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js'; import {itEth, expect} from './util/index.js'; import {CollectionLimitField, TokenPermissionField} from './util/playgrounds/types.js'; --- a/js-packages/tests/eth/contractSponsoring.test.ts +++ b/js-packages/tests/eth/contractSponsoring.test.ts @@ -17,7 +17,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {EthUniqueHelper} from './util/playgrounds/unique.dev.js'; import {itEth, expect, SponsoringMode, usingEthPlaygrounds} from './util/index.js'; -import {usingPlaygrounds} from '../util/index.js'; +import {usingPlaygrounds} from '@unique/test-utils/util.js'; import type {CompiledContract} from './util/playgrounds/types.js'; describe('Sponsoring EVM contracts', () => { --- a/js-packages/tests/eth/createCollection.test.ts +++ b/js-packages/tests/eth/createCollection.test.ts @@ -16,11 +16,11 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {evmToAddress} from '@polkadot/util-crypto'; -import {Pallets, requirePalletsOrSkip} from '../util/index.js'; +import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; import {CREATE_COLLECTION_DATA_DEFAULTS, CollectionLimitField, CollectionMode, CreateCollectionData, TokenPermissionField, emptyAddress} from './util/playgrounds/types.js'; -import {CollectionFlag} from '@unique/playgrounds/types.js'; -import type {IEthCrossAccountId, TCollectionMode} from '@unique/playgrounds/types.js'; +import {CollectionFlag} from '@unique-nft/playgrounds/types.js'; +import type {IEthCrossAccountId, TCollectionMode} from '@unique-nft/playgrounds/types.js'; const DECIMALS = 18; const CREATE_COLLECTION_DATA_DEFAULTS_ARRAY = [ --- a/js-packages/tests/eth/createFTCollection.seqtest.ts +++ b/js-packages/tests/eth/createFTCollection.seqtest.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {Pallets, requirePalletsOrSkip} from '../util/index.js'; +import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; const DECIMALS = 18; --- a/js-packages/tests/eth/createFTCollection.test.ts +++ b/js-packages/tests/eth/createFTCollection.test.ts @@ -16,7 +16,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {evmToAddress} from '@polkadot/util-crypto'; -import {Pallets, requirePalletsOrSkip} from '../util/index.js'; +import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; import {CollectionLimitField} from './util/playgrounds/types.js'; --- a/js-packages/tests/eth/createRFTCollection.test.ts +++ b/js-packages/tests/eth/createRFTCollection.test.ts @@ -16,7 +16,7 @@ import {evmToAddress} from '@polkadot/util-crypto'; import type {IKeyringPair} from '@polkadot/types/types'; -import {Pallets, requirePalletsOrSkip} from '../util/index.js'; +import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; import {CollectionLimitField} from './util/playgrounds/types.js'; --- a/js-packages/tests/eth/crossTransfer.test.ts +++ b/js-packages/tests/eth/crossTransfer.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import {itEth, usingEthPlaygrounds} from './util/index.js'; -import {CrossAccountId} from '@unique/playgrounds/unique.js'; +import {CrossAccountId} from '@unique-nft/playgrounds/unique.js'; import type {IKeyringPair} from '@polkadot/types/types'; describe('Token transfer between substrate address and EVM address. Fungible', () => { --- a/js-packages/tests/eth/destroyCollection.test.ts +++ b/js-packages/tests/eth/destroyCollection.test.ts @@ -15,9 +15,9 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {Pallets} from '../util/index.js'; +import {Pallets} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; -import type {TCollectionMode} from '@unique/playgrounds/types.js'; +import type {TCollectionMode} from '@unique-nft/playgrounds/types.js'; import {CreateCollectionData} from './util/playgrounds/types.js'; describe('Destroy Collection from EVM', function() { --- a/js-packages/tests/eth/events.test.ts +++ b/js-packages/tests/eth/events.test.ts @@ -18,8 +18,8 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {itEth, usingEthPlaygrounds} from './util/index.js'; import {EthUniqueHelper} from './util/playgrounds/unique.dev.js'; -import type {IEvent, TCollectionMode} from '@unique/playgrounds/types.js'; -import {Pallets, requirePalletsOrSkip} from '../util/index.js'; +import type {IEvent, TCollectionMode} from '@unique-nft/playgrounds/types.js'; +import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; import {CollectionLimitField, TokenPermissionField, CreateCollectionData} from './util/playgrounds/types.js'; import type {NormalizedEvent} from './util/playgrounds/types.js'; --- a/js-packages/tests/eth/fractionalizer/fractionalizer.test.ts +++ b/js-packages/tests/eth/fractionalizer/fractionalizer.test.ts @@ -25,7 +25,7 @@ import {usingEthPlaygrounds, expect, itEth} from '../util/index.js'; import {EthUniqueHelper} from '../util/playgrounds/unique.dev.js'; import type {CompiledContract} from '../util/playgrounds/types.js'; -import {requirePalletsOrSkip, Pallets, makeNames} from '../../util/index.js'; +import {requirePalletsOrSkip, Pallets, makeNames} from '@unique/test-utils/util.js'; const {dirname} = makeNames(import.meta.url); --- a/js-packages/tests/eth/getCode.test.ts +++ b/js-packages/tests/eth/getCode.test.ts @@ -16,7 +16,7 @@ import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; import type {IKeyringPair} from '@polkadot/types/types'; -import {COLLECTION_HELPER, CONTRACT_HELPER} from '../util/index.js'; +import {COLLECTION_HELPER, CONTRACT_HELPER} from '@unique/test-utils/util.js'; describe('RPC eth_getCode', () => { let donor: IKeyringPair; --- a/js-packages/tests/eth/marketplace-v2/marketplace.test.ts +++ b/js-packages/tests/eth/marketplace-v2/marketplace.test.ts @@ -19,8 +19,7 @@ import {readFile} from 'fs/promises'; import {SponsoringMode, itEth, usingEthPlaygrounds} from '../util/index.js'; import {EthUniqueHelper} from '../util/playgrounds/unique.dev.js'; -import {makeNames} from '../../util/index.js'; -import {expect} from 'chai'; +import {makeNames, expect} from '@unique/test-utils/util.js'; const {dirname} = makeNames(import.meta.url); --- a/js-packages/tests/eth/marketplace/marketplace.test.ts +++ b/js-packages/tests/eth/marketplace/marketplace.test.ts @@ -17,7 +17,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {readFile} from 'fs/promises'; import {itEth, usingEthPlaygrounds, expect, SponsoringMode} from '../util/index.js'; -import {makeNames} from '../../util/index.js'; +import {makeNames} from '@unique/test-utils/util.js'; const {dirname} = makeNames(import.meta.url); --- a/js-packages/tests/eth/migration.seqtest.ts +++ b/js-packages/tests/eth/migration.seqtest.ts @@ -18,7 +18,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {Struct} from '@polkadot/types'; -import type {IEvent} from '@unique/playgrounds/types.js'; +import type {IEvent} from '@unique-nft/playgrounds/types.js'; import type {InterfaceTypes} from '@polkadot/types/types/registry'; import {ApiPromise} from '@polkadot/api'; --- a/js-packages/tests/eth/nativeFungible.test.ts +++ b/js-packages/tests/eth/nativeFungible.test.ts @@ -16,7 +16,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; -import {UniqueHelper} from '@unique/playgrounds/unique.js'; +import {UniqueHelper} from '@unique-nft/playgrounds/unique.js'; describe('NativeFungible: ERC20 calls', () => { let donor: IKeyringPair; --- a/js-packages/tests/eth/nonFungible.test.ts +++ b/js-packages/tests/eth/nonFungible.test.ts @@ -18,7 +18,7 @@ import {EthUniqueHelper} from './util/playgrounds/unique.dev.js'; import type {IKeyringPair} from '@polkadot/types/types'; import {Contract} from 'web3-eth-contract'; -import type {ITokenPropertyPermission} from '@unique/playgrounds/types.js'; +import type {ITokenPropertyPermission} from '@unique-nft/playgrounds/types.js'; import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from './util/playgrounds/types.js'; describe('Check ERC721 token URI for NFT', () => { --- a/js-packages/tests/eth/payable.test.ts +++ b/js-packages/tests/eth/payable.test.ts @@ -18,7 +18,7 @@ import {itEth, expect, usingEthPlaygrounds} from './util/index.js'; import {EthUniqueHelper} from './util/playgrounds/unique.dev.js'; -import {makeNames} from '../util/index.js'; +import {makeNames} from '@unique/test-utils/util.js'; const {dirname} = makeNames(import.meta.url); --- a/js-packages/tests/eth/proxy/fungibleProxy.test.ts +++ b/js-packages/tests/eth/proxy/fungibleProxy.test.ts @@ -19,7 +19,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {itEth, usingEthPlaygrounds} from '../util/index.js'; import {EthUniqueHelper} from '../util/playgrounds/unique.dev.js'; -import {makeNames} from '../../util/index.js'; +import {makeNames} from '@unique/test-utils/util.js'; const {dirname} = makeNames(import.meta.url); --- a/js-packages/tests/eth/proxy/nonFungibleProxy.test.ts +++ b/js-packages/tests/eth/proxy/nonFungibleProxy.test.ts @@ -18,7 +18,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {itEth, usingEthPlaygrounds, expect} from '../util/index.js'; import {EthUniqueHelper} from '../util/playgrounds/unique.dev.js'; -import {makeNames} from '../../util/index.js'; +import {makeNames} from '@unique/test-utils/util.js'; const {dirname} = makeNames(import.meta.url); --- a/js-packages/tests/eth/reFungible.test.ts +++ b/js-packages/tests/eth/reFungible.test.ts @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {Pallets, requirePalletsOrSkip} from '../util/index.js'; +import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; import type {IKeyringPair} from '@polkadot/types/types'; -import type {ITokenPropertyPermission} from '@unique/playgrounds/types.js'; +import type {ITokenPropertyPermission} from '@unique-nft/playgrounds/types.js'; import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from './util/playgrounds/types.js'; describe('Refungible: Plain calls', () => { --- a/js-packages/tests/eth/reFungibleToken.test.ts +++ b/js-packages/tests/eth/reFungibleToken.test.ts @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {Pallets, requirePalletsOrSkip} from '../util/index.js'; +import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from './util/index.js'; import {EthUniqueHelper} from './util/playgrounds/unique.dev.js'; import type {IKeyringPair} from '@polkadot/types/types'; --- a/js-packages/tests/eth/scheduling.test.ts +++ b/js-packages/tests/eth/scheduling.test.ts @@ -17,7 +17,7 @@ import {expect} from 'chai'; import {itSchedEth} from './util/index.js'; import {EthUniqueHelper} from './util/playgrounds/unique.dev.js'; -import {Pallets, usingPlaygrounds} from '../util/index.js'; +import {Pallets, usingPlaygrounds} from '@unique/test-utils/util.js'; describe('Scheduing EVM smart contracts', () => { --- a/js-packages/tests/eth/sponsoring.test.ts +++ b/js-packages/tests/eth/sponsoring.test.ts @@ -16,7 +16,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {itEth, expect, SponsoringMode} from './util/index.js'; -import {usingPlaygrounds} from '../util/index.js'; +import {usingPlaygrounds} from '@unique/test-utils/util.js'; describe('EVM sponsoring', () => { let donor: IKeyringPair; --- a/js-packages/tests/eth/tokenProperties.test.ts +++ b/js-packages/tests/eth/tokenProperties.test.ts @@ -17,9 +17,9 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {Contract} from 'web3-eth-contract'; import {itEth, usingEthPlaygrounds, expect} from './util/index.js'; -import type {ITokenPropertyPermission} from '@unique/playgrounds/types.js'; -import {Pallets} from '../util/index.js'; -import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '@unique/playgrounds/unique.js'; +import type {ITokenPropertyPermission} from '@unique-nft/playgrounds/types.js'; +import {Pallets} from '@unique/test-utils/util.js'; +import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '@unique-nft/playgrounds/unique.js'; import {CreateCollectionData, TokenPermissionField} from './util/playgrounds/types.js'; describe('EVM token properties', () => { --- a/js-packages/tests/eth/tokens/callMethodsERC20.test.ts +++ b/js-packages/tests/eth/tokens/callMethodsERC20.test.ts @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {Pallets, requirePalletsOrSkip} from '../../util/index.js'; +import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from '../util/index.js'; import type {IKeyringPair} from '@polkadot/types/types'; import {CreateCollectionData} from '../util/playgrounds/types.js'; --- a/js-packages/tests/eth/tokens/callMethodsERC721.test.ts +++ b/js-packages/tests/eth/tokens/callMethodsERC721.test.ts @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {Pallets} from '../../util/index.js'; +import {Pallets} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from '../util/index.js'; import type {IKeyringPair} from '@polkadot/types/types'; import {CreateCollectionData} from '../util/playgrounds/types.js'; --- a/js-packages/tests/eth/tokens/minting.test.ts +++ b/js-packages/tests/eth/tokens/minting.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {Pallets} from '../../util/index.js'; +import {Pallets} from '@unique/test-utils/util.js'; import {expect, itEth, usingEthPlaygrounds} from '../util/index.js'; import {CreateCollectionData} from '../util/playgrounds/types.js'; --- a/js-packages/tests/eth/util/index.ts +++ b/js-packages/tests/eth/util/index.ts @@ -7,14 +7,13 @@ import config from '../../config.js'; import {EthUniqueHelper} from './playgrounds/unique.dev.js'; -import {SilentLogger, SilentConsole} from '@unique/playgrounds/unique.dev.js'; -import {makeNames} from '../../util/index.js'; -import type {SchedKind} from '../../util/index.js'; +import {SilentLogger, SilentConsole} from '@unique/test-utils'; +import type {SchedKind} from '@unique/test-utils/util.js'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import chaiLike from 'chai-like'; -import {getTestSeed, MINIMUM_DONOR_FUND, requirePalletsOrSkip} from '../../util/index.js'; +import {getTestSeed, MINIMUM_DONOR_FUND, requirePalletsOrSkip, makeNames} from '@unique/test-utils/util.js'; chai.use(chaiAsPromised); chai.use(chaiLike); --- a/js-packages/tests/eth/util/playgrounds/types.ts +++ b/js-packages/tests/eth/util/playgrounds/types.ts @@ -1,5 +1,5 @@ -import {CollectionFlag} from '@unique/playgrounds/types.js'; -import type {TCollectionMode} from '@unique/playgrounds/types.js'; +import {CollectionFlag} from '@unique-nft/playgrounds/types.js'; +import type {TCollectionMode} from '@unique-nft/playgrounds/types.js'; export interface ContractImports { solPath: string; --- a/js-packages/tests/eth/util/playgrounds/unique.dev.ts +++ b/js-packages/tests/eth/util/playgrounds/unique.dev.ts @@ -15,7 +15,7 @@ import {evmToAddress} from '@polkadot/util-crypto'; import type {IKeyringPair} from '@polkadot/types/types'; -import {ArrangeGroup, DevUniqueHelper} from '@unique/playgrounds/unique.dev.js'; +import {ArrangeGroup, DevUniqueHelper} from '@unique/test-utils/index.js'; import type {ContractImports, CompiledContract, CrossAddress, NormalizedEvent, EthProperty} from './types.js'; import {CollectionMode, CreateCollectionData} from './types.js'; @@ -32,7 +32,7 @@ import refungibleTokenAbi from '../../abi/reFungibleToken.json' assert {type: 'json'}; import refungibleTokenDeprecatedAbi from '../../abi/reFungibleTokenDeprecated.json' assert {type: 'json'}; import contractHelpersAbi from '../../abi/contractHelpers.json' assert {type: 'json'}; -import type {ICrossAccountId, TEthereumAccount, TCollectionMode} from '@unique/playgrounds/types.js'; +import type {ICrossAccountId, TEthereumAccount, TCollectionMode} from '@unique-nft/playgrounds/types.js'; class EthGroupBase { helper: EthUniqueHelper; --- a/js-packages/tests/fungible.test.ts +++ b/js-packages/tests/fungible.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect, requirePalletsOrSkip, Pallets} from './util/index.js'; +import {itSub, usingPlaygrounds, expect, requirePalletsOrSkip, Pallets} from '@unique/test-utils/util.js'; const U128_MAX = (1n << 128n) - 1n; --- a/js-packages/tests/getPropertiesRpc.test.ts +++ b/js-packages/tests/getPropertiesRpc.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from './util/index.js'; -import {UniqueHelper, UniqueNFTCollection} from '@unique/playgrounds/unique.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {UniqueHelper, UniqueNFTCollection} from '@unique-nft/playgrounds/unique.js'; const collectionProps = [ {key: 'col-0', value: 'col-0-value'}, --- a/js-packages/tests/inflation.seqtest.ts +++ b/js-packages/tests/inflation.seqtest.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, usingPlaygrounds} from './util/index.js'; +import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js'; const TREASURY = '5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z'; --- a/js-packages/tests/limits.test.ts +++ b/js-packages/tests/limits.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util/index.js'; +import {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js'; describe('Number of tokens per address (NFT)', () => { let alice: IKeyringPair; --- a/js-packages/tests/maintenance.seqtest.ts +++ b/js-packages/tests/maintenance.seqtest.ts @@ -16,7 +16,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {ApiPromise} from '@polkadot/api'; -import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util/index.js'; +import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js'; import {itEth} from './eth/util/index.js'; import {main as correctState} from './migrations/correctStateAfterMaintenance.js'; import type {PalletBalancesIdAmount} from '@polkadot/types/lookup'; --- a/js-packages/tests/migrations/942057-appPromotion/index.ts +++ b/js-packages/tests/migrations/942057-appPromotion/index.ts @@ -1,4 +1,4 @@ -import type {Migration} from '../../util/frankensteinMigrate.js'; +import type {Migration} from '../index.js'; import {collectData} from './collectData.js'; import {migrateLockedToFreeze} from './lockedToFreeze.js'; --- a/js-packages/tests/migrations/942057-appPromotion/lockedToFreeze.ts +++ b/js-packages/tests/migrations/942057-appPromotion/lockedToFreeze.ts @@ -1,6 +1,6 @@ // import { usingApi, privateKey, onlySign } from "./../../load/lib"; import * as fs from 'fs'; -import {usingPlaygrounds} from '../../util/index.js'; +import {usingPlaygrounds} from '@unique/test-utils/util.js'; import path, {dirname} from 'path'; import {isInteger, parse} from 'lossless-json'; import {fileURLToPath} from 'url'; --- a/js-packages/tests/migrations/correctStateAfterMaintenance.ts +++ b/js-packages/tests/migrations/correctStateAfterMaintenance.ts @@ -1,5 +1,5 @@ import config from '../config.js'; -import {usingPlaygrounds} from '../util/index.js'; +import {usingPlaygrounds} from '@unique/test-utils/util.js'; import type {u32} from '@polkadot/types-codec'; const WS_ENDPOINT = config.substrateUrl; --- /dev/null +++ b/js-packages/tests/migrations/index.ts @@ -0,0 +1,9 @@ +import {migration as locksToFreezesMigration} from './942057-appPromotion/index.js'; +export interface Migration { + before: () => Promise, + after: () => Promise, +} + +export const migrations: {[key: string]: Migration} = { + 'v942057': locksToFreezesMigration, +}; \ No newline at end of file --- a/js-packages/tests/nativeFungible.test.ts +++ b/js-packages/tests/nativeFungible.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, usingPlaygrounds} from './util/index.js'; +import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js'; describe('Native fungible', () => { let root: IKeyringPair; --- a/js-packages/tests/nextSponsoring.test.ts +++ b/js-packages/tests/nextSponsoring.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, usingPlaygrounds} from './util/index.js'; +import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js'; const SPONSORING_TIMEOUT = 5; --- a/js-packages/tests/package.json +++ b/js-packages/tests/package.json @@ -13,9 +13,9 @@ "mocha": "^10.1.0" }, "scripts": { - "setup": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ./util/globalSetup.ts", - "setIdentities": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ./util/identitySetter.ts", - "checkRelayIdentities": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ./util/relayIdentitiesChecker.ts", + "setup": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ../test-utils/globalSetup.ts", + "setIdentities": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ../scripts/identitySetter.ts", + "checkRelayIdentities": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ../scripts/relayIdentitiesChecker.ts", "_test": "yarn setup && yarn mocha --timeout 9999999 --loader=ts-node/esm.mjs", "_testParallel": "yarn setup && yarn mocha --timeout 9999999 --parallel --loader=ts-node/esm.mjs", "test": "yarn _test './**/*.*test.ts'", --- a/js-packages/tests/pallet-presence.test.ts +++ b/js-packages/tests/pallet-presence.test.ts @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {itSub, usingPlaygrounds, expect} from './util/index.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; // Pallets that must always be present const requiredPallets = [ --- a/js-packages/tests/performance.seq.test.ts +++ b/js-packages/tests/performance.seq.test.ts @@ -16,9 +16,9 @@ import {ApiPromise} from '@polkadot/api'; import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, usingPlaygrounds} from './util/index.js'; -import type {ICrossAccountId, IProperty} from '@unique/playgrounds/types.js'; -import {UniqueHelper} from '@unique/playgrounds/unique.js'; +import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js'; +import type {ICrossAccountId, IProperty} from '@unique-nft/playgrounds/types.js'; +import {UniqueHelper} from '@unique-nft/playgrounds/unique.js'; describe('Performace tests', () => { let alice: IKeyringPair; --- a/js-packages/tests/refungible.test.ts +++ b/js-packages/tests/refungible.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from './util/index.js'; +import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; const MAX_REFUNGIBLE_PIECES = 1_000_000_000_000_000_000_000n; --- a/js-packages/tests/removeCollectionAdmin.test.ts +++ b/js-packages/tests/removeCollectionAdmin.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; describe('Integration Test removeCollectionAdmin(collection_id, account_id):', () => { let alice: IKeyringPair; --- a/js-packages/tests/removeCollectionSponsor.test.ts +++ b/js-packages/tests/removeCollectionSponsor.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; describe('integration test: ext. removeCollectionSponsor():', () => { let donor: IKeyringPair; --- a/js-packages/tests/rpc.test.ts +++ b/js-packages/tests/rpc.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, itSub, expect} from './util/index.js'; -import {ICrossAccountId} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, itSub, expect} from '@unique/test-utils/util.js'; +import {ICrossAccountId} from '@unique-nft/playgrounds/types.js'; describe('integration test: RPC methods', () => { let donor: IKeyringPair; --- a/js-packages/tests/scheduler.seqtest.ts +++ b/js-packages/tests/scheduler.seqtest.ts @@ -14,9 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util/index.js'; +import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js'; import type {IKeyringPair} from '@polkadot/types/types'; -import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js'; +import {DevUniqueHelper, Event} from '@unique/test-utils'; describe('Scheduling token and balance transfers', () => { let superuser: IKeyringPair; --- a/js-packages/tests/setCollectionLimits.test.ts +++ b/js-packages/tests/setCollectionLimits.test.ts @@ -16,8 +16,8 @@ // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; const accountTokenOwnershipLimit = 0; const sponsoredDataSize = 0; --- a/js-packages/tests/setCollectionSponsor.test.ts +++ b/js-packages/tests/setCollectionSponsor.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect, Pallets} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {itSub, usingPlaygrounds, expect, Pallets} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; describe('integration test: ext. setCollectionSponsor():', () => { let alice: IKeyringPair; --- a/js-packages/tests/setPermissions.test.ts +++ b/js-packages/tests/setPermissions.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; describe('Integration Test: Set Permissions', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/appPromotion/appPromotion.seqtest.ts +++ b/js-packages/tests/sub/appPromotion/appPromotion.seqtest.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, Pallets, requirePalletsOrSkip} from '../../util/index.js'; +import {itSub, usingPlaygrounds, Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; import {expect} from '../../eth/util/index.js'; let superuser: IKeyringPair; --- a/js-packages/tests/sub/appPromotion/appPromotion.test.ts +++ b/js-packages/tests/sub/appPromotion/appPromotion.test.ts @@ -17,8 +17,8 @@ import type {IKeyringPair} from '@polkadot/types/types'; import { itSub, usingPlaygrounds, Pallets, requirePalletsOrSkip, LOCKING_PERIOD, UNLOCKING_PERIOD, -} from '../../util/index.js'; -import {DevUniqueHelper} from '@unique/playgrounds/unique.dev.js'; +} from '@unique/test-utils/util.js'; +import {DevUniqueHelper} from '@unique/test-utils'; import {itEth, expect, SponsoringMode} from '../../eth/util/index.js'; let donor: IKeyringPair; --- a/js-packages/tests/sub/check-event/burnItemEvent.test.ts +++ b/js-packages/tests/sub/check-event/burnItemEvent.test.ts @@ -16,8 +16,8 @@ // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub} from '../../util/index.js'; -import type {IEvent} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js'; +import type {IEvent} from '@unique-nft/playgrounds/types.js'; describe('Burn Item event ', () => { --- a/js-packages/tests/sub/check-event/createCollectionEvent.test.ts +++ b/js-packages/tests/sub/check-event/createCollectionEvent.test.ts @@ -16,8 +16,8 @@ // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, itSub, expect} from '../../util/index.js'; -import type {IEvent} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, itSub, expect} from '@unique/test-utils/util.js'; +import type {IEvent} from '@unique-nft/playgrounds/types.js'; describe('Create collection event ', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/check-event/createItemEvent.test.ts +++ b/js-packages/tests/sub/check-event/createItemEvent.test.ts @@ -16,8 +16,8 @@ // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from '../../util/index.js'; -import type {IEvent} from '@unique/playgrounds/types.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import type {IEvent} from '@unique-nft/playgrounds/types.js'; describe('Create Item event ', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/check-event/createMultipleItemsEvent.test.ts +++ b/js-packages/tests/sub/check-event/createMultipleItemsEvent.test.ts @@ -16,8 +16,8 @@ // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, itSub, expect} from '../../util/index.js'; -import type {IEvent} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, itSub, expect} from '@unique/test-utils/util.js'; +import type {IEvent} from '@unique-nft/playgrounds/types.js'; describe('Create Multiple Items Event event ', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/check-event/destroyCollectionEvent.test.ts +++ b/js-packages/tests/sub/check-event/destroyCollectionEvent.test.ts @@ -16,8 +16,8 @@ // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from '../../util/index.js'; -import type {IEvent} from '@unique/playgrounds/types.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import type {IEvent} from '@unique-nft/playgrounds/types.js'; describe('Destroy collection event ', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/check-event/transferEvent.test.ts +++ b/js-packages/tests/sub/check-event/transferEvent.test.ts @@ -16,8 +16,8 @@ // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub} from '../../util/index.js'; -import type {IEvent} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js'; +import type {IEvent} from '@unique-nft/playgrounds/types.js'; describe('Transfer event ', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/check-event/transferFromEvent.test.ts +++ b/js-packages/tests/sub/check-event/transferFromEvent.test.ts @@ -16,8 +16,8 @@ // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub} from '../../util/index.js'; -import type {IEvent} from '@unique/playgrounds/types.js'; +import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js'; +import type {IEvent} from '@unique-nft/playgrounds/types.js'; describe('Transfer event ', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/collator-selection/collatorSelection.seqtest.ts +++ b/js-packages/tests/sub/collator-selection/collatorSelection.seqtest.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '../../util/index.js'; +import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; async function nodeAddress(name: string) { // eslint-disable-next-line require-await --- a/js-packages/tests/sub/collator-selection/identity.seqtest.ts +++ b/js-packages/tests/sub/collator-selection/identity.seqtest.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '../../util/index.js'; -import {UniqueHelper} from '@unique/playgrounds/unique.js'; +import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js'; +import {UniqueHelper} from '@unique-nft/playgrounds/unique.js'; async function getIdentities(helper: UniqueHelper) { const identities: [string, any][] = []; --- a/js-packages/tests/sub/governance/council.test.ts +++ b/js-packages/tests/sub/governance/council.test.ts @@ -1,7 +1,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js'; -import {Event} from '@unique/playgrounds/unique.dev.js'; +import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js'; +import {Event} from '@unique/test-utils'; import {initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyEnactmentPeriod, councilMotionDuration, democracyFastTrackVotingPeriod, fellowshipRankLimit, clearCouncil, clearTechComm, initTechComm, clearFellowship, dummyProposal, dummyProposalCall, initFellowship, defaultEnactmentMoment, fellowshipPropositionOrigin} from './util.js'; import type {ICounselors} from './util.js'; --- a/js-packages/tests/sub/governance/democracy.test.ts +++ b/js-packages/tests/sub/governance/democracy.test.ts @@ -1,7 +1,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js'; +import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js'; import {clearFellowship, democracyLaunchPeriod, democracyTrackMinRank, dummyProposalCall, fellowshipConfirmPeriod, fellowshipMinEnactPeriod, fellowshipPreparePeriod, fellowshipPropositionOrigin, initFellowship, voteUnanimouslyInFellowship} from './util.js'; -import {Event} from '@unique/playgrounds/unique.dev.js'; +import {Event} from '@unique/test-utils'; describeGov('Governance: Democracy tests', () => { let regularUser: IKeyringPair; --- a/js-packages/tests/sub/governance/electsudo.test.ts +++ b/js-packages/tests/sub/governance/electsudo.test.ts @@ -1,6 +1,6 @@ import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js'; -import {Event} from '@unique/playgrounds/unique.dev.js'; +import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js'; +import {Event} from '@unique/test-utils'; import {initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyEnactmentPeriod, clearCouncil, clearTechComm, initTechComm, ITechComms} from './util.js'; import type {ICounselors} from './util.js'; --- a/js-packages/tests/sub/governance/fellowship.test.ts +++ b/js-packages/tests/sub/governance/fellowship.test.ts @@ -1,6 +1,6 @@ import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js'; -import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js'; +import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js'; +import {DevUniqueHelper, Event} from '@unique/test-utils'; import {initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyFastTrackVotingPeriod, fellowshipRankLimit, clearCouncil, clearTechComm, clearFellowship, defaultEnactmentMoment, dummyProposal, dummyProposalCall, fellowshipPropositionOrigin, initFellowship, initTechComm, voteUnanimouslyInFellowship, democracyTrackMinRank, fellowshipPreparePeriod, fellowshipConfirmPeriod, fellowshipMinEnactPeriod, democracyTrackId, hardResetFellowshipReferenda, hardResetDemocracy, hardResetGovScheduler} from './util.js'; import type {ICounselors, ITechComms} from './util.js'; --- a/js-packages/tests/sub/governance/init.test.ts +++ b/js-packages/tests/sub/governance/init.test.ts @@ -1,6 +1,6 @@ import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js'; -import {Event} from '@unique/playgrounds/unique.dev.js'; +import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js'; +import {Event} from '@unique/test-utils'; import {democracyLaunchPeriod, democracyVotingPeriod, democracyEnactmentPeriod, clearCouncil, clearTechComm, clearFellowship} from './util.js'; import type {ICounselors, ITechComms} from './util.js'; --- a/js-packages/tests/sub/governance/technicalCommittee.test.ts +++ b/js-packages/tests/sub/governance/technicalCommittee.test.ts @@ -1,6 +1,6 @@ import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js'; -import {Event} from '@unique/playgrounds/unique.dev.js'; +import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js'; +import {Event} from '@unique/test-utils'; import {initCouncil, democracyLaunchPeriod, democracyFastTrackVotingPeriod, clearCouncil, clearTechComm, clearFellowship, defaultEnactmentMoment, dummyProposal, dummyProposalCall, fellowshipPropositionOrigin, initFellowship, initTechComm, hardResetFellowshipReferenda, hardResetDemocracy, hardResetGovScheduler} from './util.js'; import type {ITechComms} from './util.js'; --- a/js-packages/tests/sub/governance/util.ts +++ b/js-packages/tests/sub/governance/util.ts @@ -1,9 +1,9 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {xxhashAsHex} from '@polkadot/util-crypto'; import type {u32} from '@polkadot/types-codec'; -import {usingPlaygrounds, expect} from '../../util/index.js'; -import {UniqueHelper} from '@unique/playgrounds/unique.js'; -import {DevUniqueHelper} from '@unique/playgrounds/unique.dev.js'; +import {usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {UniqueHelper} from '@unique-nft/playgrounds/unique.js'; +import {DevUniqueHelper} from '@unique/test-utils'; export const democracyLaunchPeriod = 35; export const democracyVotingPeriod = 35; --- a/js-packages/tests/sub/nesting/admin.test.ts +++ b/js-packages/tests/sub/nesting/admin.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, usingPlaygrounds} from '../../util/index.js'; -import {CrossAccountId} from '@unique/playgrounds/unique.js'; +import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js'; +import {CrossAccountId} from '@unique-nft/playgrounds/unique.js'; describe('Nesting by collection admin', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/nesting/collectionProperties.seqtest.ts +++ b/js-packages/tests/sub/nesting/collectionProperties.seqtest.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '../../util/index.js'; +import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '@unique/test-utils/util.js'; describe('Integration Test: Collection Properties with sudo', () => { let superuser: IKeyringPair; --- a/js-packages/tests/sub/nesting/collectionProperties.test.ts +++ b/js-packages/tests/sub/nesting/collectionProperties.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '../../util/index.js'; +import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '@unique/test-utils/util.js'; describe('Integration Test: Collection Properties', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/nesting/common.test.ts +++ b/js-packages/tests/sub/nesting/common.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, usingPlaygrounds} from '../../util/index.js'; -import {CrossAccountId, UniqueNFTCollection, UniqueRFTCollection} from '@unique/playgrounds/unique.js'; +import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js'; +import {CrossAccountId, UniqueNFTCollection, UniqueRFTCollection} from '@unique-nft/playgrounds/unique.js'; let alice: IKeyringPair; let bob: IKeyringPair; --- a/js-packages/tests/sub/nesting/e2e.test.ts +++ b/js-packages/tests/sub/nesting/e2e.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, usingPlaygrounds} from '../../util/index.js'; -import {CrossAccountId} from '@unique/playgrounds/unique.js'; +import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js'; +import {CrossAccountId} from '@unique-nft/playgrounds/unique.js'; describe('Composite nesting tests', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/nesting/graphs.test.ts +++ b/js-packages/tests/sub/nesting/graphs.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, usingPlaygrounds} from '../../util/index.js'; -import {UniqueHelper, UniqueNFTCollection, UniqueNFToken} from '@unique/playgrounds/unique.js'; +import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js'; +import {UniqueHelper, UniqueNFTCollection, UniqueNFToken} from '@unique-nft/playgrounds/unique.js'; /** * ```dot --- a/js-packages/tests/sub/nesting/nesting.negative.test.ts +++ b/js-packages/tests/sub/nesting/nesting.negative.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, usingPlaygrounds} from '../../util/index.js'; -import {UniqueFTCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '@unique/playgrounds/unique.js'; +import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js'; +import {UniqueFTCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '@unique-nft/playgrounds/unique.js'; import {itEth} from '../../eth/util/index.js'; let alice: IKeyringPair; --- a/js-packages/tests/sub/nesting/propertyPermissions.test.ts +++ b/js-packages/tests/sub/nesting/propertyPermissions.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, usingPlaygrounds, expect} from '../../util/index.js'; -import {UniqueNFTCollection, UniqueRFTCollection} from '@unique/playgrounds/unique.js'; +import {itSub, Pallets, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {UniqueNFTCollection, UniqueRFTCollection} from '@unique-nft/playgrounds/unique.js'; describe('Integration Test: Access Rights to Token Properties', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/nesting/refungible.test.ts +++ b/js-packages/tests/sub/nesting/refungible.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, usingPlaygrounds} from '../../util/index.js'; +import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js'; describe('ReFungible-specific nesting tests', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/nesting/tokenProperties.seqtest.ts +++ b/js-packages/tests/sub/nesting/tokenProperties.seqtest.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '../../util/index.js'; +import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '@unique/test-utils/util.js'; describe('Integration Test: Token Properties with sudo', () => { let superuser: IKeyringPair; --- a/js-packages/tests/sub/nesting/tokenProperties.test.ts +++ b/js-packages/tests/sub/nesting/tokenProperties.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect, sizeOfProperty} from '../../util/index.js'; -import {UniqueHelper, UniqueNFToken, UniqueRFToken} from '@unique/playgrounds/unique.js'; +import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect, sizeOfProperty} from '@unique/test-utils/util.js'; +import {UniqueHelper, UniqueNFToken, UniqueRFToken} from '@unique-nft/playgrounds/unique.js'; describe('Integration Test: Token Properties', () => { let alice: IKeyringPair; // collection owner --- a/js-packages/tests/sub/nesting/unnest.test.ts +++ b/js-packages/tests/sub/nesting/unnest.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, usingPlaygrounds} from '../../util/index.js'; -import {CrossAccountId, UniqueFTCollection, UniqueNFToken, UniqueRFToken} from '@unique/playgrounds/unique.js'; +import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js'; +import {CrossAccountId, UniqueFTCollection, UniqueNFToken, UniqueRFToken} from '@unique-nft/playgrounds/unique.js'; describe('Integration Test: Unnesting', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/nesting/unnesting.negative.test.ts +++ b/js-packages/tests/sub/nesting/unnesting.negative.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, usingPlaygrounds} from '../../util/index.js'; -import {CrossAccountId} from '@unique/playgrounds/unique.js'; +import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js'; +import {CrossAccountId} from '@unique-nft/playgrounds/unique.js'; describe('Negative Test: Unnesting', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/refungible/burn.test.ts +++ b/js-packages/tests/sub/refungible/burn.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../../util/index.js'; +import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; describe('Refungible: burn', () => { let donor: IKeyringPair; --- a/js-packages/tests/sub/refungible/nesting.test.ts +++ b/js-packages/tests/sub/refungible/nesting.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../../util/index.js'; +import {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js'; describe('Refungible nesting', () => { let alice: IKeyringPair; --- a/js-packages/tests/sub/refungible/repartition.test.ts +++ b/js-packages/tests/sub/refungible/repartition.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../../util/index.js'; +import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; describe('integration test: Refungible functionality:', () => { let donor: IKeyringPair; --- a/js-packages/tests/sub/refungible/transfer.test.ts +++ b/js-packages/tests/sub/refungible/transfer.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../../util/index.js'; +import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; describe('Refungible transfer tests', () => { let donor: IKeyringPair; --- a/js-packages/tests/transfer.test.ts +++ b/js-packages/tests/transfer.test.ts @@ -16,8 +16,8 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {itEth, usingEthPlaygrounds} from './eth/util/index.js'; -import {itSub, Pallets, usingPlaygrounds, expect} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {itSub, Pallets, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => { let donor: IKeyringPair; --- a/js-packages/tests/transferFrom.test.ts +++ b/js-packages/tests/transferFrom.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, Pallets, usingPlaygrounds, expect} from './util/index.js'; -import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js'; +import {itSub, Pallets, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; +import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js'; describe('Integration Test transferFrom(from, recipient, collection_id, item_id, value):', () => { let alice: IKeyringPair; --- a/js-packages/tests/tx-version-presence.test.ts +++ b/js-packages/tests/tx-version-presence.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import {Metadata} from '@polkadot/types'; -import {itSub, usingPlaygrounds, expect} from './util/index.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; let metadata: Metadata; --- a/js-packages/tests/util/authorizeEnactUpgrade.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {readFile} from 'fs/promises'; -import {u8aToHex} from '@polkadot/util'; -import {usingPlaygrounds} from './index.js'; -import {blake2AsHex} from '@polkadot/util-crypto'; - - -const codePath = process.argv[2]; -if(!codePath) throw new Error('missing code path argument'); - -const code = await readFile(codePath); - -await usingPlaygrounds(async (helper, privateKey) => { - const alice = await privateKey('//Alice'); - const hex = blake2AsHex(code); - await helper.getSudo().executeExtrinsic(alice, 'api.tx.parachainSystem.authorizeUpgrade', [hex, true]); - await helper.getSudo().executeExtrinsicUncheckedWeight(alice, 'api.tx.parachainSystem.enactAuthorizedUpgrade', [u8aToHex(code)]); -}); -// We miss disconnect/unref somewhere. -process.exit(0); --- a/js-packages/tests/util/createHrmp.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {usingPlaygrounds} from './index.js'; -import config from '../config.js'; - -const profile = process.argv[2]; -if(!profile) throw new Error('missing profile/relay argument'); - -await usingPlaygrounds(async (helper, privateKey) => { - const bidirOpen = async (a: number, b: number) => { - console.log(`Opening ${a} <=> ${b}`); - await helper.getSudo().executeExtrinsic(alice, 'api.tx.hrmp.forceOpenHrmpChannel', [a, b, 8, 512]); - await helper.getSudo().executeExtrinsic(alice, 'api.tx.hrmp.forceOpenHrmpChannel', [b, a, 8, 512]); - }; - const alice = await privateKey('//Alice'); - switch (profile) { - case 'opal': - await bidirOpen(1001, 1002); - break; - case 'quartz': - await bidirOpen(1001, 1002); - await bidirOpen(1001, 1003); - await bidirOpen(1001, 1004); - await bidirOpen(1001, 1005); - break; - case 'unique': - await bidirOpen(1001, 1002); - await bidirOpen(1001, 1003); - await bidirOpen(1001, 1004); - await bidirOpen(1001, 1005); - await bidirOpen(1001, 1006); - await bidirOpen(1001, 1007); - break; - default: - throw new Error(`unknown hrmp config profile: ${profile}`); - } -}, config.relayUrl); -// We miss disconnect/unref somewhere. -process.exit(0); --- a/js-packages/tests/util/frankensteinMigrate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {migration as locksToFreezesMigration} from '../migrations/942057-appPromotion/index.js'; -export interface Migration { - before: () => Promise, - after: () => Promise, -} - -export const migrations: {[key: string]: Migration} = { - 'v942057': locksToFreezesMigration, -}; --- a/js-packages/tests/util/globalSetup.ts +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -import { - usingPlaygrounds, Pallets, DONOR_FUNDING, MINIMUM_DONOR_FUND, LOCKING_PERIOD, UNLOCKING_PERIOD, makeNames, -} from './index.js'; -import * as path from 'path'; -import {promises as fs} from 'fs'; - -const {dirname} = makeNames(import.meta.url); - -// This function should be called before running test suites. -const globalSetup = async (): Promise => { - await usingPlaygrounds(async (helper, privateKey) => { - try { - // 1. Wait node producing blocks - await helper.wait.newBlocks(1, 600_000); - - // 2. Create donors for test files - await fundFilenamesWithRetries(3) - .then((result) => { - if(!result) throw Error('Some problems with fundFilenamesWithRetries'); - }); - - // 3. Configure App Promotion - const missingPallets = helper.fetchMissingPalletNames([Pallets.AppPromotion]); - if(missingPallets.length === 0) { - const superuser = await privateKey('//Alice'); - const palletAddress = helper.arrange.calculatePalletAddress('appstake'); - const palletAdmin = await privateKey('//PromotionAdmin'); - const api = helper.getApi(); - await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}))); - const nominal = helper.balance.getOneTokenNominal(); - await helper.balance.transferToSubstrate(superuser, palletAdmin.address, 10000n * nominal); - await helper.balance.transferToSubstrate(superuser, palletAddress, 10000n * nominal); - await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [api.tx.configuration - .setAppPromotionConfigurationOverride({ - recalculationInterval: LOCKING_PERIOD, - pendingInterval: UNLOCKING_PERIOD})], true); - } - } catch (error) { - console.error(error); - throw Error('Error during globalSetup'); - } - }); -}; - -async function getFiles(rootPath: string): Promise { - const files = await fs.readdir(rootPath, {withFileTypes: true}); - const filenames: string[] = []; - for(const entry of files) { - const res = path.resolve(rootPath, entry.name); - if(entry.isDirectory()) { - filenames.push(...await getFiles(res)); - } else { - filenames.push(res); - } - } - return filenames; -} - -const fundFilenames = async () => { - await usingPlaygrounds(async (helper, privateKey) => { - const oneToken = helper.balance.getOneTokenNominal(); - const alice = await privateKey('//Alice'); - const nonce = await helper.chain.getNonce(alice.address); - const filenames = await getFiles(path.resolve(dirname, '..')); - - // batching is actually undesireable, it takes away the time while all the transactions actually succeed - const batchSize = 300; - let balanceGrantedCounter = 0; - for(let b = 0; b < filenames.length; b += batchSize) { - const tx: Promise[] = []; - let batchBalanceGrantedCounter = 0; - for(let i = 0; batchBalanceGrantedCounter < batchSize && b + i < filenames.length; i++) { - const f = filenames[b + i]; - if(!f.endsWith('.test.ts') && !f.endsWith('seqtest.ts') || f.includes('.outdated')) continue; - const account = await privateKey({filename: f, ignoreFundsPresence: true}); - const aliceBalance = await helper.balance.getSubstrate(account.address); - - if(aliceBalance < MINIMUM_DONOR_FUND * oneToken) { - tx.push(helper.executeExtrinsic( - alice, - 'api.tx.balances.transferKeepAlive', - [account.address, DONOR_FUNDING * oneToken], - true, - {nonce: nonce + balanceGrantedCounter++}, - ).then(() => true).catch(() => {console.error(`Transaction to ${path.basename(f)} registered as failed. Strange.`); return false;})); - batchBalanceGrantedCounter++; - } - } - - if(tx.length > 0) { - console.log(`Granting funds to ${batchBalanceGrantedCounter} filename accounts.`); - const result = await Promise.all(tx); - if(result && result.lastIndexOf(false) > -1) throw new Error('The transactions actually probably succeeded, should check the balances.'); - } - } - - if(balanceGrantedCounter == 0) console.log('No account needs additional funding.'); - }); -}; - -const fundFilenamesWithRetries = (retriesLeft: number): Promise => { - if(retriesLeft <= 0) return Promise.resolve(false); - return fundFilenames() - .then(() => Promise.resolve(true)) - .catch(e => { - console.error(e); - console.error(`Some transactions might have failed. ${retriesLeft > 1 ? 'Retrying...' : 'Something is wrong.'}\n`); - return fundFilenamesWithRetries(--retriesLeft); - }); -}; - -globalSetup().catch(e => { - console.error('Setup error'); - console.error(e); - process.exit(1); -}); --- a/js-packages/tests/util/identitySetter.ts +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// SPDX-License-Identifier: Apache-2.0 -// -// Pulls identities and sub-identities from a chain and then makes a preimage to later force upload them into another. -// Only changed or previously non-existent data are inserted. -// -// Usage: `yarn setIdentities [relay WS URL] [parachain WS URL] [user key] -// Example: `yarn setIdentities wss://polkadot-rpc.dwellir.com ws://localhost:9944 escape pattern miracle train sudden cart adapt embark wedding alien lamp mesh` - -import {encodeAddress} from '@polkadot/keyring'; -import type {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds, Pallets} from './index.js'; -import {ChainHelperBase} from '@unique/playgrounds/unique.js'; - -const relayUrl = process.argv[2] ?? 'ws://localhost:9844'; -const paraUrl = process.argv[3] ?? 'ws://localhost:9944'; -const key = process.argv.length > 4 ? process.argv.slice(4).join(' ') : '//Alice'; - -export function extractAccountId(key: any): string { - return (key as any).toHuman()[0]; -} - -export function extractIdentityInfo(value: any): object { - const heart = (value as any).unwrap(); - const identity = heart.toJSON(); - identity.judgements = heart.judgements.toHuman(); - return identity; -} - -export function extractIdentity(key: any, value: any): [string, any] { - return [extractAccountId(key), extractIdentityInfo(value)]; -} - -export async function getIdentities(helper: ChainHelperBase, noneCasePredicate?: (key: any, value: any) => void) { - const identities: [string, any][] = []; - for(const [key, v] of await helper.getApi().query.identity.identityOf.entries()) { - const value = v as any; - if(value.isNone) { - if(noneCasePredicate) noneCasePredicate(key, value); - continue; - } - identities.push(extractIdentity(key, value)); - } - return identities; -} - -// whether the existing chain data is more important than the coming -function isCurrentChainDataPriority(helper: ChainHelperBase, currentChainId: number | undefined, relayChainId: number) { - if(!currentChainId) return false; - // information has come from local chain, and is automatically superior - if(currentChainId == helper.chain.getChainProperties().ss58Format) return true; - // the lower the id, the more important it is (Polkadot has ss58 prefix = 0, Kusama has ss58 prefix = 2) - return currentChainId < relayChainId; -} - -// construct an object with all data necessary for insertion from storage query results -export function constructSubInfo(identityAccount: string, subQuery: any, supers: any[], ss58?: number): [string, any] { - const deposit = subQuery.toJSON()[0]; - const subIdentities = subQuery.toHuman()[1]; - subIdentities.map((sub: string) => supers.find((sup: any) => sup[0] === sub)); - - return [ - encodeAddress(identityAccount, ss58), [ - deposit, - subIdentities.map((sub: string): [string, object] | null => { - const sup = supers.find((sup: any) => sup[0] === sub); - if(!sup) { - console.error(`Error: Could not find info on super for \nsub-identity account\t${sub} of \nsuper account \t\t${identityAccount}, skipping.`); - return null; - } - return [encodeAddress(sub, ss58), sup[1].toJSON()[1]]; - }).filter((x: any) => x), - ], - ]; -} - -export async function getSubs(helper: ChainHelperBase) { - return (await helper.getApi().query.identity.subsOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]); -} - -export async function getSupers(helper: ChainHelperBase) { - return (await helper.getApi().query.identity.superOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]); -} - -async function uploadPreimage(helper: ChainHelperBase, preimageMaker: IKeyringPair, preimage: string) { - try { - await helper.executeExtrinsic(preimageMaker, 'api.tx.preimage.notePreimage', [preimage]); - } catch (e: any) { - if(e.message.includes('AlreadyNoted')) { - console.warn('Warning: The same preimage already exists on the chain. Nothing was uploaded.'); - } else { - console.error(e); - } - } -} - -// The utility for pulling identity and sub-identity data -const forceInsertIdentities = async (): Promise => { - let relaySS58Prefix = 0; - const identitiesOnRelay: any[] = []; - const subsOnRelay: any[] = []; - const identitiesToRemove: string[] = []; - await usingPlaygrounds(async helper => { - try { - relaySS58Prefix = helper.chain.getChainProperties().ss58Format; - // iterate over every identity - for(const [key, value] of await getIdentities(helper, (key, _value) => identitiesToRemove.push((key as any).toHuman()[0]))) { - // if any of the judgements resulted in a good confirmed outcome, keep this identity - let knownGood = false, reasonable = false; - for(const [_id, judgement] of value.judgements) { - if(judgement == 'KnownGood') knownGood = true; - if(judgement == 'Reasonable') reasonable = true; - } - if(!(reasonable || knownGood)) continue; - // replace the registrator id with the relay chain's ss58 format - value.judgements = [[helper.chain.getChainProperties().ss58Format, knownGood ? 'KnownGood' : 'Reasonable']]; - identitiesOnRelay.push([key, value]); - } - - const sublessIdentities = [...identitiesOnRelay]; - const supersOfSubs = await getSupers(helper); - - // iterate over every sub-identity - for(const [key, value] of await getSubs(helper)) { - // only get subs of the identities interesting to us - const identityIndex = sublessIdentities.findIndex((x: any) => x[0] == key); - if(identityIndex == -1) continue; - sublessIdentities.splice(identityIndex, 1); - subsOnRelay.push(constructSubInfo(key, value, supersOfSubs)); - } - - // mark the rest of sub-identities for deletion with empty arrays - /*for(const [account, _identity] of sublessIdentities) { - subsOnRelay.push([account, ['0', []]]); - }*/ - } catch (error) { - console.error(error); - throw Error('Error during fetching identities'); - } - }, relayUrl); - - await usingPlaygrounds(async (helper, privateKey) => { - if(helper.fetchMissingPalletNames([Pallets.Identity]).length != 0) console.error('pallet-identity is not included in parachain.'); - if(helper.fetchMissingPalletNames([Pallets.Preimage]).length != 0) console.error('pallet-preimage is not included in parachain.'); - - try { - const preimageMaker = await privateKey(key); - const ss58Format = helper.chain.getChainProperties().ss58Format; - const paraIdentities = await getIdentities(helper); - const identitiesToAdd: any[] = []; - const paraAccountsRegistrators: {[name: string]: number} = {}; - - // cross-reference every account for changes - for(const [key, value] of identitiesOnRelay) { - const encodedKey = encodeAddress(key, ss58Format); - - // only update if the identity info does not exist or is changed - const identity = paraIdentities.find(i => i[0] === encodedKey); - if(identity) { - const registratorId = identity[1].judgements[0][0]; - paraAccountsRegistrators[encodedKey] = registratorId; - if(isCurrentChainDataPriority(helper, registratorId, value.judgements[0][0]) - || JSON.stringify(value.info) === JSON.stringify(identity[1].info)) { - continue; - } - } - - identitiesToAdd.push([key, value]); - // exercise caution - in case we have an identity and the realy doesn't, it might mean one of two things: - // 1) it was deleted on the relay; - // 2) it is our own identity, we don't want to delete it. - // identitiesToRemove.push((key as any).toHuman()[0]); - } - - if(identitiesToRemove.length != 0) - await uploadPreimage( - helper, - preimageMaker, - helper.constructApiCall('api.tx.identity.forceRemoveIdentities', [identitiesToRemove]).method.toHex(), - ); - if(identitiesToAdd.length != 0) - await uploadPreimage( - helper, - preimageMaker, - helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex(), - ); - - console.log(`Tried to push ${identitiesToAdd.length} identities` - + ` and found ${identitiesToRemove.length} identities for potential removal.` - + ` Currently there are ${(await helper.getApi().query.identity.identityOf.keys()).length} identities on the chain.`); - - // fill sub-identities - const paraSubs = await getSubs(helper); - const supersOfSubs = await getSupers(helper); - const subsToUpdate: any[] = []; - - for(const [key, value] of subsOnRelay) { - const encodedKey = encodeAddress(key, ss58Format); - const sub = paraSubs.find(i => i[0] === encodedKey); - if(sub) { - // only update if the sub-identity info does not exist or is changed - if(isCurrentChainDataPriority(helper, paraAccountsRegistrators[encodedKey], relaySS58Prefix) - || JSON.stringify(value) === JSON.stringify(constructSubInfo(sub[0], sub[1], supersOfSubs)[1])) { - continue; - } - } else if(value[1].length == 0) - continue; - - subsToUpdate.push([key, value]); - } - - if(subsToUpdate.length != 0) - await uploadPreimage( - helper, - preimageMaker, - helper.constructApiCall('api.tx.identity.forceSetSubs', [subsToUpdate]).method.toHex(), - ); - - console.log(`Also tried to push ${subsToUpdate.length} identities with their sub-identities.` - + ` Currently there are ${(await helper.getApi().query.identity.subsOf.keys()).length} identities with subs.`); - } catch (error) { - console.error(error); - throw Error('Error during setting identities'); - } - }, paraUrl); -}; - -if(process.argv[1] === module.filename) - forceInsertIdentities().catch(() => process.exit(1)); --- a/js-packages/tests/util/index.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -import * as path from 'path'; -import * as crypto from 'crypto'; -import type {IKeyringPair} from '@polkadot/types/types/interfaces'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import chaiSubset from 'chai-subset'; -import {Context} from 'mocha'; -import config from '../config.js'; -import {ChainHelperBase} from '@unique/playgrounds/unique.js'; -import type {ILogger} from '@unique/playgrounds/types.js'; -import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper, DevHydraDxHelper} from '@unique/playgrounds/unique.dev.js'; -import {dirname} from 'path'; -import {fileURLToPath} from 'url'; - -chai.config.truncateThreshold = 0; -chai.use(chaiAsPromised); -chai.use(chaiSubset); -export const expect = chai.expect; - -const getTestHash = (filename: string) => crypto.createHash('md5').update(filename).digest('hex'); - -export const getTestSeed = (filename: string) => `//Alice+${getTestHash(filename)}`; - -async function usingPlaygroundsGeneral( - helperType: new (logger: ILogger) => T, - url: string, - code: (helper: T, privateKey: (seed: string | { filename?: string, url?: string, ignoreFundsPresence?: boolean }) => Promise) => Promise, -): Promise { - const silentConsole = new SilentConsole(); - silentConsole.enable(); - - const helper = new helperType(new SilentLogger()); - let result; - try { - await helper.connect(url); - const ss58Format = helper.chain.getChainProperties().ss58Format; - const privateKey = async (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => { - if(typeof seed === 'string') { - return helper.util.fromSeed(seed, ss58Format); - } - if(seed.url) { - const {filename} = makeNames(seed.url); - seed.filename = filename; - } else if(seed.filename) { - // Pass - } else { - throw new Error('no url nor filename set'); - } - const actualSeed = getTestSeed(seed.filename); - let account = helper.util.fromSeed(actualSeed, ss58Format); - // here's to hoping that no - if(!seed.ignoreFundsPresence && ((helper as any)['balance'] == undefined || await (helper as any).balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND)) { - console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`); - account = helper.util.fromSeed('//Alice', ss58Format); - } - return account; - }; - result = await code(helper, privateKey); - } - finally { - await helper.disconnect(); - silentConsole.disable(); - } - return result as any as R; -} - -export const usingPlaygrounds = (code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => Promise) => Promise, url: string = config.substrateUrl) => usingPlaygroundsGeneral(DevUniqueHelper, url, code); - -export const usingWestmintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevWestmintHelper, url, code); - -export const usingStateminePlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevWestmintHelper, url, code); - -export const usingStatemintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevWestmintHelper, url, code); - -export const usingRelayPlaygrounds = (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevRelayHelper, url, code); - -export const usingAcalaPlaygrounds = (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevAcalaHelper, url, code); - -export const usingKaruraPlaygrounds = (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevAcalaHelper, url, code); - -export const usingMoonbeamPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevMoonbeamHelper, url, code); - -export const usingMoonriverPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevMoonriverHelper, url, code); - -export const usingAstarPlaygrounds = (url: string, code: (helper: DevAstarHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevAstarHelper, url, code); - -export const usingShidenPlaygrounds = (url: string, code: (helper: DevShidenHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevShidenHelper, url, code); - -export const usingPolkadexPlaygrounds = (url: string, code: (helper: DevPolkadexHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevPolkadexHelper, url, code); - -export const usingHydraDxPlaygrounds = (url: string, code: (helper: DevHydraDxHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevHydraDxHelper, url, code); - -export const MINIMUM_DONOR_FUND = 4_000_000n; -export const DONOR_FUNDING = 4_000_000n; - -// App-promotion periods: -export const LOCKING_PERIOD = 12n; // 12 blocks of relay -export const UNLOCKING_PERIOD = 6n; // 6 blocks of parachain - -// Native contracts -export const COLLECTION_HELPER = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f'; -export const CONTRACT_HELPER = '0x842899ECF380553E8a4de75bF534cdf6fBF64049'; - -export enum Pallets { - Inflation = 'inflation', - ReFungible = 'refungible', - Fungible = 'fungible', - NFT = 'nonfungible', - Scheduler = 'scheduler', - UniqueScheduler = 'uniqueScheduler', - AppPromotion = 'apppromotion', - CollatorSelection = 'collatorselection', - Session = 'session', - Identity = 'identity', - Democracy = 'democracy', - Council = 'council', - //CouncilMembership = 'councilmembership', - TechnicalCommittee = 'technicalcommittee', - Fellowship = 'fellowshipcollective', - Preimage = 'preimage', - Maintenance = 'maintenance', - TestUtils = 'testutils', -} - -export function requirePalletsOrSkip(test: Context, helper: DevUniqueHelper, requiredPallets: readonly string[]) { - const missingPallets = helper.fetchMissingPalletNames(requiredPallets); - - if(missingPallets.length > 0) { - const skipMsg = `\tSkipping test '${test.test?.title}'.\n\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; - console.warn('\x1b[38:5:208m%s\x1b[0m', skipMsg); - test.skip(); - } -} - -export function itSub(name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) { - (opts.only ? it.only : - opts.skip ? it.skip : it)(name, async function () { - await usingPlaygrounds(async (helper, privateKey) => { - if(opts.requiredPallets) { - requirePalletsOrSkip(this, helper, opts.requiredPallets); - } - - await cb({helper, privateKey}); - }); - }); -} -export function itSubIfWithPallet(name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) { - return itSub(name, cb, {requiredPallets: required, ...opts}); -} -itSub.only = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSub(name, cb, {only: true}); -itSub.skip = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSub(name, cb, {skip: true}); - -itSubIfWithPallet.only = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSubIfWithPallet(name, required, cb, {only: true}); -itSubIfWithPallet.skip = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSubIfWithPallet(name, required, cb, {skip: true}); -itSub.ifWithPallets = itSubIfWithPallet; - -export type SchedKind = 'anon' | 'named'; - -export function itSched( - name: string, - cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any, - opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}, -) { - itSub(name + ' (anonymous scheduling)', (apis) => cb('anon', apis), opts); - itSub(name + ' (named scheduling)', (apis) => cb('named', apis), opts); -} -itSched.only = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSched(name, cb, {only: true}); -itSched.skip = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any) => itSched(name, cb, {skip: true}); -itSched.ifWithPallets = itSchedIfWithPallets; - -function itSchedIfWithPallets(name: string, required: string[], cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) { - return itSched(name, cb, {requiredPallets: required, ...opts}); -} - -export function describeXCM(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) { - (process.env.RUN_XCM_TESTS && !opts.skip - ? describe - : describe.skip)(title, fn); -} - -describeXCM.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeXCM(name, fn, {skip: true}); - -export function describeGov(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) { - (process.env.RUN_GOV_TESTS && !opts.skip - ? describe - : describe.skip)(title, fn); -} - -describeGov.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeGov(name, fn, {skip: true}); - -export function sizeOfInt(i: number) { - if(i < 0 || i > 0xffffffff) throw new Error('out of range'); - if(i < 0b11_1111) { - return 1; - } else if(i < 0b11_1111_1111_1111) { - return 2; - } else if(i < 0b11_1111_1111_1111_1111_1111_1111_1111) { - return 4; - } else { - return 5; - } -} - -const UTF8_ENCODER = new TextEncoder(); -export function sizeOfEncodedStr(v: string) { - const encoded = UTF8_ENCODER.encode(v); - return sizeOfInt(encoded.length) + encoded.length; -} - -export function sizeOfProperty(prop: {key: string, value: string}) { - return sizeOfEncodedStr(prop.key) + sizeOfEncodedStr(prop.value); -} - -export function makeNames(url: string) { - const filename = fileURLToPath(url); - return { - filename, - dirname: dirname(filename), - }; -} --- a/js-packages/tests/util/relayIdentitiesChecker.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// SPDX-License-Identifier: Apache-2.0 -// -// Checks and reports the differences between identities and sub-identities on two chains. -// -// Usage: `yarn checkRelayIdentities [relay-1 WS URL] [relay-2 WS URL]` -// Example: `yarn checkRelayIdentities wss://polkadot-rpc.dwellir.com wss://kusama-rpc.dwellir.com` - -import {encodeAddress} from '@polkadot/keyring'; -import {usingPlaygrounds} from './index.js'; -import {getIdentities, getSubs, getSupers, constructSubInfo} from './identitySetter.js'; - -const relay1Url = process.argv[2] ?? 'ws://localhost:9844'; -const relay2Url = process.argv[3] ?? 'ws://localhost:9844'; - -async function pullIdentities(relayUrl: string): Promise<[any[], any[]]> { - const identities: any[] = []; - const subs: any[] = []; - - await usingPlaygrounds(async helper => { - try { - // iterate over every identity - for(const [key, value] of await getIdentities(helper)) { - // if any of the judgements resulted in a good confirmed outcome, keep this identity - if(value.toHuman().judgements.filter((x: any) => x[1] == 'Reasonable' || x[1] == 'KnownGood').length == 0) continue; - identities.push([key, value]); - } - - const supersOfSubs = await getSupers(helper); - - // iterate over every sub-identity - for(const [key, value] of await getSubs(helper)) { - // only get subs of the identities interesting to us - if(identities.find((x: any) => x[0] == key) == -1) continue; - subs.push(constructSubInfo(key, value, supersOfSubs)); - } - } catch (error) { - console.error(error); - throw Error(`Error during fetching identities on ${relayUrl}`); - } - }, relayUrl); - - return [identities, subs]; -} - -// The utility for pulling identity and sub-identity data -const checkRelayIdentities = async (): Promise => { - const [identitiesOnRelay1, subIdentitiesOnRelay1] = await pullIdentities(relay1Url); - const [identitiesOnRelay2, subIdentitiesOnRelay2] = await pullIdentities(relay2Url); - - console.log('identities counts:\t', identitiesOnRelay1.length, identitiesOnRelay2.length); - console.log('sub-identities counts:\t', subIdentitiesOnRelay1.length, subIdentitiesOnRelay2.length); - console.log(); - - try { - const matchingAddresses: string[] = []; - const inequalIdentities: {[name: string]: [any, any]} = {}; - - for(const [key1, value1] of identitiesOnRelay1) { - const address = encodeAddress(key1); - const identity2 = identitiesOnRelay2.find(([key2, _value2]) => address === encodeAddress(key2)); - if(!identity2) continue; - matchingAddresses.push(address); - - //const [[key2, value2]] = identitiesOnRelay2.splice(index2, 1); - const [_key2, value2] = identity2; - if(JSON.stringify(value1.info) === JSON.stringify(value2.info)) continue; - inequalIdentities[address] = [value1, value2]; - } - - /*for (const [v1, v2] of Object.values(inequalIdentities)) { - console.log(v1.toHuman().info); - console.log(); - console.log(v2.toHuman().info); - await new Promise(resolve => setTimeout(resolve, 4000)); - }*/ - - console.log(`Accounts with identities on both relays:\t${matchingAddresses.length}`); - console.log(`Sub-identities with conflicting information:\t${Object.entries(inequalIdentities).length}`); - console.log(); - - const inequalSubIdentities = []; - let matchesFound = 0; - for(const address of matchingAddresses) { - const sub1 = subIdentitiesOnRelay1.find(([key1, _value1]) => address === encodeAddress(key1)); - if(!sub1) continue; - const sub2 = subIdentitiesOnRelay2.find(([key2, _value2]) => address === encodeAddress(key2)); - if(!sub2) continue; - - const [value1, value2] = [sub1[1], sub2[1]]; - matchesFound++; - - if(JSON.stringify(value1[1]) === JSON.stringify(value2[1])) { - continue; - } - inequalSubIdentities.push([value1, value2]); - } - - /*for (const [v1, v2] of inequalSubIdentities) { - console.log(v1[1]); - console.log(); - console.log(v2[1]); - await new Promise(resolve => setTimeout(resolve, 300)); - }*/ - console.log(`Accounts with sub-identities on both relays:\t${matchesFound}`); - console.log(`Of them, those with conflicting sub-identities:\t${inequalSubIdentities.length}`); - } catch (error) { - console.error(error); - throw Error('Error during comparison'); - } -}; - -if(process.argv[1] === module.filename) - checkRelayIdentities().catch(() => process.exit(1)); --- a/js-packages/tests/util/setCode.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {readFile} from 'fs/promises'; -import {u8aToHex} from '@polkadot/util'; -import {usingPlaygrounds} from './index.js'; - -const codePath = process.argv[2]; -if(!codePath) throw new Error('missing code path argument'); - -const code = await readFile(codePath); - -await usingPlaygrounds(async (helper, privateKey) => { - const alice = await privateKey('//Alice'); - await helper.getSudo().executeExtrinsicUncheckedWeight(alice, 'api.tx.system.setCode', [u8aToHex(code)]); -}); -// We miss disconnect/unref somewhere. -process.exit(0); --- a/js-packages/tests/vesting.test.ts +++ b/js-packages/tests/vesting.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from './util/index.js'; +import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js'; describe('Vesting', () => { let donor: IKeyringPair; --- a/js-packages/tests/xcm/lowLevelXcmQuartz.test.ts +++ b/js-packages/tests/xcm/lowLevelXcmQuartz.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util/index.js'; +import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds} from '@unique/test-utils/util.js'; import {QUARTZ_CHAIN, QTZ_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, shidenUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT, SENDER_BUDGET, relayUrl} from './xcm.types.js'; import {hexToString} from '@polkadot/util'; --- a/js-packages/tests/xcm/lowLevelXcmUnique.test.ts +++ b/js-packages/tests/xcm/lowLevelXcmUnique.test.ts @@ -16,7 +16,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import config from '../config.js'; -import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingHydraDxPlaygrounds} from '../util/index.js'; +import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingHydraDxPlaygrounds} from '@unique/test-utils/util.js'; import {nToBigInt} from '@polkadot/util'; import {hexToString} from '@polkadot/util'; import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, hydraDxUrl, moonbeamUrl, polkadexUrl, uniqueAssetId} from './xcm.types.js'; --- a/js-packages/tests/xcm/xcm.types.ts +++ b/js-packages/tests/xcm/xcm.types.ts @@ -1,7 +1,6 @@ import type {IKeyringPair} from '@polkadot/types/types'; -import {hexToString} from '@polkadot/util'; -import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingHydraDxPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util/index.js'; -import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js'; +import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingHydraDxPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '@unique/test-utils/util.js'; +import {DevUniqueHelper, Event} from '@unique/test-utils'; import config from '../config.js'; export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037); --- a/js-packages/tests/xcm/xcmOpal.test.ts +++ b/js-packages/tests/xcm/xcmOpal.test.ts @@ -16,7 +16,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import config from '../config.js'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '../util/index.js'; +import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '@unique/test-utils/util.js'; import {XcmTestHelper} from './xcm.types.js'; const STATEMINE_CHAIN = +(process.env.RELAY_WESTMINT_ID || 1000); --- a/js-packages/tests/xcm/xcmQuartz.test.ts +++ b/js-packages/tests/xcm/xcmQuartz.test.ts @@ -15,8 +15,8 @@ // along with Unique Network. If not, see . import type {IKeyringPair} from '@polkadot/types/types'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util/index.js'; -import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js'; +import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '@unique/test-utils/util.js'; +import {DevUniqueHelper, Event} from '@unique/test-utils'; import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types.js'; import {hexToString} from '@polkadot/util'; import {XcmTestHelper} from './xcm.types.js'; --- a/js-packages/tests/xcm/xcmUnique.test.ts +++ b/js-packages/tests/xcm/xcmUnique.test.ts @@ -16,8 +16,8 @@ import type {IKeyringPair} from '@polkadot/types/types'; import config from '../config.js'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util/index.js'; -import {Event} from '@unique/playgrounds/unique.dev.js'; +import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '@unique/test-utils/util.js'; +import {Event} from '@unique/test-utils'; import {hexToString, nToBigInt} from '@polkadot/util'; import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js'; import {XcmTestHelper} from './xcm.types.js'; --- a/js-packages/types/package.json +++ b/js-packages/types/package.json @@ -5,7 +5,7 @@ "engines": { "node": ">=16" }, - "name": "@unique/opal-types", + "name": "@unique-nft/opal-testnet-types", "type": "module", "version": "1.0.0", "main": "index.js", --- a/js-packages/yarn.lock +++ b/js-packages/yarn.lock @@ -1242,22 +1242,21 @@ languageName: node linkType: hard -"@unique/opal-types@workspace:*, @unique/opal-types@workspace:types": +"@unique-nft/opal-testnet-types@workspace:*, @unique-nft/opal-testnet-types@workspace:types": version: 0.0.0-use.local - resolution: "@unique/opal-types@workspace:types" + resolution: "@unique-nft/opal-testnet-types@workspace:types" dependencies: "@polkadot/typegen": ^10.10.1 languageName: unknown linkType: soft -"@unique/playgrounds@workspace:*, @unique/playgrounds@workspace:playgrounds": +"@unique-nft/playgrounds@workspace:*, @unique-nft/playgrounds@workspace:playgrounds": version: 0.0.0-use.local - resolution: "@unique/playgrounds@workspace:playgrounds" + resolution: "@unique-nft/playgrounds@workspace:playgrounds" dependencies: "@polkadot/api": 10.10.1 "@polkadot/util": ^12.5.1 "@polkadot/util-crypto": ^12.5.1 - "@unique/opal-types": "workspace:*" languageName: unknown linkType: soft @@ -1267,6 +1266,17 @@ languageName: unknown linkType: soft +"@unique/test-utils@workspace:*, @unique/test-utils@workspace:test-utils": + version: 0.0.0-use.local + resolution: "@unique/test-utils@workspace:test-utils" + dependencies: + "@polkadot/api": 10.10.1 + "@polkadot/util": ^12.5.1 + "@polkadot/util-crypto": ^12.5.1 + "@unique-nft/opal-testnet-types": "workspace:*" + languageName: unknown + linkType: soft + "@unique/tests@workspace:tests": version: 0.0.0-use.local resolution: "@unique/tests@workspace:tests" @@ -6001,8 +6011,9 @@ "@types/node": ^20.8.10 "@typescript-eslint/eslint-plugin": ^6.10.0 "@typescript-eslint/parser": ^6.10.0 - "@unique/opal-types": "workspace:*" - "@unique/playgrounds": "workspace:*" + "@unique-nft/opal-testnet-types": "workspace:*" + "@unique-nft/playgrounds": "workspace:*" + "@unique/test-utils": "workspace:*" chai: ^4.3.10 chai-as-promised: ^7.1.1 chai-like: ^1.1.1 -- gitstuff