difftreelog
Merge pull request #996 from UniqueNetwork/tests/playgrounds-refactor
in: master
refactor(playgorunds): rearranging the code structure
10 files changed
.envrcdiffbeforeafterboth--- a/.envrc
+++ b/.envrc
@@ -30,7 +30,7 @@
fi
echo -e "${GREEN}Baedeker env updated${RESET}"
- nginx_id=$(docker compose -f .baedeker/.bdk-env/docker-compose.yml ps --format=json | jq -r '.[] | select(.Service == "nginx") | .ID' -e)
+ nginx_id=$(docker compose -f .baedeker/.bdk-env/docker-compose.yml ps --format=json | jq -s 'flatten' | jq -r '.[] | select(.Service == "nginx") | .ID' -e)
if ! [ $? -eq 0 ]; then
echo -e "${RED}Nginx container not found${RESET}"
exit 0
tests/src/governance/util.tsdiffbeforeafterboth--- a/tests/src/governance/util.ts
+++ b/tests/src/governance/util.ts
@@ -2,6 +2,7 @@
import {xxhashAsHex} from '@polkadot/util-crypto';
import {usingPlaygrounds, expect} from '../util';
import {UniqueHelper} from '../util/playgrounds/unique';
+import {DevUniqueHelper} from '../util/playgrounds/unique.dev';
export const democracyLaunchPeriod = 35;
export const democracyVotingPeriod = 35;
@@ -203,7 +204,7 @@
});
}
-export async function voteUnanimouslyInFellowship(helper: UniqueHelper, fellows: IKeyringPair[][], minRank: number, referendumIndex: number) {
+export async function voteUnanimouslyInFellowship(helper: DevUniqueHelper, fellows: IKeyringPair[][], minRank: number, referendumIndex: number) {
for(let rank = minRank; rank < fellowshipRankLimit; rank++) {
for(const member of fellows[rank]) {
await helper.fellowship.collective.vote(member, referendumIndex, true);
tests/src/maintenance.seqtest.tsdiffbeforeafterboth--- a/tests/src/maintenance.seqtest.ts
+++ b/tests/src/maintenance.seqtest.ts
@@ -192,19 +192,22 @@
const blocksToWait = 6;
// Scheduling works before the maintenance
- await nftBeforeMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM})
+ .nft.transferToken(bob, collection.collectionId, nftBeforeMM.tokenId, {Substrate: superuser.address});
+
await helper.wait.newBlocks(blocksToWait + 1);
expect(await nftBeforeMM.getOwner()).to.be.deep.equal({Substrate: superuser.address});
// Schedule a transaction that should occur *during* the maintenance
- await nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM})
+ .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address});
+
// Schedule a transaction that should occur *after* the maintenance
- await nftDuringMM.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM})
+ .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address});
+
await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []);
expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true;
@@ -214,16 +217,16 @@
expect(await nftDuringMM.getOwner()).to.be.deep.equal({Substrate: bob.address});
// Any attempts to schedule a tx during the MM should be rejected
- await expect(nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM})
- .transfer(bob, {Substrate: superuser.address}))
+ await expect(helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM})
+ .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address}))
.to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/);
await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []);
expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false;
// Scheduling works after the maintenance
- await nftAfterMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM})
+ .nft.transferToken(bob, collection.collectionId, nftAfterMM.tokenId, {Substrate: superuser.address});
await helper.wait.newBlocks(blocksToWait + 1);
tests/src/scheduler.seqtest.tsdiffbeforeafterboth--- a/tests/src/scheduler.seqtest.ts
+++ b/tests/src/scheduler.seqtest.ts
@@ -47,9 +47,8 @@
const token = await collection.mintToken(alice);
const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;
const blocksBeforeExecution = 4;
-
- await token.scheduleAfter(blocksBeforeExecution, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(blocksBeforeExecution, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;
expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
@@ -103,8 +102,8 @@
expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
await helper.scheduler.cancelScheduled(alice, scheduledId);
@@ -363,8 +362,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(bob, {Substrate: alice.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
await helper.getSudo().scheduler.cancelScheduled(superuser, scheduledId);
@@ -404,8 +403,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 6;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(bob, {Substrate: alice.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
const priority = 112;
@@ -583,8 +582,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId});
@@ -614,8 +613,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
await expect(helper.scheduler.cancelScheduled(bob, scheduledId))
@@ -655,8 +654,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(bob, {Substrate: alice.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
const priority = 112;
await expect(helper.scheduler.changePriority(alice, scheduledId, priority))
tests/src/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -216,43 +216,6 @@
},
}
-export interface IForeignAssetMetadata {
- name?: number | Uint8Array,
- 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 AcalaAssetMetadata {
- name: string,
- symbol: string,
- decimals: number,
- minimalBalance: bigint,
-}
-
-export interface DemocracyStandardAccountVote {
- balance: bigint,
- vote: {
- aye: boolean,
- conviction: number,
- },
-}
-
export interface DemocracySplitAccount {
aye: bigint,
nay: bigint,
tests/src/util/playgrounds/types.xcm.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/util/playgrounds/types.xcm.ts
@@ -0,0 +1,36 @@
+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,
+ },
+}
+
+export interface IForeignAssetMetadata {
+ name?: number | Uint8Array,
+ symbol?: string,
+ decimals?: number,
+ minimalBalance?: bigint,
+}
\ No newline at end of file
tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {stringToU8a} from '@polkadot/util';5import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';6import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper, PolkadexHelper} from './unique';7import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';8import * as defs from '../../interfaces/definitions';9import {IKeyringPair} from '@polkadot/types/types';10import {EventRecord} from '@polkadot/types/interfaces';11import {ICrossAccountId, IPovInfo, ITransactionResult, TSigner} from './types';12import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';13import {VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';15import {spawnSync} from 'child_process';1617export class SilentLogger {18 log(_msg: any, _level: any): void { }19 level = {20 ERROR: 'ERROR' as const,21 WARNING: 'WARNING' as const,22 INFO: 'INFO' as const,23 };24}2526export class SilentConsole {27 // TODO: Remove, this is temporary: Filter unneeded API output28 // (Jaco promised it will be removed in the next version)29 consoleErr: any;30 consoleLog: any;31 consoleWarn: any;3233 constructor() {34 this.consoleErr = console.error;35 this.consoleLog = console.log;36 this.consoleWarn = console.warn;37 }3839 enable() {40 const outFn = (printer: any) => (...args: any[]) => {41 for(const arg of args) {42 if(typeof arg !== 'string')43 continue;44 const skippedWarnings = ['1000:: Normal connection closure', 'Not decorating unknown runtime apis:', 'RPC methods not decorated:', 'Not decorating runtime apis', 'Bad input data provided to validate_transaction', 'account balance too low', '1006:: Abnormal Closure'];45 const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false);46 if(needToSkip || arg === 'Normal connection closure')47 return;48 }49 printer(...args);50 };5152 console.error = outFn(this.consoleErr.bind(console));53 console.log = outFn(this.consoleLog.bind(console));54 console.warn = outFn(this.consoleWarn.bind(console));55 }5657 disable() {58 console.error = this.consoleErr;59 console.log = this.consoleLog;60 console.warn = this.consoleWarn;61 }62}6364export interface IEventHelper {65 section(): string;6667 method(): string;6869 wrapEvent(data: any[]): any;70}7172// eslint-disable-next-line @typescript-eslint/naming-convention73function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {74 const helperClass = class implements IEventHelper {75 wrapEvent: (data: any[]) => any;76 _section: string;77 _method: string;7879 constructor() {80 this.wrapEvent = wrapEvent;81 this._section = section;82 this._method = method;83 }8485 section(): string {86 return this._section;87 }8889 method(): string {90 return this._method;91 }9293 filter(txres: ITransactionResult) {94 return txres.result.events.filter(e => e.event.section === section && e.event.method === method)95 .map(e => this.wrapEvent(e.event.data));96 }9798 find(txres: ITransactionResult) {99 const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);100 return e ? this.wrapEvent(e.event.data) : null;101 }102103 expect(txres: ITransactionResult) {104 const e = this.find(txres);105 if(e) {106 return e;107 } else {108 throw Error(`Expected event ${section}.${method}`);109 }110 }111 };112113 return helperClass;114}115116function eventJsonData<T = any>(data: any[], index: number) {117 return data[index].toJSON() as T;118}119120function eventHumanData(data: any[], index: number) {121 return data[index].toHuman();122}123124function eventData<T = any>(data: any[], index: number) {125 return data[index] as T;126}127128// eslint-disable-next-line @typescript-eslint/naming-convention129function EventSection(section: string) {130 return class Section {131 static section = section;132133 static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {134 const helperClass = EventHelper(Section.section, name, wrapEvent);135 return new helperClass();136 }137 };138}139140function schedulerSection(schedulerInstance: string) {141 return class extends EventSection(schedulerInstance) {142 static Dispatched = this.Method('Dispatched', data => ({143 task: eventJsonData(data, 0),144 id: eventHumanData(data, 1),145 result: data[2],146 }));147148 static PriorityChanged = this.Method('PriorityChanged', data => ({149 task: eventJsonData(data, 0),150 priority: eventJsonData(data, 1),151 }));152 };153}154155export class Event {156 static Democracy = class extends EventSection('democracy') {157 static Proposed = this.Method('Proposed', data => ({158 proposalIndex: eventJsonData<number>(data, 0),159 }));160161 static ExternalTabled = this.Method('ExternalTabled');162163 static Started = this.Method('Started', data => ({164 referendumIndex: eventJsonData<number>(data, 0),165 threshold: eventHumanData(data, 1),166 }));167168 static Voted = this.Method('Voted', data => ({169 voter: eventJsonData(data, 0),170 referendumIndex: eventJsonData<number>(data, 1),171 vote: eventJsonData(data, 2),172 }));173174 static Passed = this.Method('Passed', data => ({175 referendumIndex: eventJsonData<number>(data, 0),176 }));177178 static ProposalCanceled = this.Method('ProposalCanceled', data => ({179 propIndex: eventJsonData<number>(data, 0),180 }));181182 static Cancelled = this.Method('Cancelled', data => ({183 propIndex: eventJsonData<number>(data, 0),184 }));185186 static Vetoed = this.Method('Vetoed', data => ({187 who: eventHumanData(data, 0),188 proposalHash: eventHumanData(data, 1),189 until: eventJsonData<number>(data, 1),190 }));191 };192193 static Council = class extends EventSection('council') {194 static Proposed = this.Method('Proposed', data => ({195 account: eventHumanData(data, 0),196 proposalIndex: eventJsonData<number>(data, 1),197 proposalHash: eventHumanData(data, 2),198 threshold: eventJsonData<number>(data, 3),199 }));200 static Closed = this.Method('Closed', data => ({201 proposalHash: eventHumanData(data, 0),202 yes: eventJsonData<number>(data, 1),203 no: eventJsonData<number>(data, 2),204 }));205 static Executed = this.Method('Executed', data => ({206 proposalHash: eventHumanData(data, 0),207 }));208 };209210 static TechnicalCommittee = class extends EventSection('technicalCommittee') {211 static Proposed = this.Method('Proposed', data => ({212 account: eventHumanData(data, 0),213 proposalIndex: eventJsonData<number>(data, 1),214 proposalHash: eventHumanData(data, 2),215 threshold: eventJsonData<number>(data, 3),216 }));217 static Closed = this.Method('Closed', data => ({218 proposalHash: eventHumanData(data, 0),219 yes: eventJsonData<number>(data, 1),220 no: eventJsonData<number>(data, 2),221 }));222 static Approved = this.Method('Approved', data => ({223 proposalHash: eventHumanData(data, 0),224 }));225 static Executed = this.Method('Executed', data => ({226 proposalHash: eventHumanData(data, 0),227 result: eventHumanData(data, 1),228 }));229 };230231 static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {232 static Submitted = this.Method('Submitted', data => ({233 referendumIndex: eventJsonData<number>(data, 0),234 trackId: eventJsonData<number>(data, 1),235 proposal: eventJsonData(data, 2),236 }));237238 static Cancelled = this.Method('Cancelled', data => ({239 index: eventJsonData<number>(data, 0),240 tally: eventJsonData(data, 1),241 }));242 };243244 static UniqueScheduler = schedulerSection('uniqueScheduler');245 static Scheduler = schedulerSection('scheduler');246247 static XcmpQueue = class extends EventSection('xcmpQueue') {248 static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({249 messageHash: eventJsonData(data, 0),250 }));251252 static Success = this.Method('Success', data => ({253 messageHash: eventJsonData(data, 0),254 }));255256 static Fail = this.Method('Fail', data => ({257 messageHash: eventJsonData(data, 0),258 outcome: eventData<XcmV2TraitsError>(data, 1),259 }));260 };261}262263export class DevUniqueHelper extends UniqueHelper {264 /**265 * Arrange methods for tests266 */267 arrange: ArrangeGroup;268 wait: WaitGroup;269 admin: AdminGroup;270 session: SessionGroup;271 testUtils: TestUtilGroup;272273 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {274 options.helperBase = options.helperBase ?? DevUniqueHelper;275276 super(logger, options);277 this.arrange = new ArrangeGroup(this);278 this.wait = new WaitGroup(this);279 this.admin = new AdminGroup(this);280 this.testUtils = new TestUtilGroup(this);281 this.session = new SessionGroup(this);282 }283284 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {285 if(!wsEndpoint) throw new Error('wsEndpoint was not set');286 const wsProvider = new WsProvider(wsEndpoint);287 this.api = new ApiPromise({288 provider: wsProvider,289 signedExtensions: {290 ContractHelpers: {291 extrinsic: {},292 payload: {},293 },294 CheckMaintenance: {295 extrinsic: {},296 payload: {},297 },298 DisableIdentityCalls: {299 extrinsic: {},300 payload: {},301 },302 FakeTransactionFinalizer: {303 extrinsic: {},304 payload: {},305 },306 },307 rpc: {308 unique: defs.unique.rpc,309 appPromotion: defs.appPromotion.rpc,310 povinfo: defs.povinfo.rpc,311 eth: {312 feeHistory: {313 description: 'Dummy',314 params: [],315 type: 'u8',316 },317 maxPriorityFeePerGas: {318 description: 'Dummy',319 params: [],320 type: 'u8',321 },322 },323 },324 });325 await this.api.isReadyOrError;326 this.network = await UniqueHelper.detectNetwork(this.api);327 this.wsEndpoint = wsEndpoint;328 }329}330331export class DevRelayHelper extends RelayHelper {332 wait: WaitGroup;333334 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {335 options.helperBase = options.helperBase ?? DevRelayHelper;336337 super(logger, options);338 this.wait = new WaitGroup(this);339 }340}341342export class DevWestmintHelper extends WestmintHelper {343 wait: WaitGroup;344345 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {346 options.helperBase = options.helperBase ?? DevWestmintHelper;347348 super(logger, options);349 this.wait = new WaitGroup(this);350 }351}352353export class DevStatemineHelper extends DevWestmintHelper {}354355export class DevStatemintHelper extends DevWestmintHelper {}356357export class DevMoonbeamHelper extends MoonbeamHelper {358 account: MoonbeamAccountGroup;359 wait: WaitGroup;360 fastDemocracy: MoonbeamFastDemocracyGroup;361362 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {363 options.helperBase = options.helperBase ?? DevMoonbeamHelper;364 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';365366 super(logger, options);367 this.account = new MoonbeamAccountGroup(this);368 this.wait = new WaitGroup(this);369 this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);370 }371}372373export class DevMoonriverHelper extends DevMoonbeamHelper {374 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {375 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';376 super(logger, options);377 }378}379380export class DevAstarHelper extends AstarHelper {381 wait: WaitGroup;382383 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {384 options.helperBase = options.helperBase ?? DevAstarHelper;385386 super(logger, options);387 this.wait = new WaitGroup(this);388 }389}390391export class DevShidenHelper extends AstarHelper {392 wait: WaitGroup;393394 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {395 options.helperBase = options.helperBase ?? DevShidenHelper;396397 super(logger, options);398 this.wait = new WaitGroup(this);399 }400}401402export class DevAcalaHelper extends AcalaHelper {403 wait: WaitGroup;404405 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {406 options.helperBase = options.helperBase ?? DevAcalaHelper;407408 super(logger, options);409 this.wait = new WaitGroup(this);410 }411}412413export class DevPolkadexHelper extends PolkadexHelper {414 wait: WaitGroup;415 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {416 options.helperBase = options.helperBase ?? PolkadexHelper;417418 super(logger, options);419 this.wait = new WaitGroup(this);420 }421}422423export class DevKaruraHelper extends DevAcalaHelper {}424425export class ArrangeGroup {426 helper: DevUniqueHelper;427428 scheduledIdSlider = 0;429430 constructor(helper: DevUniqueHelper) {431 this.helper = helper;432 }433434 /**435 * Generates accounts with the specified UNQ token balance436 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.437 * @param donor donor account for balances438 * @returns array of newly created accounts439 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);440 */441 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {442 let nonce = await this.helper.chain.getNonce(donor.address);443 const wait = new WaitGroup(this.helper);444 const ss58Format = this.helper.chain.getChainProperties().ss58Format;445 const tokenNominal = this.helper.balance.getOneTokenNominal();446 const transactions = [];447 const accounts: IKeyringPair[] = [];448 for(const balance of balances) {449 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);450 accounts.push(recipient);451 if(balance !== 0n) {452 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);453 transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));454 nonce++;455 }456 }457458 await Promise.all(transactions).catch(_e => {});459460 //#region TODO remove this region, when nonce problem will be solved461 const checkBalances = async () => {462 let isSuccess = true;463 for(let i = 0; i < balances.length; i++) {464 const balance = await this.helper.balance.getSubstrate(accounts[i].address);465 if(balance !== balances[i] * tokenNominal) {466 isSuccess = false;467 break;468 }469 }470 return isSuccess;471 };472473 let accountsCreated = false;474 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;475 // checkBalances retry up to 5-50 blocks476 for(let index = 0; index < maxBlocksChecked; index++) {477 accountsCreated = await checkBalances();478 if(accountsCreated) break;479 await wait.newBlocks(1);480 }481482 if(!accountsCreated) throw Error('Accounts generation failed');483 //#endregion484485 return accounts;486 };487488 // TODO combine this method and createAccounts into one489 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {490 const createAsManyAsCan = async () => {491 let transactions: any = [];492 const accounts: IKeyringPair[] = [];493 let nonce = await this.helper.chain.getNonce(donor.address);494 const tokenNominal = this.helper.balance.getOneTokenNominal();495 const ss58Format = this.helper.chain.getChainProperties().ss58Format;496 for(let i = 0; i < accountsToCreate; i++) {497 if(i === 500) { // if there are too many accounts to create498 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled499 transactions = []; //500 nonce = await this.helper.chain.getNonce(donor.address); // update nonce501 }502 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);503 accounts.push(recipient);504 if(withBalance !== 0n) {505 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);506 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));507 nonce++;508 }509 }510511 const fullfilledAccounts = [];512 await Promise.allSettled(transactions);513 for(const account of accounts) {514 const accountBalance = await this.helper.balance.getSubstrate(account.address);515 if(accountBalance === withBalance * tokenNominal) {516 fullfilledAccounts.push(account);517 }518 }519 return fullfilledAccounts;520 };521522523 const crowd: IKeyringPair[] = [];524 // do up to 5 retries525 for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {526 const asManyAsCan = await createAsManyAsCan();527 crowd.push(...asManyAsCan);528 accountsToCreate -= asManyAsCan.length;529 }530531 if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);532533 return crowd;534 };535536 /**537 * Generates one account with zero balance538 * @returns the newly generated account539 * @example const account = await helper.arrange.createEmptyAccount();540 */541 createEmptyAccount = (): IKeyringPair => {542 const ss58Format = this.helper.chain.getChainProperties().ss58Format;543 return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);544 };545546 isDevNode = async () => {547 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();548 if(blockNumber == 0) {549 await this.helper.wait.newBlocks(1);550 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();551 }552 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);553 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);554 const findCreationDate = (block: any) => {555 const humanBlock = block.toHuman();556 let date;557 humanBlock.block.extrinsics.forEach((ext: any) => {558 if(ext.method.section === 'timestamp') {559 date = Number(ext.method.args.now.replaceAll(',', ''));560 }561 });562 return date;563 };564 const block1date = await findCreationDate(block1);565 const block2date = await findCreationDate(block2);566 if(block2date! - block1date! < 9000) return true;567 };568569 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {570 const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);571 let balance = await this.helper.balance.getSubstrate(address);572573 await promise();574575 balance -= await this.helper.balance.getSubstrate(address);576577 return balance;578 }579580 async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {581 const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);582583 const kvJson: {[key: string]: string} = {};584585 for(const kv of rawPovInfo.keyValues) {586 kvJson[kv.key.toHex()] = kv.value.toHex();587 }588589 const kvStr = JSON.stringify(kvJson);590591 const chainql = spawnSync(592 'chainql',593 [594 `--tla-code=data=${kvStr}`,595 '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,596 ],597 );598599 if(!chainql.stdout) {600 throw Error('unable to get an output from the `chainql`');601 }602603 return {604 proofSize: rawPovInfo.proofSize.toNumber(),605 compactProofSize: rawPovInfo.compactProofSize.toNumber(),606 compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),607 results: rawPovInfo.results,608 kv: JSON.parse(chainql.stdout.toString()),609 };610 }611612 calculatePalletAddress(palletId: any) {613 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));614 return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);615 }616617 makeScheduledIds(num: number): string[] {618 function makeId(slider: number) {619 const scheduledIdSize = 64;620 const hexId = slider.toString(16);621 const prefixSize = scheduledIdSize - hexId.length;622623 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;624625 return scheduledId;626 }627628 const ids = [];629 for(let i = 0; i < num; i++) {630 ids.push(makeId(this.scheduledIdSlider));631 this.scheduledIdSlider += 1;632 }633634 return ids;635 }636637 makeScheduledId(): string {638 return (this.makeScheduledIds(1))[0];639 }640641 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {642 const capture = new EventCapture(this.helper, eventSection, eventMethod);643 await capture.startCapture();644645 return capture;646 }647648 makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {649 return {650 V2: [651 {652 WithdrawAsset: [653 {654 id,655 fun: {656 Fungible: amount,657 },658 },659 ],660 },661 {662 BuyExecution: {663 fees: {664 id,665 fun: {666 Fungible: amount,667 },668 },669 weightLimit: 'Unlimited',670 },671 },672 {673 DepositAsset: {674 assets: {675 Wild: 'All',676 },677 maxAssets: 1,678 beneficiary: {679 parents: 0,680 interior: {681 X1: {682 AccountId32: {683 network: 'Any',684 id: beneficiary,685 },686 },687 },688 },689 },690 },691 ],692 };693 }694695 makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {696 return {697 V2: [698 {699 ReserveAssetDeposited: [700 {701 id,702 fun: {703 Fungible: amount,704 },705 },706 ],707 },708 {709 BuyExecution: {710 fees: {711 id,712 fun: {713 Fungible: amount,714 },715 },716 weightLimit: 'Unlimited',717 },718 },719 {720 DepositAsset: {721 assets: {722 Wild: 'All',723 },724 maxAssets: 1,725 beneficiary: {726 parents: 0,727 interior: {728 X1: {729 AccountId32: {730 network: 'Any',731 id: beneficiary,732 },733 },734 },735 },736 },737 },738 ],739 };740 }741}742743class MoonbeamAccountGroup {744 helper: MoonbeamHelper;745746 keyring: Keyring;747 _alithAccount: IKeyringPair;748 _baltatharAccount: IKeyringPair;749 _dorothyAccount: IKeyringPair;750751 constructor(helper: MoonbeamHelper) {752 this.helper = helper;753754 this.keyring = new Keyring({type: 'ethereum'});755 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';756 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';757 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';758759 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');760 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');761 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');762 }763764 alithAccount() {765 return this._alithAccount;766 }767768 baltatharAccount() {769 return this._baltatharAccount;770 }771772 dorothyAccount() {773 return this._dorothyAccount;774 }775776 create() {777 return this.keyring.addFromUri(mnemonicGenerate());778 }779}780781class MoonbeamFastDemocracyGroup {782 helper: DevMoonbeamHelper;783784 constructor(helper: DevMoonbeamHelper) {785 this.helper = helper;786 }787788 async executeProposal(proposalDesciption: string, encodedProposal: string) {789 const proposalHash = blake2AsHex(encodedProposal);790791 const alithAccount = this.helper.account.alithAccount();792 const baltatharAccount = this.helper.account.baltatharAccount();793 const dorothyAccount = this.helper.account.dorothyAccount();794795 const councilVotingThreshold = 2;796 const technicalCommitteeThreshold = 2;797 const fastTrackVotingPeriod = 3;798 const fastTrackDelayPeriod = 0;799800 console.log(`[democracy] executing '${proposalDesciption}' proposal`);801802 // >>> Propose external motion through council >>>803 console.log('\t* Propose external motion through council.......');804 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});805 const encodedMotion = externalMotion?.method.toHex() || '';806 const motionHash = blake2AsHex(encodedMotion);807 console.log('\t* Motion hash is %s', motionHash);808809 await this.helper.collective.council.propose(810 baltatharAccount,811 councilVotingThreshold,812 externalMotion,813 externalMotion.encodedLength,814 );815816 const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;817 await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);818 await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);819820 await this.helper.collective.council.close(821 dorothyAccount,822 motionHash,823 councilProposalIdx,824 {825 refTime: 1_000_000_000,826 proofSize: 1_000_000,827 },828 externalMotion.encodedLength,829 );830 console.log('\t* Propose external motion through council.......DONE');831 // <<< Propose external motion through council <<<832833 // >>> Fast track proposal through technical committee >>>834 console.log('\t* Fast track proposal through technical committee.......');835 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);836 const encodedFastTrack = fastTrack?.method.toHex() || '';837 const fastTrackHash = blake2AsHex(encodedFastTrack);838 console.log('\t* FastTrack hash is %s', fastTrackHash);839840 await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);841842 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;843 await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);844 await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);845846 await this.helper.collective.techCommittee.close(847 baltatharAccount,848 fastTrackHash,849 techProposalIdx,850 {851 refTime: 1_000_000_000,852 proofSize: 1_000_000,853 },854 fastTrack.encodedLength,855 );856 console.log('\t* Fast track proposal through technical committee.......DONE');857 // <<< Fast track proposal through technical committee <<<858859 const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);860 const referendumIndex = democracyStarted.referendumIndex;861862 // >>> Referendum voting >>>863 console.log(`\t* Referendum #${referendumIndex} voting.......`);864 await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {865 balance: 10_000_000_000_000_000_000n,866 vote: {aye: true, conviction: 1},867 });868 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);869 // <<< Referendum voting <<<870871 // Wait the proposal to pass872 await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);873874 await this.helper.wait.newBlocks(1);875876 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);877 }878}879880class WaitGroup {881 helper: ChainHelperBase;882883 constructor(helper: ChainHelperBase) {884 this.helper = helper;885 }886887 sleep(milliseconds: number) {888 return new Promise((resolve) => setTimeout(resolve, milliseconds));889 }890891 private async waitWithTimeout(promise: Promise<any>, timeout: number) {892 let isBlock = false;893 promise.then(() => isBlock = true).catch(() => isBlock = true);894 let totalTime = 0;895 const step = 100;896 while(!isBlock) {897 await this.sleep(step);898 totalTime += step;899 if(totalTime >= timeout) throw Error('Blocks production failed');900 }901 return promise;902 }903904 /**905 * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.906 * @param promise async operation to race against the timeout907 * @param timeoutMS time after which to time out908 * @param timeoutError error message to throw909 * @returns promise of the same type the operation had910 */911 withTimeout<T>(912 promise: Promise<T>,913 timeoutMS = 30000,914 timeoutError = 'The operation has timed out!',915 ): Promise<T> {916 const timeout = new Promise<never>((_, reject) => {917 setTimeout(() => {918 reject(new Error(timeoutError));919 }, timeoutMS);920 });921922 return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});923 }924925 /**926 * Wait for specified number of blocks927 * @param blocksCount number of blocks to wait928 * @returns929 */930 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {931 timeout = timeout ?? blocksCount * 60_000;932 // eslint-disable-next-line no-async-promise-executor933 const promise = new Promise<void>(async (resolve) => {934 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {935 if(blocksCount > 0) {936 blocksCount--;937 } else {938 unsubscribe();939 resolve();940 }941 });942 });943 await this.waitWithTimeout(promise, timeout);944 return promise;945 }946947 /**948 * Wait for the specified number of sessions to pass.949 * Only applicable if the Session pallet is turned on.950 * @param sessionCount number of sessions to wait951 * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks952 * @returns953 */954 async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {955 console.log(`Waiting for ${sessionCount} new session${sessionCount>1's'''}.`956 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');957958 const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;959 let currentSessionIndex = -1;960961 while(currentSessionIndex < expectedSessionIndex) {962 // eslint-disable-next-line no-async-promise-executor963 currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {964 await this.newBlocks(1);965 const res = await (this.helper as DevUniqueHelper).session.getIndex();966 resolve(res);967 }), blockTimeout, 'The chain has stopped producing blocks!');968 }969 }970971 async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {972 timeout = timeout ?? 30 * 60 * 1000;973 // eslint-disable-next-line no-async-promise-executor974 const promise = new Promise<void>(async (resolve) => {975 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {976 if(data.number.toNumber() >= blockNumber) {977 unsubscribe();978 resolve();979 }980 });981 });982 await this.waitWithTimeout(promise, timeout);983 return promise;984 }985986 async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {987 timeout = timeout ?? 30 * 60 * 1000;988 // eslint-disable-next-line no-async-promise-executor989 const promise = new Promise<void>(async (resolve) => {990 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {991 if(data.value.relayParentNumber.toNumber() >= blockNumber) {992 // @ts-ignore993 unsubscribe();994 resolve();995 }996 });997 });998 await this.waitWithTimeout(promise, timeout);999 return promise;1000 }10011002 noScheduledTasks() {1003 const api = this.helper.getApi();10041005 // eslint-disable-next-line no-async-promise-executor1006 const promise = new Promise<void>(async resolve => {1007 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1008 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();10091010 if(areThereScheduledTasks.length == 0) {1011 unsubscribe();1012 resolve();1013 }1014 });1015 });10161017 return promise;1018 }10191020 parachainBlockMultiplesOf(val: bigint) {1021 // eslint-disable-next-line no-async-promise-executor1022 const promise = new Promise<void>(async resolve => {1023 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1024 if(data.number.toBigInt() % val == 0n) {1025 console.log(`from waiter: ${data.number.toBigInt()}`);1026 unsubscribe();1027 resolve();1028 }1029 });1030 });1031 return promise;1032 }10331034 event<T extends IEventHelper>(1035 maxBlocksToWait: number,1036 eventHelper: T,1037 filter: (_: any) => boolean = () => true,1038 ): any {1039 // eslint-disable-next-line no-async-promise-executor1040 const promise = new Promise<T | null>(async (resolve) => {1041 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1042 const blockNumber = header.number.toHuman();1043 const blockHash = header.hash;1044 const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1045 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;10461047 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);10481049 const apiAt = await this.helper.getApi().at(blockHash);1050 const eventRecords = (await apiAt.query.system.events()) as any;10511052 const neededEvent = eventRecords.toArray()1053 .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1054 .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1055 .find(filter);10561057 if(neededEvent) {1058 unsubscribe();1059 resolve(neededEvent);1060 } else if(maxBlocksToWait > 0) {1061 maxBlocksToWait--;1062 } else {1063 this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);1064 unsubscribe();1065 resolve(null);1066 }1067 });1068 });1069 return promise;1070 }10711072 async expectEvent<T extends IEventHelper>(1073 maxBlocksToWait: number,1074 eventHelper: T,1075 filter: (e: any) => boolean = () => true,1076 ) {1077 const e = await this.event(maxBlocksToWait, eventHelper, filter);1078 if(e == null) {1079 throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1080 } else {1081 return e;1082 }1083 }1084}10851086class SessionGroup {1087 helper: ChainHelperBase;10881089 constructor(helper: ChainHelperBase) {1090 this.helper = helper;1091 }10921093 //todo:collator documentation1094 async getIndex(): Promise<number> {1095 return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1096 }10971098 newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1099 return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1100 }11011102 setOwnKeys(signer: TSigner, key: string) {1103 return this.helper.executeExtrinsic(1104 signer,1105 'api.tx.session.setKeys',1106 [key, '0x0'],1107 true,1108 );1109 }11101111 setOwnKeysFromAddress(signer: TSigner) {1112 return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1113 }1114}11151116class TestUtilGroup {1117 helper: DevUniqueHelper;11181119 constructor(helper: DevUniqueHelper) {1120 this.helper = helper;1121 }11221123 async enable() {1124 if(this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {1125 return;1126 }11271128 const signer = this.helper.util.fromSeed('//Alice');1129 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1130 }11311132 async setTestValue(signer: TSigner, testVal: number) {1133 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1134 }11351136 async incTestValue(signer: TSigner) {1137 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1138 }11391140 async setTestValueAndRollback(signer: TSigner, testVal: number) {1141 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1142 }11431144 async testValue(blockIdx?: number) {1145 const api = blockIdx1146 ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1147 : this.helper.getApi();11481149 return (await api.query.testUtils.testValue()).toJSON();1150 }11511152 async justTakeFee(signer: TSigner) {1153 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1154 }11551156 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1157 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1158 }1159}11601161class EventCapture {1162 helper: DevUniqueHelper;1163 eventSection: string;1164 eventMethod: string;1165 events: EventRecord[] = [];1166 unsubscribe: VoidFn | null = null;11671168 constructor(1169 helper: DevUniqueHelper,1170 eventSection: string,1171 eventMethod: string,1172 ) {1173 this.helper = helper;1174 this.eventSection = eventSection;1175 this.eventMethod = eventMethod;1176 }11771178 async startCapture() {1179 this.stopCapture();1180 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1181 const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);11821183 this.events.push(...newEvents);1184 })) as any;1185 }11861187 stopCapture() {1188 if(this.unsubscribe !== null) {1189 this.unsubscribe();1190 }1191 }11921193 extractCapturedEvents() {1194 return this.events;1195 }1196}11971198class AdminGroup {1199 helper: UniqueHelper;12001201 constructor(helper: UniqueHelper) {1202 this.helper = helper;1203 }12041205 async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> {1206 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1207 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1208 staker: e.event.data[0].toString(),1209 stake: e.event.data[1].toBigInt(),1210 payout: e.event.data[2].toBigInt(),1211 }));1212 }1213}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {stringToU8a} from '@polkadot/util';5import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';6import {UniqueHelper, ChainHelperBase, ChainHelperBaseConstructor, HelperGroup, UniqueHelperConstructor} from './unique';7import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';8import * as defs from '../../interfaces/definitions';9import {IKeyringPair} from '@polkadot/types/types';10import {EventRecord} from '@polkadot/types/interfaces';11import {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types';12import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';13import {SignerOptions, VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';15import {spawnSync} from 'child_process';16import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup} from './unique.xcm';17import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, ICollectiveGroup, IFellowshipGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance';1819export class SilentLogger {20 log(_msg: any, _level: any): void { }21 level = {22 ERROR: 'ERROR' as const,23 WARNING: 'WARNING' as const,24 INFO: 'INFO' as const,25 };26}2728export class SilentConsole {29 // TODO: Remove, this is temporary: Filter unneeded API output30 // (Jaco promised it will be removed in the next version)31 consoleErr: any;32 consoleLog: any;33 consoleWarn: any;3435 constructor() {36 this.consoleErr = console.error;37 this.consoleLog = console.log;38 this.consoleWarn = console.warn;39 }4041 enable() {42 const outFn = (printer: any) => (...args: any[]) => {43 for(const arg of args) {44 if(typeof arg !== 'string')45 continue;46 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'];47 const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false);48 if(needToSkip || arg === 'Normal connection closure')49 return;50 }51 printer(...args);52 };5354 console.error = outFn(this.consoleErr.bind(console));55 console.log = outFn(this.consoleLog.bind(console));56 console.warn = outFn(this.consoleWarn.bind(console));57 }5859 disable() {60 console.error = this.consoleErr;61 console.log = this.consoleLog;62 console.warn = this.consoleWarn;63 }64}6566export interface IEventHelper {67 section(): string;6869 method(): string;7071 wrapEvent(data: any[]): any;72}7374// eslint-disable-next-line @typescript-eslint/naming-convention75function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {76 const helperClass = class implements IEventHelper {77 wrapEvent: (data: any[]) => any;78 _section: string;79 _method: string;8081 constructor() {82 this.wrapEvent = wrapEvent;83 this._section = section;84 this._method = method;85 }8687 section(): string {88 return this._section;89 }9091 method(): string {92 return this._method;93 }9495 filter(txres: ITransactionResult) {96 return txres.result.events.filter(e => e.event.section === section && e.event.method === method)97 .map(e => this.wrapEvent(e.event.data));98 }99100 find(txres: ITransactionResult) {101 const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);102 return e ? this.wrapEvent(e.event.data) : null;103 }104105 expect(txres: ITransactionResult) {106 const e = this.find(txres);107 if(e) {108 return e;109 } else {110 throw Error(`Expected event ${section}.${method}`);111 }112 }113 };114115 return helperClass;116}117118function eventJsonData<T = any>(data: any[], index: number) {119 return data[index].toJSON() as T;120}121122function eventHumanData(data: any[], index: number) {123 return data[index].toHuman();124}125126function eventData<T = any>(data: any[], index: number) {127 return data[index] as T;128}129130// eslint-disable-next-line @typescript-eslint/naming-convention131function EventSection(section: string) {132 return class Section {133 static section = section;134135 static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {136 const helperClass = EventHelper(Section.section, name, wrapEvent);137 return new helperClass();138 }139 };140}141142function schedulerSection(schedulerInstance: string) {143 return class extends EventSection(schedulerInstance) {144 static Dispatched = this.Method('Dispatched', data => ({145 task: eventJsonData(data, 0),146 id: eventHumanData(data, 1),147 result: data[2],148 }));149150 static PriorityChanged = this.Method('PriorityChanged', data => ({151 task: eventJsonData(data, 0),152 priority: eventJsonData(data, 1),153 }));154 };155}156157export class Event {158 static Democracy = class extends EventSection('democracy') {159 static Proposed = this.Method('Proposed', data => ({160 proposalIndex: eventJsonData<number>(data, 0),161 }));162163 static ExternalTabled = this.Method('ExternalTabled');164165 static Started = this.Method('Started', data => ({166 referendumIndex: eventJsonData<number>(data, 0),167 threshold: eventHumanData(data, 1),168 }));169170 static Voted = this.Method('Voted', data => ({171 voter: eventJsonData(data, 0),172 referendumIndex: eventJsonData<number>(data, 1),173 vote: eventJsonData(data, 2),174 }));175176 static Passed = this.Method('Passed', data => ({177 referendumIndex: eventJsonData<number>(data, 0),178 }));179180 static ProposalCanceled = this.Method('ProposalCanceled', data => ({181 propIndex: eventJsonData<number>(data, 0),182 }));183184 static Cancelled = this.Method('Cancelled', data => ({185 propIndex: eventJsonData<number>(data, 0),186 }));187188 static Vetoed = this.Method('Vetoed', data => ({189 who: eventHumanData(data, 0),190 proposalHash: eventHumanData(data, 1),191 until: eventJsonData<number>(data, 1),192 }));193 };194195 static Council = class extends EventSection('council') {196 static Proposed = this.Method('Proposed', data => ({197 account: eventHumanData(data, 0),198 proposalIndex: eventJsonData<number>(data, 1),199 proposalHash: eventHumanData(data, 2),200 threshold: eventJsonData<number>(data, 3),201 }));202 static Closed = this.Method('Closed', data => ({203 proposalHash: eventHumanData(data, 0),204 yes: eventJsonData<number>(data, 1),205 no: eventJsonData<number>(data, 2),206 }));207 static Executed = this.Method('Executed', data => ({208 proposalHash: eventHumanData(data, 0),209 }));210 };211212 static TechnicalCommittee = class extends EventSection('technicalCommittee') {213 static Proposed = this.Method('Proposed', data => ({214 account: eventHumanData(data, 0),215 proposalIndex: eventJsonData<number>(data, 1),216 proposalHash: eventHumanData(data, 2),217 threshold: eventJsonData<number>(data, 3),218 }));219 static Closed = this.Method('Closed', data => ({220 proposalHash: eventHumanData(data, 0),221 yes: eventJsonData<number>(data, 1),222 no: eventJsonData<number>(data, 2),223 }));224 static Approved = this.Method('Approved', data => ({225 proposalHash: eventHumanData(data, 0),226 }));227 static Executed = this.Method('Executed', data => ({228 proposalHash: eventHumanData(data, 0),229 result: eventHumanData(data, 1),230 }));231 };232233 static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {234 static Submitted = this.Method('Submitted', data => ({235 referendumIndex: eventJsonData<number>(data, 0),236 trackId: eventJsonData<number>(data, 1),237 proposal: eventJsonData(data, 2),238 }));239240 static Cancelled = this.Method('Cancelled', data => ({241 index: eventJsonData<number>(data, 0),242 tally: eventJsonData(data, 1),243 }));244 };245246 static UniqueScheduler = schedulerSection('uniqueScheduler');247 static Scheduler = schedulerSection('scheduler');248249 static XcmpQueue = class extends EventSection('xcmpQueue') {250 static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({251 messageHash: eventJsonData(data, 0),252 }));253254 static Success = this.Method('Success', data => ({255 messageHash: eventJsonData(data, 0),256 }));257258 static Fail = this.Method('Fail', data => ({259 messageHash: eventJsonData(data, 0),260 outcome: eventData<XcmV2TraitsError>(data, 1),261 }));262 };263}264265// eslint-disable-next-line @typescript-eslint/naming-convention266export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {267 return class extends Base {268 constructor(...args: any[]) {269 super(...args);270 }271272 async executeExtrinsic(273 sender: IKeyringPair,274 extrinsic: string,275 params: any[],276 expectSuccess?: boolean,277 options: Partial<SignerOptions> | null = null,278 ): Promise<ITransactionResult> {279 const call = this.constructApiCall(extrinsic, params);280 const result = await super.executeExtrinsic(281 sender,282 'api.tx.sudo.sudo',283 [call],284 expectSuccess,285 options,286 );287288 if(result.status === 'Fail') return result;289290 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;291 if(data.isErr) {292 if(data.asErr.isModule) {293 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;294 const metaError = super.getApi()?.registry.findMetaError(error);295 throw new Error(`${metaError.section}.${metaError.name}`);296 } else if(data.asErr.isToken) {297 throw new Error(`Token: ${data.asErr.asToken}`);298 }299 // May be [object Object] in case of unhandled non-unit enum300 throw new Error(`Misc: ${data.asErr.toHuman()}`);301 }302 return result;303 }304 async executeExtrinsicUncheckedWeight(305 sender: IKeyringPair,306 extrinsic: string,307 params: any[],308 expectSuccess?: boolean,309 options: Partial<SignerOptions> | null = null,310 ): Promise<ITransactionResult> {311 const call = this.constructApiCall(extrinsic, params);312 const result = await super.executeExtrinsic(313 sender,314 'api.tx.sudo.sudoUncheckedWeight',315 [call, {refTime: 0, proofSize: 0}],316 expectSuccess,317 options,318 );319320 if(result.status === 'Fail') return result;321322 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;323 if(data.isErr) {324 if(data.asErr.isModule) {325 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;326 const metaError = super.getApi()?.registry.findMetaError(error);327 throw new Error(`${metaError.section}.${metaError.name}`);328 } else if(data.asErr.isToken) {329 throw new Error(`Token: ${data.asErr.asToken}`);330 }331 // May be [object Object] in case of unhandled non-unit enum332 throw new Error(`Misc: ${data.asErr.toHuman()}`);333 }334 return result;335 }336 };337}338339class SchedulerGroup extends HelperGroup<UniqueHelper> {340 constructor(helper: UniqueHelper) {341 super(helper);342 }343344 cancelScheduled(signer: TSigner, scheduledId: string) {345 return this.helper.executeExtrinsic(346 signer,347 'api.tx.scheduler.cancelNamed',348 [scheduledId],349 true,350 );351 }352353 changePriority(signer: TSigner, scheduledId: string, priority: number) {354 return this.helper.executeExtrinsic(355 signer,356 'api.tx.scheduler.changeNamedPriority',357 [scheduledId, priority],358 true,359 );360 }361362 scheduleAt<T extends DevUniqueHelper>(363 executionBlockNumber: number,364 options: ISchedulerOptions = {},365 ) {366 return this.schedule<T>('schedule', executionBlockNumber, options);367 }368369 scheduleAfter<T extends DevUniqueHelper>(370 blocksBeforeExecution: number,371 options: ISchedulerOptions = {},372 ) {373 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);374 }375376 schedule<T extends UniqueHelper>(377 scheduleFn: 'schedule' | 'scheduleAfter',378 blocksNum: number,379 options: ISchedulerOptions = {},380 ) {381 // eslint-disable-next-line @typescript-eslint/naming-convention382 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);383 return this.helper.clone(ScheduledHelperType, {384 scheduleFn,385 blocksNum,386 options,387 }) as T;388 }389}390391class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {392 //todo:collator documentation393 addInvulnerable(signer: TSigner, address: string) {394 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);395 }396397 removeInvulnerable(signer: TSigner, address: string) {398 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);399 }400401 async getInvulnerables(): Promise<string[]> {402 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());403 }404405 /** and also total max invulnerables */406 maxCollators(): number {407 return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);408 }409410 async getDesiredCollators(): Promise<number> {411 return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();412 }413414 setLicenseBond(signer: TSigner, amount: bigint) {415 return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);416 }417418 async getLicenseBond(): Promise<bigint> {419 return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();420 }421422 obtainLicense(signer: TSigner) {423 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);424 }425426 releaseLicense(signer: TSigner) {427 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);428 }429430 forceReleaseLicense(signer: TSigner, released: string) {431 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);432 }433434 async hasLicense(address: string): Promise<bigint> {435 return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();436 }437438 onboard(signer: TSigner) {439 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);440 }441442 offboard(signer: TSigner) {443 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);444 }445446 async getCandidates(): Promise<string[]> {447 return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());448 }449}450451export class DevUniqueHelper extends UniqueHelper {452 /**453 * Arrange methods for tests454 */455 arrange: ArrangeGroup;456 wait: WaitGroup;457 admin: AdminGroup;458 session: SessionGroup;459 testUtils: TestUtilGroup;460 foreignAssets: ForeignAssetsGroup;461 xcm: XcmGroup<UniqueHelper>;462 xTokens: XTokensGroup<UniqueHelper>;463 tokens: TokensGroup<UniqueHelper>;464 scheduler: SchedulerGroup;465 collatorSelection: CollatorSelectionGroup;466 council: ICollectiveGroup;467 technicalCommittee: ICollectiveGroup;468 fellowship: IFellowshipGroup;469 democracy: DemocracyGroup;470471 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {472 options.helperBase = options.helperBase ?? DevUniqueHelper;473474 super(logger, options);475 this.arrange = new ArrangeGroup(this);476 this.wait = new WaitGroup(this);477 this.admin = new AdminGroup(this);478 this.testUtils = new TestUtilGroup(this);479 this.session = new SessionGroup(this);480 this.foreignAssets = new ForeignAssetsGroup(this);481 this.xcm = new XcmGroup(this, 'polkadotXcm');482 this.xTokens = new XTokensGroup(this);483 this.tokens = new TokensGroup(this);484 this.scheduler = new SchedulerGroup(this);485 this.collatorSelection = new CollatorSelectionGroup(this);486 this.council = {487 collective: new CollectiveGroup(this, 'council'),488 membership: new CollectiveMembershipGroup(this, 'councilMembership'),489 };490 this.technicalCommittee = {491 collective: new CollectiveGroup(this, 'technicalCommittee'),492 membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),493 };494 this.fellowship = {495 collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),496 referenda: new ReferendaGroup(this, 'fellowshipReferenda'),497 };498 this.democracy = new DemocracyGroup(this);499 }500501 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {502 if(!wsEndpoint) throw new Error('wsEndpoint was not set');503 const wsProvider = new WsProvider(wsEndpoint);504 this.api = new ApiPromise({505 provider: wsProvider,506 signedExtensions: {507 ContractHelpers: {508 extrinsic: {},509 payload: {},510 },511 CheckMaintenance: {512 extrinsic: {},513 payload: {},514 },515 DisableIdentityCalls: {516 extrinsic: {},517 payload: {},518 },519 FakeTransactionFinalizer: {520 extrinsic: {},521 payload: {},522 },523 },524 rpc: {525 unique: defs.unique.rpc,526 appPromotion: defs.appPromotion.rpc,527 povinfo: defs.povinfo.rpc,528 eth: {529 feeHistory: {530 description: 'Dummy',531 params: [],532 type: 'u8',533 },534 maxPriorityFeePerGas: {535 description: 'Dummy',536 params: [],537 type: 'u8',538 },539 },540 },541 });542 await this.api.isReadyOrError;543 this.network = await UniqueHelper.detectNetwork(this.api);544 this.wsEndpoint = wsEndpoint;545 }546 getSudo<T extends DevUniqueHelper>() {547 // eslint-disable-next-line @typescript-eslint/naming-convention548 const SudoHelperType = SudoHelper(this.helperBase);549 return this.clone(SudoHelperType) as T;550 }551}552553export class DevRelayHelper extends RelayHelper {554 wait: WaitGroup;555556 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {557 options.helperBase = options.helperBase ?? DevRelayHelper;558559 super(logger, options);560 this.wait = new WaitGroup(this);561 }562}563564export class DevWestmintHelper extends WestmintHelper {565 wait: WaitGroup;566567 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {568 options.helperBase = options.helperBase ?? DevWestmintHelper;569570 super(logger, options);571 this.wait = new WaitGroup(this);572 }573}574575export class DevStatemineHelper extends DevWestmintHelper {}576577export class DevStatemintHelper extends DevWestmintHelper {}578579export class DevMoonbeamHelper extends MoonbeamHelper {580 account: MoonbeamAccountGroup;581 wait: WaitGroup;582 fastDemocracy: MoonbeamFastDemocracyGroup;583584 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {585 options.helperBase = options.helperBase ?? DevMoonbeamHelper;586 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';587588 super(logger, options);589 this.account = new MoonbeamAccountGroup(this);590 this.wait = new WaitGroup(this);591 this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);592 }593}594595export class DevMoonriverHelper extends DevMoonbeamHelper {596 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {597 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';598 super(logger, options);599 }600}601602export class DevAstarHelper extends AstarHelper {603 wait: WaitGroup;604605 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {606 options.helperBase = options.helperBase ?? DevAstarHelper;607608 super(logger, options);609 this.wait = new WaitGroup(this);610 }611612 getSudo<T extends AstarHelper>() {613 // eslint-disable-next-line @typescript-eslint/naming-convention614 const SudoHelperType = SudoHelper(this.helperBase);615 return this.clone(SudoHelperType) as T;616 }617}618619export class DevShidenHelper extends DevAstarHelper { }620621export class DevAcalaHelper extends AcalaHelper {622 wait: WaitGroup;623624 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {625 options.helperBase = options.helperBase ?? DevAcalaHelper;626627 super(logger, options);628 this.wait = new WaitGroup(this);629 }630 getSudo<T extends AcalaHelper>() {631 // eslint-disable-next-line @typescript-eslint/naming-convention632 const SudoHelperType = SudoHelper(this.helperBase);633 return this.clone(SudoHelperType) as T;634 }635}636637export class DevPolkadexHelper extends PolkadexHelper {638 wait: WaitGroup;639 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {640 options.helperBase = options.helperBase ?? PolkadexHelper;641642 super(logger, options);643 this.wait = new WaitGroup(this);644 }645646 getSudo<T extends PolkadexHelper>() {647 // eslint-disable-next-line @typescript-eslint/naming-convention648 const SudoHelperType = SudoHelper(this.helperBase);649 return this.clone(SudoHelperType) as T;650 }651}652653export class DevKaruraHelper extends DevAcalaHelper {}654655export class ArrangeGroup {656 helper: DevUniqueHelper;657658 scheduledIdSlider = 0;659660 constructor(helper: DevUniqueHelper) {661 this.helper = helper;662 }663664 /**665 * Generates accounts with the specified UNQ token balance666 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.667 * @param donor donor account for balances668 * @returns array of newly created accounts669 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);670 */671 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {672 let nonce = await this.helper.chain.getNonce(donor.address);673 const wait = new WaitGroup(this.helper);674 const ss58Format = this.helper.chain.getChainProperties().ss58Format;675 const tokenNominal = this.helper.balance.getOneTokenNominal();676 const transactions = [];677 const accounts: IKeyringPair[] = [];678 for(const balance of balances) {679 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);680 accounts.push(recipient);681 if(balance !== 0n) {682 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);683 transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));684 nonce++;685 }686 }687688 await Promise.all(transactions).catch(_e => {});689690 //#region TODO remove this region, when nonce problem will be solved691 const checkBalances = async () => {692 let isSuccess = true;693 for(let i = 0; i < balances.length; i++) {694 const balance = await this.helper.balance.getSubstrate(accounts[i].address);695 if(balance !== balances[i] * tokenNominal) {696 isSuccess = false;697 break;698 }699 }700 return isSuccess;701 };702703 let accountsCreated = false;704 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;705 // checkBalances retry up to 5-50 blocks706 for(let index = 0; index < maxBlocksChecked; index++) {707 accountsCreated = await checkBalances();708 if(accountsCreated) break;709 await wait.newBlocks(1);710 }711712 if(!accountsCreated) throw Error('Accounts generation failed');713 //#endregion714715 return accounts;716 };717718 // TODO combine this method and createAccounts into one719 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {720 const createAsManyAsCan = async () => {721 let transactions: any = [];722 const accounts: IKeyringPair[] = [];723 let nonce = await this.helper.chain.getNonce(donor.address);724 const tokenNominal = this.helper.balance.getOneTokenNominal();725 const ss58Format = this.helper.chain.getChainProperties().ss58Format;726 for(let i = 0; i < accountsToCreate; i++) {727 if(i === 500) { // if there are too many accounts to create728 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled729 transactions = []; //730 nonce = await this.helper.chain.getNonce(donor.address); // update nonce731 }732 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);733 accounts.push(recipient);734 if(withBalance !== 0n) {735 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);736 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));737 nonce++;738 }739 }740741 const fullfilledAccounts = [];742 await Promise.allSettled(transactions);743 for(const account of accounts) {744 const accountBalance = await this.helper.balance.getSubstrate(account.address);745 if(accountBalance === withBalance * tokenNominal) {746 fullfilledAccounts.push(account);747 }748 }749 return fullfilledAccounts;750 };751752753 const crowd: IKeyringPair[] = [];754 // do up to 5 retries755 for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {756 const asManyAsCan = await createAsManyAsCan();757 crowd.push(...asManyAsCan);758 accountsToCreate -= asManyAsCan.length;759 }760761 if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);762763 return crowd;764 };765766 /**767 * Generates one account with zero balance768 * @returns the newly generated account769 * @example const account = await helper.arrange.createEmptyAccount();770 */771 createEmptyAccount = (): IKeyringPair => {772 const ss58Format = this.helper.chain.getChainProperties().ss58Format;773 return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);774 };775776 isDevNode = async () => {777 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();778 if(blockNumber == 0) {779 await this.helper.wait.newBlocks(1);780 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();781 }782 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);783 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);784 const findCreationDate = (block: any) => {785 const humanBlock = block.toHuman();786 let date;787 humanBlock.block.extrinsics.forEach((ext: any) => {788 if(ext.method.section === 'timestamp') {789 date = Number(ext.method.args.now.replaceAll(',', ''));790 }791 });792 return date;793 };794 const block1date = await findCreationDate(block1);795 const block2date = await findCreationDate(block2);796 if(block2date! - block1date! < 9000) return true;797 };798799 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {800 const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);801 let balance = await this.helper.balance.getSubstrate(address);802803 await promise();804805 balance -= await this.helper.balance.getSubstrate(address);806807 return balance;808 }809810 async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {811 const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);812813 const kvJson: {[key: string]: string} = {};814815 for(const kv of rawPovInfo.keyValues) {816 kvJson[kv.key.toHex()] = kv.value.toHex();817 }818819 const kvStr = JSON.stringify(kvJson);820821 const chainql = spawnSync(822 'chainql',823 [824 `--tla-code=data=${kvStr}`,825 '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,826 ],827 );828829 if(!chainql.stdout) {830 throw Error('unable to get an output from the `chainql`');831 }832833 return {834 proofSize: rawPovInfo.proofSize.toNumber(),835 compactProofSize: rawPovInfo.compactProofSize.toNumber(),836 compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),837 results: rawPovInfo.results,838 kv: JSON.parse(chainql.stdout.toString()),839 };840 }841842 calculatePalletAddress(palletId: any) {843 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));844 return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);845 }846847 makeScheduledIds(num: number): string[] {848 function makeId(slider: number) {849 const scheduledIdSize = 64;850 const hexId = slider.toString(16);851 const prefixSize = scheduledIdSize - hexId.length;852853 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;854855 return scheduledId;856 }857858 const ids = [];859 for(let i = 0; i < num; i++) {860 ids.push(makeId(this.scheduledIdSlider));861 this.scheduledIdSlider += 1;862 }863864 return ids;865 }866867 makeScheduledId(): string {868 return (this.makeScheduledIds(1))[0];869 }870871 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {872 const capture = new EventCapture(this.helper, eventSection, eventMethod);873 await capture.startCapture();874875 return capture;876 }877878 makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {879 return {880 V2: [881 {882 WithdrawAsset: [883 {884 id,885 fun: {886 Fungible: amount,887 },888 },889 ],890 },891 {892 BuyExecution: {893 fees: {894 id,895 fun: {896 Fungible: amount,897 },898 },899 weightLimit: 'Unlimited',900 },901 },902 {903 DepositAsset: {904 assets: {905 Wild: 'All',906 },907 maxAssets: 1,908 beneficiary: {909 parents: 0,910 interior: {911 X1: {912 AccountId32: {913 network: 'Any',914 id: beneficiary,915 },916 },917 },918 },919 },920 },921 ],922 };923 }924925 makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {926 return {927 V2: [928 {929 ReserveAssetDeposited: [930 {931 id,932 fun: {933 Fungible: amount,934 },935 },936 ],937 },938 {939 BuyExecution: {940 fees: {941 id,942 fun: {943 Fungible: amount,944 },945 },946 weightLimit: 'Unlimited',947 },948 },949 {950 DepositAsset: {951 assets: {952 Wild: 'All',953 },954 maxAssets: 1,955 beneficiary: {956 parents: 0,957 interior: {958 X1: {959 AccountId32: {960 network: 'Any',961 id: beneficiary,962 },963 },964 },965 },966 },967 },968 ],969 };970 }971}972973class MoonbeamAccountGroup {974 helper: MoonbeamHelper;975976 keyring: Keyring;977 _alithAccount: IKeyringPair;978 _baltatharAccount: IKeyringPair;979 _dorothyAccount: IKeyringPair;980981 constructor(helper: MoonbeamHelper) {982 this.helper = helper;983984 this.keyring = new Keyring({type: 'ethereum'});985 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';986 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';987 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';988989 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');990 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');991 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');992 }993994 alithAccount() {995 return this._alithAccount;996 }997998 baltatharAccount() {999 return this._baltatharAccount;1000 }10011002 dorothyAccount() {1003 return this._dorothyAccount;1004 }10051006 create() {1007 return this.keyring.addFromUri(mnemonicGenerate());1008 }1009}10101011class MoonbeamFastDemocracyGroup {1012 helper: DevMoonbeamHelper;10131014 constructor(helper: DevMoonbeamHelper) {1015 this.helper = helper;1016 }10171018 async executeProposal(proposalDesciption: string, encodedProposal: string) {1019 const proposalHash = blake2AsHex(encodedProposal);10201021 const alithAccount = this.helper.account.alithAccount();1022 const baltatharAccount = this.helper.account.baltatharAccount();1023 const dorothyAccount = this.helper.account.dorothyAccount();10241025 const councilVotingThreshold = 2;1026 const technicalCommitteeThreshold = 2;1027 const fastTrackVotingPeriod = 3;1028 const fastTrackDelayPeriod = 0;10291030 console.log(`[democracy] executing '${proposalDesciption}' proposal`);10311032 // >>> Propose external motion through council >>>1033 console.log('\t* Propose external motion through council.......');1034 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});1035 const encodedMotion = externalMotion?.method.toHex() || '';1036 const motionHash = blake2AsHex(encodedMotion);1037 console.log('\t* Motion hash is %s', motionHash);10381039 await this.helper.collective.council.propose(1040 baltatharAccount,1041 councilVotingThreshold,1042 externalMotion,1043 externalMotion.encodedLength,1044 );10451046 const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;1047 await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);1048 await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);10491050 await this.helper.collective.council.close(1051 dorothyAccount,1052 motionHash,1053 councilProposalIdx,1054 {1055 refTime: 1_000_000_000,1056 proofSize: 1_000_000,1057 },1058 externalMotion.encodedLength,1059 );1060 console.log('\t* Propose external motion through council.......DONE');1061 // <<< Propose external motion through council <<<10621063 // >>> Fast track proposal through technical committee >>>1064 console.log('\t* Fast track proposal through technical committee.......');1065 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);1066 const encodedFastTrack = fastTrack?.method.toHex() || '';1067 const fastTrackHash = blake2AsHex(encodedFastTrack);1068 console.log('\t* FastTrack hash is %s', fastTrackHash);10691070 await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);10711072 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;1073 await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);1074 await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);10751076 await this.helper.collective.techCommittee.close(1077 baltatharAccount,1078 fastTrackHash,1079 techProposalIdx,1080 {1081 refTime: 1_000_000_000,1082 proofSize: 1_000_000,1083 },1084 fastTrack.encodedLength,1085 );1086 console.log('\t* Fast track proposal through technical committee.......DONE');1087 // <<< Fast track proposal through technical committee <<<10881089 const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);1090 const referendumIndex = democracyStarted.referendumIndex;10911092 // >>> Referendum voting >>>1093 console.log(`\t* Referendum #${referendumIndex} voting.......`);1094 await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {1095 balance: 10_000_000_000_000_000_000n,1096 vote: {aye: true, conviction: 1},1097 });1098 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);1099 // <<< Referendum voting <<<11001101 // Wait the proposal to pass1102 await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);11031104 await this.helper.wait.newBlocks(1);11051106 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);1107 }1108}11091110class WaitGroup {1111 helper: ChainHelperBase;11121113 constructor(helper: ChainHelperBase) {1114 this.helper = helper;1115 }11161117 sleep(milliseconds: number) {1118 return new Promise((resolve) => setTimeout(resolve, milliseconds));1119 }11201121 private async waitWithTimeout(promise: Promise<any>, timeout: number) {1122 let isBlock = false;1123 promise.then(() => isBlock = true).catch(() => isBlock = true);1124 let totalTime = 0;1125 const step = 100;1126 while(!isBlock) {1127 await this.sleep(step);1128 totalTime += step;1129 if(totalTime >= timeout) throw Error('Blocks production failed');1130 }1131 return promise;1132 }11331134 /**1135 * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.1136 * @param promise async operation to race against the timeout1137 * @param timeoutMS time after which to time out1138 * @param timeoutError error message to throw1139 * @returns promise of the same type the operation had1140 */1141 withTimeout<T>(1142 promise: Promise<T>,1143 timeoutMS = 30000,1144 timeoutError = 'The operation has timed out!',1145 ): Promise<T> {1146 const timeout = new Promise<never>((_, reject) => {1147 setTimeout(() => {1148 reject(new Error(timeoutError));1149 }, timeoutMS);1150 });11511152 return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});1153 }11541155 /**1156 * Wait for specified number of blocks1157 * @param blocksCount number of blocks to wait1158 * @returns1159 */1160 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {1161 timeout = timeout ?? blocksCount * 60_000;1162 // eslint-disable-next-line no-async-promise-executor1163 const promise = new Promise<void>(async (resolve) => {1164 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {1165 if(blocksCount > 0) {1166 blocksCount--;1167 } else {1168 unsubscribe();1169 resolve();1170 }1171 });1172 });1173 await this.waitWithTimeout(promise, timeout);1174 return promise;1175 }11761177 /**1178 * Wait for the specified number of sessions to pass.1179 * Only applicable if the Session pallet is turned on.1180 * @param sessionCount number of sessions to wait1181 * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks1182 * @returns1183 */1184 async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {1185 console.log(`Waiting for ${sessionCount} new session${sessionCount>1's'''}.`1186 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');11871188 const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;1189 let currentSessionIndex = -1;11901191 while(currentSessionIndex < expectedSessionIndex) {1192 // eslint-disable-next-line no-async-promise-executor1193 currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {1194 await this.newBlocks(1);1195 const res = await (this.helper as DevUniqueHelper).session.getIndex();1196 resolve(res);1197 }), blockTimeout, 'The chain has stopped producing blocks!');1198 }1199 }12001201 async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {1202 timeout = timeout ?? 30 * 60 * 1000;1203 // eslint-disable-next-line no-async-promise-executor1204 const promise = new Promise<void>(async (resolve) => {1205 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1206 if(data.number.toNumber() >= blockNumber) {1207 unsubscribe();1208 resolve();1209 }1210 });1211 });1212 await this.waitWithTimeout(promise, timeout);1213 return promise;1214 }12151216 async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {1217 timeout = timeout ?? 30 * 60 * 1000;1218 // eslint-disable-next-line no-async-promise-executor1219 const promise = new Promise<void>(async (resolve) => {1220 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {1221 if(data.value.relayParentNumber.toNumber() >= blockNumber) {1222 // @ts-ignore1223 unsubscribe();1224 resolve();1225 }1226 });1227 });1228 await this.waitWithTimeout(promise, timeout);1229 return promise;1230 }12311232 noScheduledTasks() {1233 const api = this.helper.getApi();12341235 // eslint-disable-next-line no-async-promise-executor1236 const promise = new Promise<void>(async resolve => {1237 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1238 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();12391240 if(areThereScheduledTasks.length == 0) {1241 unsubscribe();1242 resolve();1243 }1244 });1245 });12461247 return promise;1248 }12491250 parachainBlockMultiplesOf(val: bigint) {1251 // eslint-disable-next-line no-async-promise-executor1252 const promise = new Promise<void>(async resolve => {1253 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1254 if(data.number.toBigInt() % val == 0n) {1255 console.log(`from waiter: ${data.number.toBigInt()}`);1256 unsubscribe();1257 resolve();1258 }1259 });1260 });1261 return promise;1262 }12631264 event<T extends IEventHelper>(1265 maxBlocksToWait: number,1266 eventHelper: T,1267 filter: (_: any) => boolean = () => true,1268 ): any {1269 // eslint-disable-next-line no-async-promise-executor1270 const promise = new Promise<T | null>(async (resolve) => {1271 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1272 const blockNumber = header.number.toHuman();1273 const blockHash = header.hash;1274 const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1275 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;12761277 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);12781279 const apiAt = await this.helper.getApi().at(blockHash);1280 const eventRecords = (await apiAt.query.system.events()) as any;12811282 const neededEvent = eventRecords.toArray()1283 .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1284 .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1285 .find(filter);12861287 if(neededEvent) {1288 unsubscribe();1289 resolve(neededEvent);1290 } else if(maxBlocksToWait > 0) {1291 maxBlocksToWait--;1292 } else {1293 this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);1294 unsubscribe();1295 resolve(null);1296 }1297 });1298 });1299 return promise;1300 }13011302 async expectEvent<T extends IEventHelper>(1303 maxBlocksToWait: number,1304 eventHelper: T,1305 filter: (e: any) => boolean = () => true,1306 ) {1307 const e = await this.event(maxBlocksToWait, eventHelper, filter);1308 if(e == null) {1309 throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1310 } else {1311 return e;1312 }1313 }1314}13151316class SessionGroup {1317 helper: ChainHelperBase;13181319 constructor(helper: ChainHelperBase) {1320 this.helper = helper;1321 }13221323 //todo:collator documentation1324 async getIndex(): Promise<number> {1325 return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1326 }13271328 newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1329 return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1330 }13311332 setOwnKeys(signer: TSigner, key: string) {1333 return this.helper.executeExtrinsic(1334 signer,1335 'api.tx.session.setKeys',1336 [key, '0x0'],1337 true,1338 );1339 }13401341 setOwnKeysFromAddress(signer: TSigner) {1342 return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1343 }1344}13451346class TestUtilGroup {1347 helper: DevUniqueHelper;13481349 constructor(helper: DevUniqueHelper) {1350 this.helper = helper;1351 }13521353 async enable() {1354 if(this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {1355 return;1356 }13571358 const signer = this.helper.util.fromSeed('//Alice');1359 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1360 }13611362 async setTestValue(signer: TSigner, testVal: number) {1363 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1364 }13651366 async incTestValue(signer: TSigner) {1367 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1368 }13691370 async setTestValueAndRollback(signer: TSigner, testVal: number) {1371 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1372 }13731374 async testValue(blockIdx?: number) {1375 const api = blockIdx1376 ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1377 : this.helper.getApi();13781379 return (await api.query.testUtils.testValue()).toJSON();1380 }13811382 async justTakeFee(signer: TSigner) {1383 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1384 }13851386 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1387 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1388 }1389}13901391class EventCapture {1392 helper: DevUniqueHelper;1393 eventSection: string;1394 eventMethod: string;1395 events: EventRecord[] = [];1396 unsubscribe: VoidFn | null = null;13971398 constructor(1399 helper: DevUniqueHelper,1400 eventSection: string,1401 eventMethod: string,1402 ) {1403 this.helper = helper;1404 this.eventSection = eventSection;1405 this.eventMethod = eventMethod;1406 }14071408 async startCapture() {1409 this.stopCapture();1410 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1411 const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);14121413 this.events.push(...newEvents);1414 })) as any;1415 }14161417 stopCapture() {1418 if(this.unsubscribe !== null) {1419 this.unsubscribe();1420 }1421 }14221423 extractCapturedEvents() {1424 return this.events;1425 }1426}14271428class AdminGroup {1429 helper: UniqueHelper;14301431 constructor(helper: UniqueHelper) {1432 this.helper = helper;1433 }14341435 async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> {1436 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1437 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1438 staker: e.event.data[0].toString(),1439 stake: e.event.data[1].toBigInt(),1440 payout: e.event.data[2].toBigInt(),1441 }));1442 }1443}14441445// eslint-disable-next-line @typescript-eslint/naming-convention1446function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {1447 return class extends Base {1448 scheduleFn: 'schedule' | 'scheduleAfter';1449 blocksNum: number;1450 options: ISchedulerOptions;14511452 constructor(...args: any[]) {1453 const logger = args[0] as ILogger;1454 const options = args[1] as {1455 scheduleFn: 'schedule' | 'scheduleAfter',1456 blocksNum: number,1457 options: ISchedulerOptions1458 };14591460 super(logger);14611462 this.scheduleFn = options.scheduleFn;1463 this.blocksNum = options.blocksNum;1464 this.options = options.options;1465 }14661467 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {1468 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);14691470 const mandatorySchedArgs = [1471 this.blocksNum,1472 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,1473 this.options.priority ?? null,1474 scheduledTx,1475 ];14761477 let schedArgs;1478 let scheduleFn;14791480 if(this.options.scheduledId) {1481 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];14821483 if(this.scheduleFn == 'schedule') {1484 scheduleFn = 'scheduleNamed';1485 } else if(this.scheduleFn == 'scheduleAfter') {1486 scheduleFn = 'scheduleNamedAfter';1487 }1488 } else {1489 schedArgs = mandatorySchedArgs;1490 scheduleFn = this.scheduleFn;1491 }14921493 const extrinsic = 'api.tx.scheduler.' + scheduleFn;14941495 return super.executeExtrinsic(1496 sender,1497 extrinsic as any,1498 schedArgs,1499 expectSuccess,1500 );1501 }1502 };1503}tests/src/util/playgrounds/unique.governance.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/util/playgrounds/unique.governance.ts
@@ -0,0 +1,531 @@
+import {blake2AsHex} from '@polkadot/util-crypto';
+import {PalletDemocracyConviction} from '@polkadot/types/lookup';
+import {IPhasicEvent, TSigner} from './types';
+import {HelperGroup, UniqueHelper} from './unique';
+
+export class CollectiveGroup extends HelperGroup<UniqueHelper> {
+ /**
+ * 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<UniqueHelper> {
+ /**
+ * 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<UniqueHelper> {
+ /**
+ * 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<UniqueHelper> {
+ /**
+ * 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<UniqueHelper> {
+ // 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);
+ }*/
+}
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -39,16 +39,11 @@
TSigner,
TSubstrateAccount,
TNetworks,
- IForeignAssetMetadata,
- AcalaAssetMetadata,
- MoonbeamAssetInfo,
- DemocracyStandardAccountVote,
IEthCrossAccountId,
- IPhasicEvent,
} from './types';
import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';
import type {Vec} from '@polkadot/types-codec';
-import {FrameSystemEventRecord, PalletDemocracyConviction} from '@polkadot/types/lookup';
+import {FrameSystemEventRecord} from '@polkadot/types/lookup';
export class CrossAccountId {
Substrate!: TSubstrateAccount;
@@ -818,7 +813,7 @@
}
-class HelperGroup<T extends ChainHelperBase> {
+export class HelperGroup<T extends ChainHelperBase> {
helper: T;
constructor(uniqueHelper: T) {
@@ -2373,7 +2368,7 @@
}
}
-class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+export class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
/**
* Get substrate address balance
* @param address substrate address
@@ -2440,7 +2435,7 @@
}
}
-class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+export class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
/**
* Get ethereum address balance
* @param address ethereum address
@@ -2760,6 +2755,7 @@
}
}
+
class StakingGroup extends HelperGroup<UniqueHelper> {
/**
* Stake tokens for App Promotion
@@ -2867,645 +2863,7 @@
}
}
-class SchedulerGroup extends HelperGroup<UniqueHelper> {
- 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<T extends UniqueHelper>(
- executionBlockNumber: number,
- options: ISchedulerOptions = {},
- ) {
- return this.schedule<T>('schedule', executionBlockNumber, options);
- }
- scheduleAfter<T extends UniqueHelper>(
- blocksBeforeExecution: number,
- options: ISchedulerOptions = {},
- ) {
- return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);
- }
-
- schedule<T extends UniqueHelper>(
- 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<UniqueHelper> {
- //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<string[]> {
- 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<number> {
- 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<bigint> {
- 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<bigint> {
- 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<string[]> {
- return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());
- }
-}
-
-class CollectiveGroup extends HelperGroup<UniqueHelper> {
- /**
- * 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]);
- }
-}
-
-class CollectiveMembershipGroup extends HelperGroup<UniqueHelper> {
- /**
- * 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`, []);
- }
-}
-
-class RankedCollectiveGroup extends HelperGroup<UniqueHelper> {
- /**
- * 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;
- }
-}
-
-class ReferendaGroup extends HelperGroup<UniqueHelper> {
- /**
- * 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;
-}
-
-class DemocracyGroup extends HelperGroup<UniqueHelper> {
- // 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);
- }*/
-}
-
class PreimageGroup extends HelperGroup<UniqueHelper> {
async getPreimageInfo(h256: string) {
return (await this.helper.callRpc('api.query.preimage.statusFor', [h256])).toJSON();
@@ -3572,173 +2930,7 @@
*/
unrequestPreimage(signer: TSigner, h256: string) {
return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unrequestPreimage', [h256]);
- }
-}
-
-class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
- async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
- await this.helper.executeExtrinsic(
- signer,
- 'api.tx.foreignAssets.registerForeignAsset',
- [ownerAddress, location, metadata],
- true,
- );
- }
-
- async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
- await this.helper.executeExtrinsic(
- signer,
- 'api.tx.foreignAssets.updateForeignAsset',
- [foreignAssetId, location, metadata],
- true,
- );
- }
-}
-
-class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {
- 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,
- );
- }
-}
-
-class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
- 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);
- }
-}
-
-class PolkadexXcmHelperGroup<T extends ChainHelperBase> extends HelperGroup<T> {
- async whitelistToken(signer: TSigner, assetId: any) {
- await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);
- }
-}
-
-class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
- async accounts(address: string, currencyId: any) {
- const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;
- return BigInt(free);
- }
-}
-
-class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {
- async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {
- await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
- }
-
- async setMetadata(signer: TSigner, assetId: number, 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, beneficiary: string, amount: bigint) {
- await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
- }
-
- async account(assetId: string | number, 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;
- }
- }
}
class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {
@@ -3752,90 +2944,9 @@
batchAllCall(txs: any[]) {
return this.helper.constructApiCall('api.tx.utility.batchAll', [txs]);
- }
-}
-
-class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {
- async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {
- await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);
}
}
-class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {
- 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<MoonbeamHelper> {
- 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 MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {
- 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`, []));
- }
-}
-
export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;
export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;
@@ -3846,17 +2957,7 @@
rft: RFTGroup;
ft: FTGroup;
staking: StakingGroup;
- scheduler: SchedulerGroup;
- collatorSelection: CollatorSelectionGroup;
- council: ICollectiveGroup;
- technicalCommittee: ICollectiveGroup;
- fellowship: IFellowshipGroup;
- democracy: DemocracyGroup;
preimage: PreimageGroup;
- foreignAssets: ForeignAssetsGroup;
- xcm: XcmGroup<UniqueHelper>;
- xTokens: XTokensGroup<UniqueHelper>;
- tokens: TokensGroup<UniqueHelper>;
utility: UtilityGroup<UniqueHelper>;
constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
@@ -3868,303 +2969,11 @@
this.rft = new RFTGroup(this);
this.ft = new FTGroup(this);
this.staking = new StakingGroup(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);
this.preimage = new PreimageGroup(this);
- this.foreignAssets = new ForeignAssetsGroup(this);
- this.xcm = new XcmGroup(this, 'polkadotXcm');
- this.xTokens = new XTokensGroup(this);
- this.tokens = new TokensGroup(this);
this.utility = new UtilityGroup(this);
- }
-
- getSudo<T extends UniqueHelper>() {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const SudoHelperType = SudoHelper(this.helperBase);
- return this.clone(SudoHelperType) as T;
- }
-}
-
-export class XcmChainHelper extends ChainHelperBase {
- async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
- const wsProvider = new WsProvider(wsEndpoint);
- this.api = new ApiPromise({
- provider: wsProvider,
- });
- await this.api.isReadyOrError;
- this.network = await UniqueHelper.detectNetwork(this.api);
- }
-}
-
-export class RelayHelper extends XcmChainHelper {
- balance: SubstrateBalanceGroup<RelayHelper>;
- xcm: XcmGroup<RelayHelper>;
-
- 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<WestmintHelper>;
- xcm: XcmGroup<WestmintHelper>;
- assets: AssetsGroup<WestmintHelper>;
- xTokens: XTokensGroup<WestmintHelper>;
-
- 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<MoonbeamHelper>;
- assetManager: MoonbeamAssetManagerGroup;
- assets: AssetsGroup<MoonbeamHelper>;
- xTokens: XTokensGroup<MoonbeamHelper>;
- 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<AstarHelper>;
- assets: AssetsGroup<AstarHelper>;
- xcm: XcmGroup<AstarHelper>;
-
- 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');
- }
-
- getSudo<T extends UniqueHelper>() {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const SudoHelperType = SudoHelper(this.helperBase);
- return this.clone(SudoHelperType) as T;
- }
-}
-
-export class AcalaHelper extends XcmChainHelper {
- balance: SubstrateBalanceGroup<AcalaHelper>;
- assetRegistry: AcalaAssetRegistryGroup;
- xTokens: XTokensGroup<AcalaHelper>;
- tokens: TokensGroup<AcalaHelper>;
- xcm: XcmGroup<AcalaHelper>;
-
- 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');
- }
-
- getSudo<T extends AcalaHelper>() {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const SudoHelperType = SudoHelper(this.helperBase);
- return this.clone(SudoHelperType) as T;
- }
-}
-
-export class PolkadexHelper extends XcmChainHelper {
- assets: AssetsGroup<PolkadexHelper>;
- balance: SubstrateBalanceGroup<PolkadexHelper>;
- xTokens: XTokensGroup<PolkadexHelper>;
- xcm: XcmGroup<PolkadexHelper>;
- xcmHelper: PolkadexXcmHelperGroup<PolkadexHelper>;
-
- 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);
- }
-
- getSudo<T extends PolkadexHelper>() {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const SudoHelperType = SudoHelper(this.helperBase);
- return this.clone(SudoHelperType) as T;
- }
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(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;
- }
-
- executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {
- 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,
- );
- }
- };
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {
- return class extends Base {
- constructor(...args: any[]) {
- super(...args);
- }
-
- async executeExtrinsic(
- sender: IKeyringPair,
- extrinsic: string,
- params: any[],
- expectSuccess?: boolean,
- options: Partial<SignerOptions> | null = null,
- ): Promise<ITransactionResult> {
- 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;
- }
- async executeExtrinsicUncheckedWeight(
- sender: IKeyringPair,
- extrinsic: string,
- params: any[],
- expectSuccess?: boolean,
- options: Partial<SignerOptions> | null = null,
- ): Promise<ITransactionResult> {
- 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;
- }
- };
-}
-
export class UniqueBaseCollection {
helper: UniqueHelper;
collectionId: number;
@@ -4272,29 +3081,8 @@
async burn(signer: TSigner) {
return await this.helper.collection.burn(signer, this.collectionId);
- }
-
- scheduleAt<T extends UniqueHelper>(
- executionBlockNumber: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);
- return new UniqueBaseCollection(this.collectionId, scheduledHelper);
- }
-
- scheduleAfter<T extends UniqueHelper>(
- blocksBeforeExecution: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);
- return new UniqueBaseCollection(this.collectionId, scheduledHelper);
- }
-
- getSudo<T extends UniqueHelper>() {
- return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());
}
}
-
export class UniqueNFTCollection extends UniqueBaseCollection {
getTokenObject(tokenId: number) {
@@ -4386,29 +3174,8 @@
async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);
- }
-
- scheduleAt<T extends UniqueHelper>(
- executionBlockNumber: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);
- return new UniqueNFTCollection(this.collectionId, scheduledHelper);
- }
-
- scheduleAfter<T extends UniqueHelper>(
- blocksBeforeExecution: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);
- return new UniqueNFTCollection(this.collectionId, scheduledHelper);
- }
-
- getSudo<T extends UniqueHelper>() {
- return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());
}
}
-
export class UniqueRFTCollection extends UniqueBaseCollection {
getTokenObject(tokenId: number) {
@@ -4512,29 +3279,8 @@
async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);
- }
-
- scheduleAt<T extends UniqueHelper>(
- executionBlockNumber: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);
- return new UniqueRFTCollection(this.collectionId, scheduledHelper);
- }
-
- scheduleAfter<T extends UniqueHelper>(
- blocksBeforeExecution: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);
- return new UniqueRFTCollection(this.collectionId, scheduledHelper);
- }
-
- getSudo<T extends UniqueHelper>() {
- return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());
}
}
-
export class UniqueFTCollection extends UniqueBaseCollection {
async getBalance(addressObj: ICrossAccountId) {
@@ -4579,29 +3325,8 @@
async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {
return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);
- }
-
- scheduleAt<T extends UniqueHelper>(
- executionBlockNumber: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);
- return new UniqueFTCollection(this.collectionId, scheduledHelper);
- }
-
- scheduleAfter<T extends UniqueHelper>(
- blocksBeforeExecution: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);
- return new UniqueFTCollection(this.collectionId, scheduledHelper);
- }
-
- getSudo<T extends UniqueHelper>() {
- return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());
}
}
-
export class UniqueBaseToken {
collection: UniqueNFTCollection | UniqueRFTCollection;
@@ -4640,29 +3365,8 @@
nestingAccount() {
return this.collection.helper.util.getTokenAccount(this);
- }
-
- scheduleAt<T extends UniqueHelper>(
- executionBlockNumber: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);
- return new UniqueBaseToken(this.tokenId, scheduledCollection);
- }
-
- scheduleAfter<T extends UniqueHelper>(
- blocksBeforeExecution: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);
- return new UniqueBaseToken(this.tokenId, scheduledCollection);
- }
-
- getSudo<T extends UniqueHelper>() {
- return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());
}
}
-
export class UniqueNFToken extends UniqueBaseToken {
collection: UniqueNFTCollection;
@@ -4718,26 +3422,6 @@
async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {
return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);
- }
-
- scheduleAt<T extends UniqueHelper>(
- executionBlockNumber: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);
- return new UniqueNFToken(this.tokenId, scheduledCollection);
- }
-
- scheduleAfter<T extends UniqueHelper>(
- blocksBeforeExecution: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);
- return new UniqueNFToken(this.tokenId, scheduledCollection);
- }
-
- getSudo<T extends UniqueHelper>() {
- return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());
}
}
@@ -4807,25 +3491,5 @@
async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {
return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);
- }
-
- scheduleAt<T extends UniqueHelper>(
- executionBlockNumber: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);
- return new UniqueRFToken(this.tokenId, scheduledCollection);
- }
-
- scheduleAfter<T extends UniqueHelper>(
- blocksBeforeExecution: number,
- options: ISchedulerOptions = {},
- ) {
- const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);
- return new UniqueRFToken(this.tokenId, scheduledCollection);
- }
-
- getSudo<T extends UniqueHelper>() {
- return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());
}
}
tests/src/util/playgrounds/unique.xcm.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/util/playgrounds/unique.xcm.ts
@@ -0,0 +1,371 @@
+import {ApiPromise, WsProvider} from '@polkadot/api';
+import {IKeyringPair} from '@polkadot/types/types';
+import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from './unique';
+import {ILogger, TSigner, TSubstrateAccount} from './types';
+import {AcalaAssetMetadata, DemocracyStandardAccountVote, IForeignAssetMetadata, MoonbeamAssetInfo} from './types.xcm';
+
+
+export class XcmChainHelper extends ChainHelperBase {
+ async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
+ 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<AcalaHelper> {
+ async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);
+ }
+}
+
+class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {
+ 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<MoonbeamHelper> {
+ 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 MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {
+ 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 PolkadexXcmHelperGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ async whitelistToken(signer: TSigner, assetId: any) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);
+ }
+}
+
+export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
+ async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
+ await this.helper.executeExtrinsic(
+ signer,
+ 'api.tx.foreignAssets.registerForeignAsset',
+ [ownerAddress, location, metadata],
+ true,
+ );
+ }
+
+ async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
+ await this.helper.executeExtrinsic(
+ signer,
+ 'api.tx.foreignAssets.updateForeignAsset',
+ [foreignAssetId, location, metadata],
+ true,
+ );
+ }
+}
+
+export class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ 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<T extends ChainHelperBase> extends HelperGroup<T> {
+ 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<T extends ChainHelperBase> extends HelperGroup<T> {
+ 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<T extends ChainHelperBase> extends HelperGroup<T> {
+ async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
+ }
+
+ async setMetadata(signer: TSigner, assetId: number, 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, beneficiary: string, amount: bigint) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
+ }
+
+ async account(assetId: string | number, 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<RelayHelper>;
+ xcm: XcmGroup<RelayHelper>;
+
+ 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<WestmintHelper>;
+ xcm: XcmGroup<WestmintHelper>;
+ assets: AssetsGroup<WestmintHelper>;
+ xTokens: XTokensGroup<WestmintHelper>;
+
+ 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<MoonbeamHelper>;
+ assetManager: MoonbeamAssetManagerGroup;
+ assets: AssetsGroup<MoonbeamHelper>;
+ xTokens: XTokensGroup<MoonbeamHelper>;
+ 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<AstarHelper>;
+ assets: AssetsGroup<AstarHelper>;
+ xcm: XcmGroup<AstarHelper>;
+
+ 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<AcalaHelper>;
+ assetRegistry: AcalaAssetRegistryGroup;
+ xTokens: XTokensGroup<AcalaHelper>;
+ tokens: TokensGroup<AcalaHelper>;
+ xcm: XcmGroup<AcalaHelper>;
+
+ 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<PolkadexHelper>;
+ balance: SubstrateBalanceGroup<PolkadexHelper>;
+ xTokens: XTokensGroup<PolkadexHelper>;
+ xcm: XcmGroup<PolkadexHelper>;
+ xcmHelper: PolkadexXcmHelperGroup<PolkadexHelper>;
+
+ 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);
+ }
+}
+