difftreelog
tests: move scheduler to dev, move governance to separate file
in: master
7 files changed
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/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -216,13 +216,6 @@
},
}
-export interface IForeignAssetMetadata {
- name?: number | Uint8Array,
- symbol?: string,
- decimals?: number,
- minimalBalance?: bigint,
-}
-
export interface DemocracySplitAccount {
aye: bigint,
nay: bigint,
tests/src/util/playgrounds/types.xcm.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/types.xcm.ts
+++ b/tests/src/util/playgrounds/types.xcm.ts
@@ -26,4 +26,11 @@
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, ChainHelperBase, ChainHelperBaseConstructor} 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 {SignerOptions, VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';15import {spawnSync} from 'child_process';16import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper} from './unique.xcm';1718export class SilentLogger {19 log(_msg: any, _level: any): void { }20 level = {21 ERROR: 'ERROR' as const,22 WARNING: 'WARNING' as const,23 INFO: 'INFO' as const,24 };25}2627export class SilentConsole {28 // TODO: Remove, this is temporary: Filter unneeded API output29 // (Jaco promised it will be removed in the next version)30 consoleErr: any;31 consoleLog: any;32 consoleWarn: any;3334 constructor() {35 this.consoleErr = console.error;36 this.consoleLog = console.log;37 this.consoleWarn = console.warn;38 }3940 enable() {41 const outFn = (printer: any) => (...args: any[]) => {42 for(const arg of args) {43 if(typeof arg !== 'string')44 continue;45 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'];46 const needToSkip = skippedWarnings.reduce((a, b) => a || arg.includes(b), false);47 if(needToSkip || arg === 'Normal connection closure')48 return;49 }50 printer(...args);51 };5253 console.error = outFn(this.consoleErr.bind(console));54 console.log = outFn(this.consoleLog.bind(console));55 console.warn = outFn(this.consoleWarn.bind(console));56 }5758 disable() {59 console.error = this.consoleErr;60 console.log = this.consoleLog;61 console.warn = this.consoleWarn;62 }63}6465export interface IEventHelper {66 section(): string;6768 method(): string;6970 wrapEvent(data: any[]): any;71}7273// eslint-disable-next-line @typescript-eslint/naming-convention74function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {75 const helperClass = class implements IEventHelper {76 wrapEvent: (data: any[]) => any;77 _section: string;78 _method: string;7980 constructor() {81 this.wrapEvent = wrapEvent;82 this._section = section;83 this._method = method;84 }8586 section(): string {87 return this._section;88 }8990 method(): string {91 return this._method;92 }9394 filter(txres: ITransactionResult) {95 return txres.result.events.filter(e => e.event.section === section && e.event.method === method)96 .map(e => this.wrapEvent(e.event.data));97 }9899 find(txres: ITransactionResult) {100 const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);101 return e ? this.wrapEvent(e.event.data) : null;102 }103104 expect(txres: ITransactionResult) {105 const e = this.find(txres);106 if(e) {107 return e;108 } else {109 throw Error(`Expected event ${section}.${method}`);110 }111 }112 };113114 return helperClass;115}116117function eventJsonData<T = any>(data: any[], index: number) {118 return data[index].toJSON() as T;119}120121function eventHumanData(data: any[], index: number) {122 return data[index].toHuman();123}124125function eventData<T = any>(data: any[], index: number) {126 return data[index] as T;127}128129// eslint-disable-next-line @typescript-eslint/naming-convention130function EventSection(section: string) {131 return class Section {132 static section = section;133134 static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {135 const helperClass = EventHelper(Section.section, name, wrapEvent);136 return new helperClass();137 }138 };139}140141function schedulerSection(schedulerInstance: string) {142 return class extends EventSection(schedulerInstance) {143 static Dispatched = this.Method('Dispatched', data => ({144 task: eventJsonData(data, 0),145 id: eventHumanData(data, 1),146 result: data[2],147 }));148149 static PriorityChanged = this.Method('PriorityChanged', data => ({150 task: eventJsonData(data, 0),151 priority: eventJsonData(data, 1),152 }));153 };154}155156export class Event {157 static Democracy = class extends EventSection('democracy') {158 static Proposed = this.Method('Proposed', data => ({159 proposalIndex: eventJsonData<number>(data, 0),160 }));161162 static ExternalTabled = this.Method('ExternalTabled');163164 static Started = this.Method('Started', data => ({165 referendumIndex: eventJsonData<number>(data, 0),166 threshold: eventHumanData(data, 1),167 }));168169 static Voted = this.Method('Voted', data => ({170 voter: eventJsonData(data, 0),171 referendumIndex: eventJsonData<number>(data, 1),172 vote: eventJsonData(data, 2),173 }));174175 static Passed = this.Method('Passed', data => ({176 referendumIndex: eventJsonData<number>(data, 0),177 }));178179 static ProposalCanceled = this.Method('ProposalCanceled', data => ({180 propIndex: eventJsonData<number>(data, 0),181 }));182183 static Cancelled = this.Method('Cancelled', data => ({184 propIndex: eventJsonData<number>(data, 0),185 }));186187 static Vetoed = this.Method('Vetoed', data => ({188 who: eventHumanData(data, 0),189 proposalHash: eventHumanData(data, 1),190 until: eventJsonData<number>(data, 1),191 }));192 };193194 static Council = class extends EventSection('council') {195 static Proposed = this.Method('Proposed', data => ({196 account: eventHumanData(data, 0),197 proposalIndex: eventJsonData<number>(data, 1),198 proposalHash: eventHumanData(data, 2),199 threshold: eventJsonData<number>(data, 3),200 }));201 static Closed = this.Method('Closed', data => ({202 proposalHash: eventHumanData(data, 0),203 yes: eventJsonData<number>(data, 1),204 no: eventJsonData<number>(data, 2),205 }));206 static Executed = this.Method('Executed', data => ({207 proposalHash: eventHumanData(data, 0),208 }));209 };210211 static TechnicalCommittee = class extends EventSection('technicalCommittee') {212 static Proposed = this.Method('Proposed', data => ({213 account: eventHumanData(data, 0),214 proposalIndex: eventJsonData<number>(data, 1),215 proposalHash: eventHumanData(data, 2),216 threshold: eventJsonData<number>(data, 3),217 }));218 static Closed = this.Method('Closed', data => ({219 proposalHash: eventHumanData(data, 0),220 yes: eventJsonData<number>(data, 1),221 no: eventJsonData<number>(data, 2),222 }));223 static Approved = this.Method('Approved', data => ({224 proposalHash: eventHumanData(data, 0),225 }));226 static Executed = this.Method('Executed', data => ({227 proposalHash: eventHumanData(data, 0),228 result: eventHumanData(data, 1),229 }));230 };231232 static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {233 static Submitted = this.Method('Submitted', data => ({234 referendumIndex: eventJsonData<number>(data, 0),235 trackId: eventJsonData<number>(data, 1),236 proposal: eventJsonData(data, 2),237 }));238239 static Cancelled = this.Method('Cancelled', data => ({240 index: eventJsonData<number>(data, 0),241 tally: eventJsonData(data, 1),242 }));243 };244245 static UniqueScheduler = schedulerSection('uniqueScheduler');246 static Scheduler = schedulerSection('scheduler');247248 static XcmpQueue = class extends EventSection('xcmpQueue') {249 static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({250 messageHash: eventJsonData(data, 0),251 }));252253 static Success = this.Method('Success', data => ({254 messageHash: eventJsonData(data, 0),255 }));256257 static Fail = this.Method('Fail', data => ({258 messageHash: eventJsonData(data, 0),259 outcome: eventData<XcmV2TraitsError>(data, 1),260 }));261 };262}263264// eslint-disable-next-line @typescript-eslint/naming-convention265export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {266 return class extends Base {267 constructor(...args: any[]) {268 super(...args);269 }270271 async executeExtrinsic(272 sender: IKeyringPair,273 extrinsic: string,274 params: any[],275 expectSuccess?: boolean,276 options: Partial<SignerOptions> | null = null,277 ): Promise<ITransactionResult> {278 const call = this.constructApiCall(extrinsic, params);279 const result = await super.executeExtrinsic(280 sender,281 'api.tx.sudo.sudo',282 [call],283 expectSuccess,284 options,285 );286287 if(result.status === 'Fail') return result;288289 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;290 if(data.isErr) {291 if(data.asErr.isModule) {292 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;293 const metaError = super.getApi()?.registry.findMetaError(error);294 throw new Error(`${metaError.section}.${metaError.name}`);295 } else if(data.asErr.isToken) {296 throw new Error(`Token: ${data.asErr.asToken}`);297 }298 // May be [object Object] in case of unhandled non-unit enum299 throw new Error(`Misc: ${data.asErr.toHuman()}`);300 }301 return result;302 }303 async executeExtrinsicUncheckedWeight(304 sender: IKeyringPair,305 extrinsic: string,306 params: any[],307 expectSuccess?: boolean,308 options: Partial<SignerOptions> | null = null,309 ): Promise<ITransactionResult> {310 const call = this.constructApiCall(extrinsic, params);311 const result = await super.executeExtrinsic(312 sender,313 'api.tx.sudo.sudoUncheckedWeight',314 [call, {refTime: 0, proofSize: 0}],315 expectSuccess,316 options,317 );318319 if(result.status === 'Fail') return result;320321 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;322 if(data.isErr) {323 if(data.asErr.isModule) {324 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;325 const metaError = super.getApi()?.registry.findMetaError(error);326 throw new Error(`${metaError.section}.${metaError.name}`);327 } else if(data.asErr.isToken) {328 throw new Error(`Token: ${data.asErr.asToken}`);329 }330 // May be [object Object] in case of unhandled non-unit enum331 throw new Error(`Misc: ${data.asErr.toHuman()}`);332 }333 return result;334 }335 };336}337338export class DevUniqueHelper extends UniqueHelper {339 /**340 * Arrange methods for tests341 */342 arrange: ArrangeGroup;343 wait: WaitGroup;344 admin: AdminGroup;345 session: SessionGroup;346 testUtils: TestUtilGroup;347348 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {349 options.helperBase = options.helperBase ?? DevUniqueHelper;350351 super(logger, options);352 this.arrange = new ArrangeGroup(this);353 this.wait = new WaitGroup(this);354 this.admin = new AdminGroup(this);355 this.testUtils = new TestUtilGroup(this);356 this.session = new SessionGroup(this);357 }358359 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {360 if(!wsEndpoint) throw new Error('wsEndpoint was not set');361 const wsProvider = new WsProvider(wsEndpoint);362 this.api = new ApiPromise({363 provider: wsProvider,364 signedExtensions: {365 ContractHelpers: {366 extrinsic: {},367 payload: {},368 },369 CheckMaintenance: {370 extrinsic: {},371 payload: {},372 },373 DisableIdentityCalls: {374 extrinsic: {},375 payload: {},376 },377 FakeTransactionFinalizer: {378 extrinsic: {},379 payload: {},380 },381 },382 rpc: {383 unique: defs.unique.rpc,384 appPromotion: defs.appPromotion.rpc,385 povinfo: defs.povinfo.rpc,386 eth: {387 feeHistory: {388 description: 'Dummy',389 params: [],390 type: 'u8',391 },392 maxPriorityFeePerGas: {393 description: 'Dummy',394 params: [],395 type: 'u8',396 },397 },398 },399 });400 await this.api.isReadyOrError;401 this.network = await UniqueHelper.detectNetwork(this.api);402 this.wsEndpoint = wsEndpoint;403 }404 getSudo<T extends UniqueHelper>() {405 // eslint-disable-next-line @typescript-eslint/naming-convention406 const SudoHelperType = SudoHelper(this.helperBase);407 return this.clone(SudoHelperType) as T;408 }409}410411export class DevRelayHelper extends RelayHelper {412 wait: WaitGroup;413414 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {415 options.helperBase = options.helperBase ?? DevRelayHelper;416417 super(logger, options);418 this.wait = new WaitGroup(this);419 }420}421422export class DevWestmintHelper extends WestmintHelper {423 wait: WaitGroup;424425 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {426 options.helperBase = options.helperBase ?? DevWestmintHelper;427428 super(logger, options);429 this.wait = new WaitGroup(this);430 }431}432433export class DevStatemineHelper extends DevWestmintHelper {}434435export class DevStatemintHelper extends DevWestmintHelper {}436437export class DevMoonbeamHelper extends MoonbeamHelper {438 account: MoonbeamAccountGroup;439 wait: WaitGroup;440 fastDemocracy: MoonbeamFastDemocracyGroup;441442 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {443 options.helperBase = options.helperBase ?? DevMoonbeamHelper;444 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';445446 super(logger, options);447 this.account = new MoonbeamAccountGroup(this);448 this.wait = new WaitGroup(this);449 this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);450 }451}452453export class DevMoonriverHelper extends DevMoonbeamHelper {454 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {455 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';456 super(logger, options);457 }458}459460export class DevAstarHelper extends AstarHelper {461 wait: WaitGroup;462463 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {464 options.helperBase = options.helperBase ?? DevAstarHelper;465466 super(logger, options);467 this.wait = new WaitGroup(this);468 }469470 getSudo<T extends UniqueHelper>() {471 // eslint-disable-next-line @typescript-eslint/naming-convention472 const SudoHelperType = SudoHelper(this.helperBase);473 return this.clone(SudoHelperType) as T;474 }475}476477export class DevShidenHelper extends AstarHelper {478 wait: WaitGroup;479480 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {481 options.helperBase = options.helperBase ?? DevShidenHelper;482483 super(logger, options);484 this.wait = new WaitGroup(this);485 }486}487488export class DevAcalaHelper extends AcalaHelper {489 wait: WaitGroup;490491 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {492 options.helperBase = options.helperBase ?? DevAcalaHelper;493494 super(logger, options);495 this.wait = new WaitGroup(this);496 }497 getSudo<T extends UniqueHelper>() {498 // eslint-disable-next-line @typescript-eslint/naming-convention499 const SudoHelperType = SudoHelper(this.helperBase);500 return this.clone(SudoHelperType) as T;501 }502}503504export class DevPolkadexHelper extends PolkadexHelper {505 wait: WaitGroup;506 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {507 options.helperBase = options.helperBase ?? PolkadexHelper;508509 super(logger, options);510 this.wait = new WaitGroup(this);511 }512513 getSudo<T extends PolkadexHelper>() {514 // eslint-disable-next-line @typescript-eslint/naming-convention515 const SudoHelperType = SudoHelper(this.helperBase);516 return this.clone(SudoHelperType) as T;517 }518}519520export class DevKaruraHelper extends DevAcalaHelper {}521522export class ArrangeGroup {523 helper: DevUniqueHelper;524525 scheduledIdSlider = 0;526527 constructor(helper: DevUniqueHelper) {528 this.helper = helper;529 }530531 /**532 * Generates accounts with the specified UNQ token balance533 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.534 * @param donor donor account for balances535 * @returns array of newly created accounts536 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);537 */538 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {539 let nonce = await this.helper.chain.getNonce(donor.address);540 const wait = new WaitGroup(this.helper);541 const ss58Format = this.helper.chain.getChainProperties().ss58Format;542 const tokenNominal = this.helper.balance.getOneTokenNominal();543 const transactions = [];544 const accounts: IKeyringPair[] = [];545 for(const balance of balances) {546 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);547 accounts.push(recipient);548 if(balance !== 0n) {549 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);550 transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));551 nonce++;552 }553 }554555 await Promise.all(transactions).catch(_e => {});556557 //#region TODO remove this region, when nonce problem will be solved558 const checkBalances = async () => {559 let isSuccess = true;560 for(let i = 0; i < balances.length; i++) {561 const balance = await this.helper.balance.getSubstrate(accounts[i].address);562 if(balance !== balances[i] * tokenNominal) {563 isSuccess = false;564 break;565 }566 }567 return isSuccess;568 };569570 let accountsCreated = false;571 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;572 // checkBalances retry up to 5-50 blocks573 for(let index = 0; index < maxBlocksChecked; index++) {574 accountsCreated = await checkBalances();575 if(accountsCreated) break;576 await wait.newBlocks(1);577 }578579 if(!accountsCreated) throw Error('Accounts generation failed');580 //#endregion581582 return accounts;583 };584585 // TODO combine this method and createAccounts into one586 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {587 const createAsManyAsCan = async () => {588 let transactions: any = [];589 const accounts: IKeyringPair[] = [];590 let nonce = await this.helper.chain.getNonce(donor.address);591 const tokenNominal = this.helper.balance.getOneTokenNominal();592 const ss58Format = this.helper.chain.getChainProperties().ss58Format;593 for(let i = 0; i < accountsToCreate; i++) {594 if(i === 500) { // if there are too many accounts to create595 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled596 transactions = []; //597 nonce = await this.helper.chain.getNonce(donor.address); // update nonce598 }599 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);600 accounts.push(recipient);601 if(withBalance !== 0n) {602 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);603 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));604 nonce++;605 }606 }607608 const fullfilledAccounts = [];609 await Promise.allSettled(transactions);610 for(const account of accounts) {611 const accountBalance = await this.helper.balance.getSubstrate(account.address);612 if(accountBalance === withBalance * tokenNominal) {613 fullfilledAccounts.push(account);614 }615 }616 return fullfilledAccounts;617 };618619620 const crowd: IKeyringPair[] = [];621 // do up to 5 retries622 for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {623 const asManyAsCan = await createAsManyAsCan();624 crowd.push(...asManyAsCan);625 accountsToCreate -= asManyAsCan.length;626 }627628 if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);629630 return crowd;631 };632633 /**634 * Generates one account with zero balance635 * @returns the newly generated account636 * @example const account = await helper.arrange.createEmptyAccount();637 */638 createEmptyAccount = (): IKeyringPair => {639 const ss58Format = this.helper.chain.getChainProperties().ss58Format;640 return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);641 };642643 isDevNode = async () => {644 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();645 if(blockNumber == 0) {646 await this.helper.wait.newBlocks(1);647 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();648 }649 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);650 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);651 const findCreationDate = (block: any) => {652 const humanBlock = block.toHuman();653 let date;654 humanBlock.block.extrinsics.forEach((ext: any) => {655 if(ext.method.section === 'timestamp') {656 date = Number(ext.method.args.now.replaceAll(',', ''));657 }658 });659 return date;660 };661 const block1date = await findCreationDate(block1);662 const block2date = await findCreationDate(block2);663 if(block2date! - block1date! < 9000) return true;664 };665666 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {667 const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);668 let balance = await this.helper.balance.getSubstrate(address);669670 await promise();671672 balance -= await this.helper.balance.getSubstrate(address);673674 return balance;675 }676677 async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {678 const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);679680 const kvJson: {[key: string]: string} = {};681682 for(const kv of rawPovInfo.keyValues) {683 kvJson[kv.key.toHex()] = kv.value.toHex();684 }685686 const kvStr = JSON.stringify(kvJson);687688 const chainql = spawnSync(689 'chainql',690 [691 `--tla-code=data=${kvStr}`,692 '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,693 ],694 );695696 if(!chainql.stdout) {697 throw Error('unable to get an output from the `chainql`');698 }699700 return {701 proofSize: rawPovInfo.proofSize.toNumber(),702 compactProofSize: rawPovInfo.compactProofSize.toNumber(),703 compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),704 results: rawPovInfo.results,705 kv: JSON.parse(chainql.stdout.toString()),706 };707 }708709 calculatePalletAddress(palletId: any) {710 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));711 return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);712 }713714 makeScheduledIds(num: number): string[] {715 function makeId(slider: number) {716 const scheduledIdSize = 64;717 const hexId = slider.toString(16);718 const prefixSize = scheduledIdSize - hexId.length;719720 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;721722 return scheduledId;723 }724725 const ids = [];726 for(let i = 0; i < num; i++) {727 ids.push(makeId(this.scheduledIdSlider));728 this.scheduledIdSlider += 1;729 }730731 return ids;732 }733734 makeScheduledId(): string {735 return (this.makeScheduledIds(1))[0];736 }737738 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {739 const capture = new EventCapture(this.helper, eventSection, eventMethod);740 await capture.startCapture();741742 return capture;743 }744745 makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {746 return {747 V2: [748 {749 WithdrawAsset: [750 {751 id,752 fun: {753 Fungible: amount,754 },755 },756 ],757 },758 {759 BuyExecution: {760 fees: {761 id,762 fun: {763 Fungible: amount,764 },765 },766 weightLimit: 'Unlimited',767 },768 },769 {770 DepositAsset: {771 assets: {772 Wild: 'All',773 },774 maxAssets: 1,775 beneficiary: {776 parents: 0,777 interior: {778 X1: {779 AccountId32: {780 network: 'Any',781 id: beneficiary,782 },783 },784 },785 },786 },787 },788 ],789 };790 }791792 makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {793 return {794 V2: [795 {796 ReserveAssetDeposited: [797 {798 id,799 fun: {800 Fungible: amount,801 },802 },803 ],804 },805 {806 BuyExecution: {807 fees: {808 id,809 fun: {810 Fungible: amount,811 },812 },813 weightLimit: 'Unlimited',814 },815 },816 {817 DepositAsset: {818 assets: {819 Wild: 'All',820 },821 maxAssets: 1,822 beneficiary: {823 parents: 0,824 interior: {825 X1: {826 AccountId32: {827 network: 'Any',828 id: beneficiary,829 },830 },831 },832 },833 },834 },835 ],836 };837 }838}839840class MoonbeamAccountGroup {841 helper: MoonbeamHelper;842843 keyring: Keyring;844 _alithAccount: IKeyringPair;845 _baltatharAccount: IKeyringPair;846 _dorothyAccount: IKeyringPair;847848 constructor(helper: MoonbeamHelper) {849 this.helper = helper;850851 this.keyring = new Keyring({type: 'ethereum'});852 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';853 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';854 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';855856 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');857 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');858 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');859 }860861 alithAccount() {862 return this._alithAccount;863 }864865 baltatharAccount() {866 return this._baltatharAccount;867 }868869 dorothyAccount() {870 return this._dorothyAccount;871 }872873 create() {874 return this.keyring.addFromUri(mnemonicGenerate());875 }876}877878class MoonbeamFastDemocracyGroup {879 helper: DevMoonbeamHelper;880881 constructor(helper: DevMoonbeamHelper) {882 this.helper = helper;883 }884885 async executeProposal(proposalDesciption: string, encodedProposal: string) {886 const proposalHash = blake2AsHex(encodedProposal);887888 const alithAccount = this.helper.account.alithAccount();889 const baltatharAccount = this.helper.account.baltatharAccount();890 const dorothyAccount = this.helper.account.dorothyAccount();891892 const councilVotingThreshold = 2;893 const technicalCommitteeThreshold = 2;894 const fastTrackVotingPeriod = 3;895 const fastTrackDelayPeriod = 0;896897 console.log(`[democracy] executing '${proposalDesciption}' proposal`);898899 // >>> Propose external motion through council >>>900 console.log('\t* Propose external motion through council.......');901 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});902 const encodedMotion = externalMotion?.method.toHex() || '';903 const motionHash = blake2AsHex(encodedMotion);904 console.log('\t* Motion hash is %s', motionHash);905906 await this.helper.collective.council.propose(907 baltatharAccount,908 councilVotingThreshold,909 externalMotion,910 externalMotion.encodedLength,911 );912913 const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;914 await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);915 await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);916917 await this.helper.collective.council.close(918 dorothyAccount,919 motionHash,920 councilProposalIdx,921 {922 refTime: 1_000_000_000,923 proofSize: 1_000_000,924 },925 externalMotion.encodedLength,926 );927 console.log('\t* Propose external motion through council.......DONE');928 // <<< Propose external motion through council <<<929930 // >>> Fast track proposal through technical committee >>>931 console.log('\t* Fast track proposal through technical committee.......');932 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);933 const encodedFastTrack = fastTrack?.method.toHex() || '';934 const fastTrackHash = blake2AsHex(encodedFastTrack);935 console.log('\t* FastTrack hash is %s', fastTrackHash);936937 await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);938939 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;940 await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);941 await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);942943 await this.helper.collective.techCommittee.close(944 baltatharAccount,945 fastTrackHash,946 techProposalIdx,947 {948 refTime: 1_000_000_000,949 proofSize: 1_000_000,950 },951 fastTrack.encodedLength,952 );953 console.log('\t* Fast track proposal through technical committee.......DONE');954 // <<< Fast track proposal through technical committee <<<955956 const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);957 const referendumIndex = democracyStarted.referendumIndex;958959 // >>> Referendum voting >>>960 console.log(`\t* Referendum #${referendumIndex} voting.......`);961 await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {962 balance: 10_000_000_000_000_000_000n,963 vote: {aye: true, conviction: 1},964 });965 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);966 // <<< Referendum voting <<<967968 // Wait the proposal to pass969 await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);970971 await this.helper.wait.newBlocks(1);972973 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);974 }975}976977class WaitGroup {978 helper: ChainHelperBase;979980 constructor(helper: ChainHelperBase) {981 this.helper = helper;982 }983984 sleep(milliseconds: number) {985 return new Promise((resolve) => setTimeout(resolve, milliseconds));986 }987988 private async waitWithTimeout(promise: Promise<any>, timeout: number) {989 let isBlock = false;990 promise.then(() => isBlock = true).catch(() => isBlock = true);991 let totalTime = 0;992 const step = 100;993 while(!isBlock) {994 await this.sleep(step);995 totalTime += step;996 if(totalTime >= timeout) throw Error('Blocks production failed');997 }998 return promise;999 }10001001 /**1002 * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.1003 * @param promise async operation to race against the timeout1004 * @param timeoutMS time after which to time out1005 * @param timeoutError error message to throw1006 * @returns promise of the same type the operation had1007 */1008 withTimeout<T>(1009 promise: Promise<T>,1010 timeoutMS = 30000,1011 timeoutError = 'The operation has timed out!',1012 ): Promise<T> {1013 const timeout = new Promise<never>((_, reject) => {1014 setTimeout(() => {1015 reject(new Error(timeoutError));1016 }, timeoutMS);1017 });10181019 return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});1020 }10211022 /**1023 * Wait for specified number of blocks1024 * @param blocksCount number of blocks to wait1025 * @returns1026 */1027 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {1028 timeout = timeout ?? blocksCount * 60_000;1029 // eslint-disable-next-line no-async-promise-executor1030 const promise = new Promise<void>(async (resolve) => {1031 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {1032 if(blocksCount > 0) {1033 blocksCount--;1034 } else {1035 unsubscribe();1036 resolve();1037 }1038 });1039 });1040 await this.waitWithTimeout(promise, timeout);1041 return promise;1042 }10431044 /**1045 * Wait for the specified number of sessions to pass.1046 * Only applicable if the Session pallet is turned on.1047 * @param sessionCount number of sessions to wait1048 * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks1049 * @returns1050 */1051 async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {1052 console.log(`Waiting for ${sessionCount} new session${sessionCount>1's'''}.`1053 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');10541055 const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;1056 let currentSessionIndex = -1;10571058 while(currentSessionIndex < expectedSessionIndex) {1059 // eslint-disable-next-line no-async-promise-executor1060 currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {1061 await this.newBlocks(1);1062 const res = await (this.helper as DevUniqueHelper).session.getIndex();1063 resolve(res);1064 }), blockTimeout, 'The chain has stopped producing blocks!');1065 }1066 }10671068 async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {1069 timeout = timeout ?? 30 * 60 * 1000;1070 // eslint-disable-next-line no-async-promise-executor1071 const promise = new Promise<void>(async (resolve) => {1072 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1073 if(data.number.toNumber() >= blockNumber) {1074 unsubscribe();1075 resolve();1076 }1077 });1078 });1079 await this.waitWithTimeout(promise, timeout);1080 return promise;1081 }10821083 async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {1084 timeout = timeout ?? 30 * 60 * 1000;1085 // eslint-disable-next-line no-async-promise-executor1086 const promise = new Promise<void>(async (resolve) => {1087 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {1088 if(data.value.relayParentNumber.toNumber() >= blockNumber) {1089 // @ts-ignore1090 unsubscribe();1091 resolve();1092 }1093 });1094 });1095 await this.waitWithTimeout(promise, timeout);1096 return promise;1097 }10981099 noScheduledTasks() {1100 const api = this.helper.getApi();11011102 // eslint-disable-next-line no-async-promise-executor1103 const promise = new Promise<void>(async resolve => {1104 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1105 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();11061107 if(areThereScheduledTasks.length == 0) {1108 unsubscribe();1109 resolve();1110 }1111 });1112 });11131114 return promise;1115 }11161117 parachainBlockMultiplesOf(val: bigint) {1118 // eslint-disable-next-line no-async-promise-executor1119 const promise = new Promise<void>(async resolve => {1120 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1121 if(data.number.toBigInt() % val == 0n) {1122 console.log(`from waiter: ${data.number.toBigInt()}`);1123 unsubscribe();1124 resolve();1125 }1126 });1127 });1128 return promise;1129 }11301131 event<T extends IEventHelper>(1132 maxBlocksToWait: number,1133 eventHelper: T,1134 filter: (_: any) => boolean = () => true,1135 ): any {1136 // eslint-disable-next-line no-async-promise-executor1137 const promise = new Promise<T | null>(async (resolve) => {1138 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1139 const blockNumber = header.number.toHuman();1140 const blockHash = header.hash;1141 const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1142 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;11431144 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);11451146 const apiAt = await this.helper.getApi().at(blockHash);1147 const eventRecords = (await apiAt.query.system.events()) as any;11481149 const neededEvent = eventRecords.toArray()1150 .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1151 .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1152 .find(filter);11531154 if(neededEvent) {1155 unsubscribe();1156 resolve(neededEvent);1157 } else if(maxBlocksToWait > 0) {1158 maxBlocksToWait--;1159 } else {1160 this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);1161 unsubscribe();1162 resolve(null);1163 }1164 });1165 });1166 return promise;1167 }11681169 async expectEvent<T extends IEventHelper>(1170 maxBlocksToWait: number,1171 eventHelper: T,1172 filter: (e: any) => boolean = () => true,1173 ) {1174 const e = await this.event(maxBlocksToWait, eventHelper, filter);1175 if(e == null) {1176 throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1177 } else {1178 return e;1179 }1180 }1181}11821183class SessionGroup {1184 helper: ChainHelperBase;11851186 constructor(helper: ChainHelperBase) {1187 this.helper = helper;1188 }11891190 //todo:collator documentation1191 async getIndex(): Promise<number> {1192 return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1193 }11941195 newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1196 return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1197 }11981199 setOwnKeys(signer: TSigner, key: string) {1200 return this.helper.executeExtrinsic(1201 signer,1202 'api.tx.session.setKeys',1203 [key, '0x0'],1204 true,1205 );1206 }12071208 setOwnKeysFromAddress(signer: TSigner) {1209 return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1210 }1211}12121213class TestUtilGroup {1214 helper: DevUniqueHelper;12151216 constructor(helper: DevUniqueHelper) {1217 this.helper = helper;1218 }12191220 async enable() {1221 if(this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {1222 return;1223 }12241225 const signer = this.helper.util.fromSeed('//Alice');1226 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1227 }12281229 async setTestValue(signer: TSigner, testVal: number) {1230 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1231 }12321233 async incTestValue(signer: TSigner) {1234 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1235 }12361237 async setTestValueAndRollback(signer: TSigner, testVal: number) {1238 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1239 }12401241 async testValue(blockIdx?: number) {1242 const api = blockIdx1243 ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1244 : this.helper.getApi();12451246 return (await api.query.testUtils.testValue()).toJSON();1247 }12481249 async justTakeFee(signer: TSigner) {1250 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1251 }12521253 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1254 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1255 }1256}12571258class EventCapture {1259 helper: DevUniqueHelper;1260 eventSection: string;1261 eventMethod: string;1262 events: EventRecord[] = [];1263 unsubscribe: VoidFn | null = null;12641265 constructor(1266 helper: DevUniqueHelper,1267 eventSection: string,1268 eventMethod: string,1269 ) {1270 this.helper = helper;1271 this.eventSection = eventSection;1272 this.eventMethod = eventMethod;1273 }12741275 async startCapture() {1276 this.stopCapture();1277 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1278 const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);12791280 this.events.push(...newEvents);1281 })) as any;1282 }12831284 stopCapture() {1285 if(this.unsubscribe !== null) {1286 this.unsubscribe();1287 }1288 }12891290 extractCapturedEvents() {1291 return this.events;1292 }1293}12941295class AdminGroup {1296 helper: UniqueHelper;12971298 constructor(helper: UniqueHelper) {1299 this.helper = helper;1300 }13011302 async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> {1303 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1304 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1305 staker: e.event.data[0].toString(),1306 stake: e.event.data[1].toBigInt(),1307 payout: e.event.data[2].toBigInt(),1308 }));1309 }1310}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,13 +39,11 @@
TSigner,
TSubstrateAccount,
TNetworks,
- IForeignAssetMetadata,
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;
@@ -2757,6 +2755,7 @@
}
}
+
class StakingGroup extends HelperGroup<UniqueHelper> {
/**
* Stake tokens for App Promotion
@@ -2860,648 +2859,10 @@
block: block.toBigInt(),
amount: amount.toBigInt(),
}));
- return result;
- }
-}
-
-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) {
@@ -3569,171 +2930,9 @@
*/
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,
- );
- }
-}
-
-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;
- }
- }
-}
-
class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {
async batch(signer: TSigner, txs: any[]) {
return await this.helper.executeExtrinsic(signer, 'api.tx.utility.batch', [txs]);
@@ -3747,9 +2946,7 @@
return this.helper.constructApiCall('api.tx.utility.batchAll', [txs]);
}
}
-
-
export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;
export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;
@@ -3760,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 } = {}) {
@@ -3782,88 +2969,9 @@
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);
}
-}
-
-// 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,
- );
- }
- };
}
export class UniqueBaseCollection {
@@ -3973,22 +3081,6 @@
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);
}
}
@@ -4083,22 +3175,6 @@
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);
- }
}
export class UniqueRFTCollection extends UniqueBaseCollection {
@@ -4204,22 +3280,6 @@
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);
- }
}
export class UniqueFTCollection extends UniqueBaseCollection {
@@ -4265,22 +3325,6 @@
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);
}
}
@@ -4321,22 +3365,6 @@
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);
}
}
@@ -4395,22 +3423,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);
- }
}
export class UniqueRFToken extends UniqueBaseToken {
@@ -4479,21 +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);
}
}
tests/src/util/playgrounds/unique.xcm.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.xcm.ts
+++ b/tests/src/util/playgrounds/unique.xcm.ts
@@ -1,9 +1,9 @@
-
import {ApiPromise, WsProvider} from '@polkadot/api';
-import {AssetsGroup, ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, TokensGroup, UniqueHelper, XTokensGroup, XcmGroup} from './unique';
-import {ILogger, TSigner} from './types';
-import {SudoHelper} from './unique.dev';
-import {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.xcm';
+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> {
@@ -102,6 +102,169 @@
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>;