difftreelog
Merge pull request #996 from UniqueNetwork/tests/playgrounds-refactor
in: master
refactor(playgorunds): rearranging the code structure
10 files changed
.envrcdiffbeforeafterboth--- a/.envrc
+++ b/.envrc
@@ -30,7 +30,7 @@
fi
echo -e "${GREEN}Baedeker env updated${RESET}"
- nginx_id=$(docker compose -f .baedeker/.bdk-env/docker-compose.yml ps --format=json | jq -r '.[] | select(.Service == "nginx") | .ID' -e)
+ nginx_id=$(docker compose -f .baedeker/.bdk-env/docker-compose.yml ps --format=json | jq -s 'flatten' | jq -r '.[] | select(.Service == "nginx") | .ID' -e)
if ! [ $? -eq 0 ]; then
echo -e "${RED}Nginx container not found${RESET}"
exit 0
tests/src/governance/util.tsdiffbeforeafterboth--- a/tests/src/governance/util.ts
+++ b/tests/src/governance/util.ts
@@ -2,6 +2,7 @@
import {xxhashAsHex} from '@polkadot/util-crypto';
import {usingPlaygrounds, expect} from '../util';
import {UniqueHelper} from '../util/playgrounds/unique';
+import {DevUniqueHelper} from '../util/playgrounds/unique.dev';
export const democracyLaunchPeriod = 35;
export const democracyVotingPeriod = 35;
@@ -203,7 +204,7 @@
});
}
-export async function voteUnanimouslyInFellowship(helper: UniqueHelper, fellows: IKeyringPair[][], minRank: number, referendumIndex: number) {
+export async function voteUnanimouslyInFellowship(helper: DevUniqueHelper, fellows: IKeyringPair[][], minRank: number, referendumIndex: number) {
for(let rank = minRank; rank < fellowshipRankLimit; rank++) {
for(const member of fellows[rank]) {
await helper.fellowship.collective.vote(member, referendumIndex, true);
tests/src/maintenance.seqtest.tsdiffbeforeafterboth--- a/tests/src/maintenance.seqtest.ts
+++ b/tests/src/maintenance.seqtest.ts
@@ -192,19 +192,22 @@
const blocksToWait = 6;
// Scheduling works before the maintenance
- await nftBeforeMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM})
+ .nft.transferToken(bob, collection.collectionId, nftBeforeMM.tokenId, {Substrate: superuser.address});
+
await helper.wait.newBlocks(blocksToWait + 1);
expect(await nftBeforeMM.getOwner()).to.be.deep.equal({Substrate: superuser.address});
// Schedule a transaction that should occur *during* the maintenance
- await nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM})
+ .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address});
+
// Schedule a transaction that should occur *after* the maintenance
- await nftDuringMM.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM})
+ .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address});
+
await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []);
expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true;
@@ -214,16 +217,16 @@
expect(await nftDuringMM.getOwner()).to.be.deep.equal({Substrate: bob.address});
// Any attempts to schedule a tx during the MM should be rejected
- await expect(nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM})
- .transfer(bob, {Substrate: superuser.address}))
+ await expect(helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM})
+ .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address}))
.to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/);
await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []);
expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false;
// Scheduling works after the maintenance
- await nftAfterMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM})
- .transfer(bob, {Substrate: superuser.address});
+ await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM})
+ .nft.transferToken(bob, collection.collectionId, nftAfterMM.tokenId, {Substrate: superuser.address});
await helper.wait.newBlocks(blocksToWait + 1);
tests/src/scheduler.seqtest.tsdiffbeforeafterboth--- a/tests/src/scheduler.seqtest.ts
+++ b/tests/src/scheduler.seqtest.ts
@@ -47,9 +47,8 @@
const token = await collection.mintToken(alice);
const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;
const blocksBeforeExecution = 4;
-
- await token.scheduleAfter(blocksBeforeExecution, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(blocksBeforeExecution, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;
expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
@@ -103,8 +102,8 @@
expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
await helper.scheduler.cancelScheduled(alice, scheduledId);
@@ -363,8 +362,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(bob, {Substrate: alice.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
await helper.getSudo().scheduler.cancelScheduled(superuser, scheduledId);
@@ -404,8 +403,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 6;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(bob, {Substrate: alice.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
const priority = 112;
@@ -583,8 +582,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId});
@@ -614,8 +613,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(alice, {Substrate: bob.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
await expect(helper.scheduler.cancelScheduled(bob, scheduledId))
@@ -655,8 +654,8 @@
const scheduledId = helper.arrange.makeScheduledId();
const waitForBlocks = 4;
- await token.scheduleAfter(waitForBlocks, {scheduledId})
- .transfer(bob, {Substrate: alice.address});
+ await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
+ .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
const priority = 112;
await expect(helper.scheduler.changePriority(alice, scheduledId, priority))
tests/src/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -216,43 +216,6 @@
},
}
-export interface IForeignAssetMetadata {
- name?: number | Uint8Array,
- symbol?: string,
- decimals?: number,
- minimalBalance?: bigint,
-}
-
-export interface MoonbeamAssetInfo {
- location: any,
- metadata: {
- name: string,
- symbol: string,
- decimals: number,
- isFrozen: boolean,
- minimalBalance: bigint,
- },
- existentialDeposit: bigint,
- isSufficient: boolean,
- unitsPerSecond: bigint,
- numAssetsWeightHint: number,
-}
-
-export interface AcalaAssetMetadata {
- name: string,
- symbol: string,
- decimals: number,
- minimalBalance: bigint,
-}
-
-export interface DemocracyStandardAccountVote {
- balance: bigint,
- vote: {
- aye: boolean,
- conviction: number,
- },
-}
-
export interface DemocracySplitAccount {
aye: bigint,
nay: bigint,
tests/src/util/playgrounds/types.xcm.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/util/playgrounds/types.xcm.ts
@@ -0,0 +1,36 @@
+export interface AcalaAssetMetadata {
+ name: string,
+ symbol: string,
+ decimals: number,
+ minimalBalance: bigint,
+}
+
+export interface MoonbeamAssetInfo {
+ location: any,
+ metadata: {
+ name: string,
+ symbol: string,
+ decimals: number,
+ isFrozen: boolean,
+ minimalBalance: bigint,
+ },
+ existentialDeposit: bigint,
+ isSufficient: boolean,
+ unitsPerSecond: bigint,
+ numAssetsWeightHint: number,
+}
+
+export interface DemocracyStandardAccountVote {
+ balance: bigint,
+ vote: {
+ aye: boolean,
+ conviction: number,
+ },
+}
+
+export interface IForeignAssetMetadata {
+ name?: number | Uint8Array,
+ symbol?: string,
+ decimals?: number,
+ minimalBalance?: bigint,
+}
\ No newline at end of file
tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -3,16 +3,18 @@
import {stringToU8a} from '@polkadot/util';
import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
-import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper, PolkadexHelper} from './unique';
+import {UniqueHelper, ChainHelperBase, ChainHelperBaseConstructor, HelperGroup, UniqueHelperConstructor} from './unique';
import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';
import * as defs from '../../interfaces/definitions';
import {IKeyringPair} from '@polkadot/types/types';
import {EventRecord} from '@polkadot/types/interfaces';
-import {ICrossAccountId, IPovInfo, ITransactionResult, TSigner} from './types';
+import {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types';
import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';
-import {VoidFn} from '@polkadot/api/types';
+import {SignerOptions, VoidFn} from '@polkadot/api/types';
import {Pallets} from '..';
import {spawnSync} from 'child_process';
+import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup} from './unique.xcm';
+import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, ICollectiveGroup, IFellowshipGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance';
export class SilentLogger {
log(_msg: any, _level: any): void { }
@@ -260,6 +262,192 @@
};
}
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {
+ return class extends Base {
+ constructor(...args: any[]) {
+ super(...args);
+ }
+
+ async executeExtrinsic(
+ sender: IKeyringPair,
+ extrinsic: string,
+ params: any[],
+ expectSuccess?: boolean,
+ options: Partial<SignerOptions> | null = null,
+ ): Promise<ITransactionResult> {
+ const call = this.constructApiCall(extrinsic, params);
+ const result = await super.executeExtrinsic(
+ sender,
+ 'api.tx.sudo.sudo',
+ [call],
+ expectSuccess,
+ options,
+ );
+
+ if(result.status === 'Fail') return result;
+
+ const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;
+ if(data.isErr) {
+ if(data.asErr.isModule) {
+ const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;
+ const metaError = super.getApi()?.registry.findMetaError(error);
+ throw new Error(`${metaError.section}.${metaError.name}`);
+ } else if(data.asErr.isToken) {
+ throw new Error(`Token: ${data.asErr.asToken}`);
+ }
+ // May be [object Object] in case of unhandled non-unit enum
+ throw new Error(`Misc: ${data.asErr.toHuman()}`);
+ }
+ return result;
+ }
+ async executeExtrinsicUncheckedWeight(
+ sender: IKeyringPair,
+ extrinsic: string,
+ params: any[],
+ expectSuccess?: boolean,
+ options: Partial<SignerOptions> | null = null,
+ ): Promise<ITransactionResult> {
+ const call = this.constructApiCall(extrinsic, params);
+ const result = await super.executeExtrinsic(
+ sender,
+ 'api.tx.sudo.sudoUncheckedWeight',
+ [call, {refTime: 0, proofSize: 0}],
+ expectSuccess,
+ options,
+ );
+
+ if(result.status === 'Fail') return result;
+
+ const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;
+ if(data.isErr) {
+ if(data.asErr.isModule) {
+ const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;
+ const metaError = super.getApi()?.registry.findMetaError(error);
+ throw new Error(`${metaError.section}.${metaError.name}`);
+ } else if(data.asErr.isToken) {
+ throw new Error(`Token: ${data.asErr.asToken}`);
+ }
+ // May be [object Object] in case of unhandled non-unit enum
+ throw new Error(`Misc: ${data.asErr.toHuman()}`);
+ }
+ return result;
+ }
+ };
+}
+
+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 DevUniqueHelper>(
+ executionBlockNumber: number,
+ options: ISchedulerOptions = {},
+ ) {
+ return this.schedule<T>('schedule', executionBlockNumber, options);
+ }
+
+ scheduleAfter<T extends DevUniqueHelper>(
+ 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());
+ }
+}
+
export class DevUniqueHelper extends UniqueHelper {
/**
* Arrange methods for tests
@@ -269,6 +457,16 @@
admin: AdminGroup;
session: SessionGroup;
testUtils: TestUtilGroup;
+ foreignAssets: ForeignAssetsGroup;
+ xcm: XcmGroup<UniqueHelper>;
+ xTokens: XTokensGroup<UniqueHelper>;
+ tokens: TokensGroup<UniqueHelper>;
+ scheduler: SchedulerGroup;
+ collatorSelection: CollatorSelectionGroup;
+ council: ICollectiveGroup;
+ technicalCommittee: ICollectiveGroup;
+ fellowship: IFellowshipGroup;
+ democracy: DemocracyGroup;
constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
options.helperBase = options.helperBase ?? DevUniqueHelper;
@@ -279,6 +477,25 @@
this.admin = new AdminGroup(this);
this.testUtils = new TestUtilGroup(this);
this.session = new SessionGroup(this);
+ this.foreignAssets = new ForeignAssetsGroup(this);
+ this.xcm = new XcmGroup(this, 'polkadotXcm');
+ this.xTokens = new XTokensGroup(this);
+ this.tokens = new TokensGroup(this);
+ this.scheduler = new SchedulerGroup(this);
+ this.collatorSelection = new CollatorSelectionGroup(this);
+ this.council = {
+ collective: new CollectiveGroup(this, 'council'),
+ membership: new CollectiveMembershipGroup(this, 'councilMembership'),
+ };
+ this.technicalCommittee = {
+ collective: new CollectiveGroup(this, 'technicalCommittee'),
+ membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),
+ };
+ this.fellowship = {
+ collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),
+ referenda: new ReferendaGroup(this, 'fellowshipReferenda'),
+ };
+ this.democracy = new DemocracyGroup(this);
}
async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
@@ -326,6 +543,11 @@
this.network = await UniqueHelper.detectNetwork(this.api);
this.wsEndpoint = wsEndpoint;
}
+ getSudo<T extends DevUniqueHelper>() {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const SudoHelperType = SudoHelper(this.helperBase);
+ return this.clone(SudoHelperType) as T;
+ }
}
export class DevRelayHelper extends RelayHelper {
@@ -386,19 +608,16 @@
super(logger, options);
this.wait = new WaitGroup(this);
}
-}
-
-export class DevShidenHelper extends AstarHelper {
- wait: WaitGroup;
- constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
- options.helperBase = options.helperBase ?? DevShidenHelper;
-
- super(logger, options);
- this.wait = new WaitGroup(this);
+ getSudo<T extends AstarHelper>() {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const SudoHelperType = SudoHelper(this.helperBase);
+ return this.clone(SudoHelperType) as T;
}
}
+export class DevShidenHelper extends DevAstarHelper { }
+
export class DevAcalaHelper extends AcalaHelper {
wait: WaitGroup;
@@ -408,6 +627,11 @@
super(logger, options);
this.wait = new WaitGroup(this);
}
+ getSudo<T extends AcalaHelper>() {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const SudoHelperType = SudoHelper(this.helperBase);
+ return this.clone(SudoHelperType) as T;
+ }
}
export class DevPolkadexHelper extends PolkadexHelper {
@@ -418,6 +642,12 @@
super(logger, options);
this.wait = new WaitGroup(this);
}
+
+ getSudo<T extends PolkadexHelper>() {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const SudoHelperType = SudoHelper(this.helperBase);
+ return this.clone(SudoHelperType) as T;
+ }
}
export class DevKaruraHelper extends DevAcalaHelper {}
@@ -1211,3 +1441,63 @@
}));
}
}
+
+// 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,
+ );
+ }
+ };
+}
\ No newline at end of file
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.tsdiffbeforeafterboth39 TSigner,39 TSigner,40 TSubstrateAccount,40 TSubstrateAccount,41 TNetworks,41 TNetworks,42 IForeignAssetMetadata,43 AcalaAssetMetadata,44 MoonbeamAssetInfo,45 DemocracyStandardAccountVote,46 IEthCrossAccountId,42 IEthCrossAccountId,47 IPhasicEvent,48} from './types';43} from './types';49import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';44import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';50import type {Vec} from '@polkadot/types-codec';45import type {Vec} from '@polkadot/types-codec';51import {FrameSystemEventRecord, PalletDemocracyConviction} from '@polkadot/types/lookup';46import {FrameSystemEventRecord} from '@polkadot/types/lookup';524753export class CrossAccountId {48export class CrossAccountId {54 Substrate!: TSubstrateAccount;49 Substrate!: TSubstrateAccount;818}813}819814820815821class HelperGroup<T extends ChainHelperBase> {816export class HelperGroup<T extends ChainHelperBase> {822 helper: T;817 helper: T;823818824 constructor(uniqueHelper: T) {819 constructor(uniqueHelper: T) {2373 }2368 }2374}2369}237523702376class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2371export class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2377 /**2372 /**2378 * Get substrate address balance2373 * Get substrate address balance2379 * @param address substrate address2374 * @param address substrate address2440 }2435 }2441}2436}244224372443class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2438export class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2444 /**2439 /**2445 * Get ethereum address balance2440 * Get ethereum address balance2446 * @param address ethereum address2441 * @param address ethereum address2867 }2863 }2868}2864}286928652870class SchedulerGroup extends HelperGroup<UniqueHelper> {2871 constructor(helper: UniqueHelper) {2872 super(helper);2873 }28742875 cancelScheduled(signer: TSigner, scheduledId: string) {2876 return this.helper.executeExtrinsic(2877 signer,2878 'api.tx.scheduler.cancelNamed',2879 [scheduledId],2880 true,2881 );2882 }28832884 changePriority(signer: TSigner, scheduledId: string, priority: number) {2885 return this.helper.executeExtrinsic(2886 signer,2887 'api.tx.scheduler.changeNamedPriority',2888 [scheduledId, priority],2889 true,2890 );2891 }28922893 scheduleAt<T extends UniqueHelper>(2894 executionBlockNumber: number,2895 options: ISchedulerOptions = {},2896 ) {2897 return this.schedule<T>('schedule', executionBlockNumber, options);2898 }28992900 scheduleAfter<T extends UniqueHelper>(2901 blocksBeforeExecution: number,2902 options: ISchedulerOptions = {},2903 ) {2904 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2905 }29062907 schedule<T extends UniqueHelper>(2908 scheduleFn: 'schedule' | 'scheduleAfter',2909 blocksNum: number,2910 options: ISchedulerOptions = {},2911 ) {2912 // eslint-disable-next-line @typescript-eslint/naming-convention2913 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2914 return this.helper.clone(ScheduledHelperType, {2915 scheduleFn,2916 blocksNum,2917 options,2918 }) as T;2919 }2920}29212922class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {2923 //todo:collator documentation2924 addInvulnerable(signer: TSigner, address: string) {2925 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);2926 }29272928 removeInvulnerable(signer: TSigner, address: string) {2929 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);2930 }29312932 async getInvulnerables(): Promise<string[]> {2933 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());2934 }29352936 /** and also total max invulnerables */2937 maxCollators(): number {2938 return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);2939 }29402941 async getDesiredCollators(): Promise<number> {2942 return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();2943 }29442945 setLicenseBond(signer: TSigner, amount: bigint) {2946 return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);2947 }29482949 async getLicenseBond(): Promise<bigint> {2950 return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();2951 }29522953 obtainLicense(signer: TSigner) {2954 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);2955 }29562957 releaseLicense(signer: TSigner) {2958 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);2959 }29602961 forceReleaseLicense(signer: TSigner, released: string) {2962 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);2963 }29642965 async hasLicense(address: string): Promise<bigint> {2966 return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();2967 }29682969 onboard(signer: TSigner) {2970 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);2971 }29722973 offboard(signer: TSigner) {2974 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);2975 }29762977 async getCandidates(): Promise<string[]> {2978 return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());2979 }2980}29812982class CollectiveGroup extends HelperGroup<UniqueHelper> {2983 /**2984 * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee'2985 */2986 private collective: string;29872988 constructor(helper: UniqueHelper, collective: string) {2989 super(helper);2990 this.collective = collective;2991 }29922993 /**2994 * Check the result of a proposal execution for the success of the underlying proposed extrinsic.2995 * @param events events of the proposal execution2996 * @returns proposal hash2997 */2998 private checkExecutedEvent(events: IPhasicEvent[]): string {2999 const executionEvents = events.filter(x =>3000 x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted'));30013002 if(executionEvents.length != 1) {3003 if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0)3004 throw new Error(`Disapproved by ${this.collective}`);3005 else3006 throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`);3007 }30083009 const result = (executionEvents[0].event.data as any).result;30103011 if(result.isErr) {3012 if(result.asErr.isModule) {3013 const error = result.asErr.asModule;3014 const metaError = this.helper.getApi()?.registry.findMetaError(error);3015 throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`);3016 } else {3017 throw new Error('Proposal execution failed with ' + result.asErr.toHuman());3018 }3019 }30203021 return (executionEvents[0].event.data as any).proposalHash;3022 }30233024 /**3025 * Returns an array of members' addresses.3026 */3027 async getMembers() {3028 return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman();3029 }30303031 /**3032 * Returns the optional address of the prime member of the collective.3033 */3034 async getPrimeMember() {3035 return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman();3036 }30373038 /**3039 * Returns an array of proposal hashes that are currently active for this collective.3040 */3041 async getProposals() {3042 return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman();3043 }30443045 /**3046 * Returns the call originally encoded under the specified hash.3047 * @param hash h256-encoded proposal3048 * @returns the optional call that the proposal hash stands for.3049 */3050 async getProposalCallOf(hash: string) {3051 return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman();3052 }30533054 /**3055 * Returns the total number of proposals so far.3056 */3057 async getTotalProposalsCount() {3058 return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber();3059 }30603061 /**3062 * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately.3063 * @param signer keyring of the proposer3064 * @param proposal constructed call to be executed if the proposal is successful3065 * @param voteThreshold minimal number of votes for the proposal to be verified and executed3066 * @param lengthBound byte length of the encoded call3067 * @returns promise of extrinsic execution and its result3068 */3069 async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) {3070 return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]);3071 }30723073 /**3074 * Casts a vote to either approve or reject a proposal.3075 * @param signer keyring of the voter3076 * @param proposalHash hash of the proposal to be voted for3077 * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors3078 * @param approve aye or nay3079 * @returns promise of extrinsic execution and its result3080 */3081 vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {3082 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]);3083 }30843085 /**3086 * Executes a call immediately as a member of the collective. Needed for the Member origin.3087 * @param signer keyring of the executor member3088 * @param proposal constructed call to be executed by the member3089 * @param lengthBound byte length of the encoded call3090 * @returns promise of extrinsic execution3091 */3092 async execute(signer: TSigner, proposal: any, lengthBound = 10000) {3093 const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]);3094 this.checkExecutedEvent(result.result.events);3095 return result;3096 }30973098 /**3099 * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing.3100 * @param signer keyring of the executor. Can be absolutely anyone.3101 * @param proposalHash hash of the proposal to close3102 * @param proposalIndex index of the proposal generated on its creation3103 * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call.3104 * @param lengthBound byte length of the encoded call3105 * @returns promise of extrinsic execution and its result3106 */3107 async close(3108 signer: TSigner,3109 proposalHash: string,3110 proposalIndex: number,3111 weightBound: [number, number] | any = [20_000_000_000, 1000_000],3112 lengthBound = 10_000,3113 ) {3114 const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [3115 proposalHash,3116 proposalIndex,3117 weightBound,3118 lengthBound,3119 ]);3120 this.checkExecutedEvent(result.result.events);3121 return result;3122 }31233124 /**3125 * Shut down a proposal, regardless of its current state.3126 * @param signer keyring of the disapprover. Must be root3127 * @param proposalHash hash of the proposal to close3128 * @returns promise of extrinsic execution and its result3129 */3130 disapproveProposal(signer: TSigner, proposalHash: string) {3131 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]);3132 }3133}31343135class CollectiveMembershipGroup extends HelperGroup<UniqueHelper> {3136 /**3137 * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership'3138 */3139 private membership: string;31403141 constructor(helper: UniqueHelper, membership: string) {3142 super(helper);3143 this.membership = membership;3144 }31453146 /**3147 * Returns an array of members' addresses according to the membership pallet's perception.3148 * Note that it does not recognize the original pallet's members set with `setMembers()`.3149 */3150 async getMembers() {3151 return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman();3152 }31533154 /**3155 * Returns the optional address of the prime member of the collective.3156 */3157 async getPrimeMember() {3158 return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman();3159 }31603161 /**3162 * Add a member to the collective.3163 * @param signer keyring of the setter. Must be root3164 * @param member address of the member to add3165 * @returns promise of extrinsic execution and its result3166 */3167 addMember(signer: TSigner, member: string) {3168 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]);3169 }31703171 addMemberCall(member: string) {3172 return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]);3173 }31743175 /**3176 * Remove a member from the collective.3177 * @param signer keyring of the setter. Must be root3178 * @param member address of the member to remove3179 * @returns promise of extrinsic execution and its result3180 */3181 removeMember(signer: TSigner, member: string) {3182 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]);3183 }31843185 removeMemberCall(member: string) {3186 return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]);3187 }31883189 /**3190 * Set members of the collective to the given list of addresses.3191 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3192 * @param members addresses of the members to set3193 * @returns promise of extrinsic execution and its result3194 */3195 resetMembers(signer: TSigner, members: string[]) {3196 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]);3197 }31983199 /**3200 * Set the collective's prime member to the given address.3201 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3202 * @param prime address of the prime member of the collective3203 * @returns promise of extrinsic execution and its result3204 */3205 setPrime(signer: TSigner, prime: string) {3206 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]);3207 }32083209 setPrimeCall(member: string) {3210 return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]);3211 }32123213 /**3214 * Remove the collective's prime member.3215 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3216 * @returns promise of extrinsic execution and its result3217 */3218 clearPrime(signer: TSigner) {3219 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []);3220 }32213222 clearPrimeCall() {3223 return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []);3224 }3225}32263227class RankedCollectiveGroup extends HelperGroup<UniqueHelper> {3228 /**3229 * Pallet name to make an API call to. Examples: 'FellowshipCollective'3230 */3231 private collective: string;32323233 constructor(helper: UniqueHelper, collective: string) {3234 super(helper);3235 this.collective = collective;3236 }32373238 addMember(signer: TSigner, newMember: string) {3239 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]);3240 }32413242 addMemberCall(newMember: string) {3243 return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]);3244 }32453246 removeMember(signer: TSigner, member: string, minRank: number) {3247 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]);3248 }32493250 removeMemberCall(newMember: string, minRank: number) {3251 return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]);3252 }32533254 promote(signer: TSigner, member: string) {3255 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]);3256 }32573258 promoteCall(member: string) {3259 return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]);3260 }32613262 demote(signer: TSigner, member: string) {3263 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]);3264 }32653266 demoteCall(newMember: string) {3267 return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]);3268 }32693270 vote(signer: TSigner, pollIndex: number, aye: boolean) {3271 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]);3272 }32733274 async getMembers() {3275 return (await this.helper.getApi().query.fellowshipCollective.members.keys())3276 .map((key) => key.args[0].toString());3277 }32783279 async getMemberRank(member: string) {3280 return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank;3281 }3282}32833284class ReferendaGroup extends HelperGroup<UniqueHelper> {3285 /**3286 * Pallet name to make an API call to. Examples: 'FellowshipReferenda'3287 */3288 private referenda: string;32893290 constructor(helper: UniqueHelper, referenda: string) {3291 super(helper);3292 this.referenda = referenda;3293 }32943295 submit(3296 signer: TSigner,3297 proposalOrigin: string,3298 proposal: any,3299 enactmentMoment: any,3300 ) {3301 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [3302 {Origins: proposalOrigin},3303 proposal,3304 enactmentMoment,3305 ]);3306 }33073308 placeDecisionDeposit(signer: TSigner, referendumIndex: number) {3309 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]);3310 }33113312 cancel(signer: TSigner, referendumIndex: number) {3313 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]);3314 }33153316 cancelCall(referendumIndex: number) {3317 return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]);3318 }33193320 async referendumInfo(referendumIndex: number) {3321 return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON();3322 }33233324 async enactmentEventId(referendumIndex: number) {3325 const api = await this.helper.getApi();33263327 const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a();3328 return blake2AsHex(bytes, 256);3329 }3330}33313332export interface IFellowshipGroup {3333 collective: RankedCollectiveGroup;3334 referenda: ReferendaGroup;3335}33363337export interface ICollectiveGroup {3338 collective: CollectiveGroup;3339 membership: CollectiveMembershipGroup;3340}33413342class DemocracyGroup extends HelperGroup<UniqueHelper> {3343 // todo displace proposal into types?3344 propose(signer: TSigner, call: any, deposit: bigint) {3345 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3346 }33473348 proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) {3349 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]);3350 }33513352 proposeCall(call: any, deposit: bigint) {3353 return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3354 }33553356 second(signer: TSigner, proposalIndex: number) {3357 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]);3358 }33593360 externalPropose(signer: TSigner, proposalCall: any) {3361 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3362 }33633364 externalProposeMajority(signer: TSigner, proposalCall: any) {3365 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3366 }33673368 externalProposeDefault(signer: TSigner, proposalCall: any) {3369 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3370 }33713372 externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) {3373 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);3374 }33753376 externalProposeCall(proposalCall: any) {3377 return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3378 }33793380 externalProposeMajorityCall(proposalCall: any) {3381 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3382 }33833384 externalProposeDefaultCall(proposalCall: any) {3385 return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3386 }33873388 externalProposeDefaultWithPreimageCall(preimage: string) {3389 return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);3390 }33913392 // ... and blacklist external proposal hash.3393 vetoExternal(signer: TSigner, proposalHash: string) {3394 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]);3395 }33963397 vetoExternalCall(proposalHash: string) {3398 return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]);3399 }34003401 blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) {3402 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3403 }34043405 blacklistCall(proposalHash: string, referendumIndex: number | null = null) {3406 return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3407 }34083409 // proposal. CancelProposalOrigin (root or all techcom)3410 cancelProposal(signer: TSigner, proposalIndex: number) {3411 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]);3412 }34133414 cancelProposalCall(proposalIndex: number) {3415 return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]);3416 }34173418 clearPublicProposals(signer: TSigner) {3419 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []);3420 }34213422 fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) {3423 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3424 }34253426 fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) {3427 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3428 }34293430 // referendum. CancellationOrigin (TechCom member)3431 emergencyCancel(signer: TSigner, referendumIndex: number) {3432 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]);3433 }34343435 emergencyCancelCall(referendumIndex: number) {3436 return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]);3437 }34383439 vote(signer: TSigner, referendumIndex: number, vote: any) {3440 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]);3441 }34423443 removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) {3444 if(targetAccount) {3445 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]);3446 } else {3447 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]);3448 }3449 }34503451 unlock(signer: TSigner, targetAccount: string) {3452 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]);3453 }34543455 delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) {3456 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]);3457 }34583459 undelegate(signer: TSigner) {3460 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []);3461 }34623463 async referendumInfo(referendumIndex: number) {3464 return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON();3465 }34663467 async publicProposals() {3468 return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON();3469 }34703471 async findPublicProposal(proposalIndex: number) {3472 const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex);34733474 return proposalInfo ? proposalInfo[1] : null;3475 }34763477 async expectPublicProposal(proposalIndex: number) {3478 const proposal = await this.findPublicProposal(proposalIndex);34793480 if(proposal) {3481 return proposal;3482 } else {3483 throw Error(`Proposal #${proposalIndex} is expected to exist`);3484 }3485 }34863487 async getExternalProposal() {3488 return (await this.helper.callRpc('api.query.democracy.nextExternal', []));3489 }34903491 async expectExternalProposal() {3492 const proposal = await this.getExternalProposal();34933494 if(proposal) {3495 return proposal;3496 } else {3497 throw Error('An external proposal is expected to exist');3498 }3499 }35003501 /* setMetadata? */35023503 /* todo?3504 referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {3505 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);3506 }*/3507}350828663509class PreimageGroup extends HelperGroup<UniqueHelper> {2867class PreimageGroup extends HelperGroup<UniqueHelper> {3510 async getPreimageInfo(h256: string) {2868 async getPreimageInfo(h256: string) {3575 }2933 }3576}2934}35773578class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {3579 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {3580 await this.helper.executeExtrinsic(3581 signer,3582 'api.tx.foreignAssets.registerForeignAsset',3583 [ownerAddress, location, metadata],3584 true,3585 );3586 }35873588 async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {3589 await this.helper.executeExtrinsic(3590 signer,3591 'api.tx.foreignAssets.updateForeignAsset',3592 [foreignAssetId, location, metadata],3593 true,3594 );3595 }3596}35973598class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {3599 palletName: string;36003601 constructor(helper: T, palletName: string) {3602 super(helper);36033604 this.palletName = palletName;3605 }36063607 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {3608 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);3609 }36103611 async setSafeXcmVersion(signer: TSigner, version: number) {3612 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true);3613 }36143615 async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {3616 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);3617 }36183619 async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) {3620 const destinationContent = {3621 parents: 0,3622 interior: {3623 X1: {3624 Parachain: destinationParaId,3625 },3626 },3627 };36283629 const beneficiaryContent = {3630 parents: 0,3631 interior: {3632 X1: {3633 AccountId32: {3634 network: 'Any',3635 id: targetAccount,3636 },3637 },3638 },3639 };36403641 const assetsContent = [3642 {3643 id: {3644 Concrete: {3645 parents: 0,3646 interior: 'Here',3647 },3648 },3649 fun: {3650 Fungible: amount,3651 },3652 },3653 ];36543655 let destination;3656 let beneficiary;3657 let assets;36583659 if(xcmVersion == 2) {3660 destination = {V1: destinationContent};3661 beneficiary = {V1: beneficiaryContent};3662 assets = {V1: assetsContent};36633664 } else if(xcmVersion == 3) {3665 destination = {V2: destinationContent};3666 beneficiary = {V2: beneficiaryContent};3667 assets = {V2: assetsContent};36683669 } else {3670 throw Error('Unknown XCM version: ' + xcmVersion);3671 }36723673 const feeAssetItem = 0;36743675 await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);3676 }36773678 async send(signer: IKeyringPair, destination: any, message: any) {3679 await this.helper.executeExtrinsic(3680 signer,3681 `api.tx.${this.palletName}.send`,3682 [3683 destination,3684 message,3685 ],3686 true,3687 );3688 }3689}36903691class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3692 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {3693 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);3694 }36953696 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {3697 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);3698 }36993700 async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {3701 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);3702 }3703}37043705class PolkadexXcmHelperGroup<T extends ChainHelperBase> extends HelperGroup<T> {3706 async whitelistToken(signer: TSigner, assetId: any) {3707 await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);3708 }3709}37103711class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3712 async accounts(address: string, currencyId: any) {3713 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;3714 return BigInt(free);3715 }3716}37173718class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {3719 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {3720 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);3721 }37223723 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {3724 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);3725 }37263727 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {3728 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);3729 }37303731 async account(assetId: string | number, address: string) {3732 const accountAsset = (3733 await this.helper.callRpc('api.query.assets.account', [assetId, address])3734 ).toJSON()! as any;37353736 if(accountAsset !== null) {3737 return BigInt(accountAsset['balance']);3738 } else {3739 return null;3740 }3741 }3742}374329353744class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {2936class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {3745 async batch(signer: TSigner, txs: any[]) {2937 async batch(signer: TSigner, txs: any[]) {3755 }2947 }3756}2948}37573758class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {3759 async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {3760 await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);3761 }3762}37633764class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {3765 makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {3766 const apiPrefix = 'api.tx.assetManager.';37673768 const registerTx = this.helper.constructApiCall(3769 apiPrefix + 'registerForeignAsset',3770 [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],3771 );37723773 const setUnitsTx = this.helper.constructApiCall(3774 apiPrefix + 'setAssetUnitsPerSecond',3775 [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],3776 );37773778 const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);3779 const encodedProposal = batchCall?.method.toHex() || '';3780 return encodedProposal;3781 }37823783 async assetTypeId(location: any) {3784 return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);3785 }3786}37873788class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {3789 notePreimagePallet: string;37903791 constructor(helper: MoonbeamHelper, options: { [key: string]: any } = {}) {3792 super(helper);3793 this.notePreimagePallet = options.notePreimagePallet;3794 }37953796 async notePreimage(signer: TSigner, encodedProposal: string) {3797 await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true);3798 }37993800 externalProposeMajority(proposal: any) {3801 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]);3802 }38033804 fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {3805 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3806 }38073808 async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {3809 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);3810 }3811}38123813class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {3814 collective: string;38153816 constructor(helper: MoonbeamHelper, collective: string) {3817 super(helper);38183819 this.collective = collective;3820 }38213822 async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {3823 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);3824 }38253826 async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {3827 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);3828 }38293830 async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) {3831 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);3832 }38333834 async proposalCount() {3835 return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));3836 }3837}383829493839export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;2950export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;3840export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;2951export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;3846 rft: RFTGroup;2957 rft: RFTGroup;3847 ft: FTGroup;2958 ft: FTGroup;3848 staking: StakingGroup;2959 staking: StakingGroup;3849 scheduler: SchedulerGroup;3850 collatorSelection: CollatorSelectionGroup;3851 council: ICollectiveGroup;3852 technicalCommittee: ICollectiveGroup;3853 fellowship: IFellowshipGroup;3854 democracy: DemocracyGroup;3855 preimage: PreimageGroup;2960 preimage: PreimageGroup;3856 foreignAssets: ForeignAssetsGroup;3857 xcm: XcmGroup<UniqueHelper>;3858 xTokens: XTokensGroup<UniqueHelper>;3859 tokens: TokensGroup<UniqueHelper>;3860 utility: UtilityGroup<UniqueHelper>;2961 utility: UtilityGroup<UniqueHelper>;386129623862 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {2963 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3868 this.rft = new RFTGroup(this);2969 this.rft = new RFTGroup(this);3869 this.ft = new FTGroup(this);2970 this.ft = new FTGroup(this);3870 this.staking = new StakingGroup(this);2971 this.staking = new StakingGroup(this);3871 this.scheduler = new SchedulerGroup(this);3872 this.collatorSelection = new CollatorSelectionGroup(this);3873 this.council = {3874 collective: new CollectiveGroup(this, 'council'),3875 membership: new CollectiveMembershipGroup(this, 'councilMembership'),3876 };3877 this.technicalCommittee = {3878 collective: new CollectiveGroup(this, 'technicalCommittee'),3879 membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),3880 };3881 this.fellowship = {3882 collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),3883 referenda: new ReferendaGroup(this, 'fellowshipReferenda'),3884 };3885 this.democracy = new DemocracyGroup(this);3886 this.preimage = new PreimageGroup(this);2972 this.preimage = new PreimageGroup(this);3887 this.foreignAssets = new ForeignAssetsGroup(this);3888 this.xcm = new XcmGroup(this, 'polkadotXcm');3889 this.xTokens = new XTokensGroup(this);3890 this.tokens = new TokensGroup(this);3891 this.utility = new UtilityGroup(this);2973 this.utility = new UtilityGroup(this);3892 }2974 }38933894 getSudo<T extends UniqueHelper>() {3895 // eslint-disable-next-line @typescript-eslint/naming-convention3896 const SudoHelperType = SudoHelper(this.helperBase);3897 return this.clone(SudoHelperType) as T;3898 }3899}2975}39003901export class XcmChainHelper extends ChainHelperBase {3902 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {3903 const wsProvider = new WsProvider(wsEndpoint);3904 this.api = new ApiPromise({3905 provider: wsProvider,3906 });3907 await this.api.isReadyOrError;3908 this.network = await UniqueHelper.detectNetwork(this.api);3909 }3910}39113912export class RelayHelper extends XcmChainHelper {3913 balance: SubstrateBalanceGroup<RelayHelper>;3914 xcm: XcmGroup<RelayHelper>;39153916 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3917 super(logger, options.helperBase ?? RelayHelper);39183919 this.balance = new SubstrateBalanceGroup(this);3920 this.xcm = new XcmGroup(this, 'xcmPallet');3921 }3922}39233924export class WestmintHelper extends XcmChainHelper {3925 balance: SubstrateBalanceGroup<WestmintHelper>;3926 xcm: XcmGroup<WestmintHelper>;3927 assets: AssetsGroup<WestmintHelper>;3928 xTokens: XTokensGroup<WestmintHelper>;39293930 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3931 super(logger, options.helperBase ?? WestmintHelper);39323933 this.balance = new SubstrateBalanceGroup(this);3934 this.xcm = new XcmGroup(this, 'polkadotXcm');3935 this.assets = new AssetsGroup(this);3936 this.xTokens = new XTokensGroup(this);3937 }3938}39393940export class MoonbeamHelper extends XcmChainHelper {3941 balance: EthereumBalanceGroup<MoonbeamHelper>;3942 assetManager: MoonbeamAssetManagerGroup;3943 assets: AssetsGroup<MoonbeamHelper>;3944 xTokens: XTokensGroup<MoonbeamHelper>;3945 democracy: MoonbeamDemocracyGroup;3946 collective: {3947 council: MoonbeamCollectiveGroup,3948 techCommittee: MoonbeamCollectiveGroup,3949 };39503951 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3952 super(logger, options.helperBase ?? MoonbeamHelper);39533954 this.balance = new EthereumBalanceGroup(this);3955 this.assetManager = new MoonbeamAssetManagerGroup(this);3956 this.assets = new AssetsGroup(this);3957 this.xTokens = new XTokensGroup(this);3958 this.democracy = new MoonbeamDemocracyGroup(this, options);3959 this.collective = {3960 council: new MoonbeamCollectiveGroup(this, 'councilCollective'),3961 techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),3962 };3963 }3964}39653966export class AstarHelper extends XcmChainHelper {3967 balance: SubstrateBalanceGroup<AstarHelper>;3968 assets: AssetsGroup<AstarHelper>;3969 xcm: XcmGroup<AstarHelper>;39703971 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3972 super(logger, options.helperBase ?? AstarHelper);39733974 this.balance = new SubstrateBalanceGroup(this);3975 this.assets = new AssetsGroup(this);3976 this.xcm = new XcmGroup(this, 'polkadotXcm');3977 }39783979 getSudo<T extends UniqueHelper>() {3980 // eslint-disable-next-line @typescript-eslint/naming-convention3981 const SudoHelperType = SudoHelper(this.helperBase);3982 return this.clone(SudoHelperType) as T;3983 }3984}39853986export class AcalaHelper extends XcmChainHelper {3987 balance: SubstrateBalanceGroup<AcalaHelper>;3988 assetRegistry: AcalaAssetRegistryGroup;3989 xTokens: XTokensGroup<AcalaHelper>;3990 tokens: TokensGroup<AcalaHelper>;3991 xcm: XcmGroup<AcalaHelper>;39923993 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3994 super(logger, options.helperBase ?? AcalaHelper);39953996 this.balance = new SubstrateBalanceGroup(this);3997 this.assetRegistry = new AcalaAssetRegistryGroup(this);3998 this.xTokens = new XTokensGroup(this);3999 this.tokens = new TokensGroup(this);4000 this.xcm = new XcmGroup(this, 'polkadotXcm');4001 }40024003 getSudo<T extends AcalaHelper>() {4004 // eslint-disable-next-line @typescript-eslint/naming-convention4005 const SudoHelperType = SudoHelper(this.helperBase);4006 return this.clone(SudoHelperType) as T;4007 }4008}40094010export class PolkadexHelper extends XcmChainHelper {4011 assets: AssetsGroup<PolkadexHelper>;4012 balance: SubstrateBalanceGroup<PolkadexHelper>;4013 xTokens: XTokensGroup<PolkadexHelper>;4014 xcm: XcmGroup<PolkadexHelper>;4015 xcmHelper: PolkadexXcmHelperGroup<PolkadexHelper>;40164017 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {4018 super(logger, options.helperBase ?? PolkadexHelper);40194020 this.assets = new AssetsGroup(this);4021 this.balance = new SubstrateBalanceGroup(this);4022 this.xTokens = new XTokensGroup(this);4023 this.xcm = new XcmGroup(this, 'polkadotXcm');4024 this.xcmHelper = new PolkadexXcmHelperGroup(this);4025 }40264027 getSudo<T extends PolkadexHelper>() {4028 // eslint-disable-next-line @typescript-eslint/naming-convention4029 const SudoHelperType = SudoHelper(this.helperBase);4030 return this.clone(SudoHelperType) as T;4031 }4032}40334034// eslint-disable-next-line @typescript-eslint/naming-convention4035function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {4036 return class extends Base {4037 scheduleFn: 'schedule' | 'scheduleAfter';4038 blocksNum: number;4039 options: ISchedulerOptions;40404041 constructor(...args: any[]) {4042 const logger = args[0] as ILogger;4043 const options = args[1] as {4044 scheduleFn: 'schedule' | 'scheduleAfter',4045 blocksNum: number,4046 options: ISchedulerOptions4047 };40484049 super(logger);40504051 this.scheduleFn = options.scheduleFn;4052 this.blocksNum = options.blocksNum;4053 this.options = options.options;4054 }40554056 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {4057 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);40584059 const mandatorySchedArgs = [4060 this.blocksNum,4061 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,4062 this.options.priority ?? null,4063 scheduledTx,4064 ];40654066 let schedArgs;4067 let scheduleFn;40684069 if(this.options.scheduledId) {4070 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];40714072 if(this.scheduleFn == 'schedule') {4073 scheduleFn = 'scheduleNamed';4074 } else if(this.scheduleFn == 'scheduleAfter') {4075 scheduleFn = 'scheduleNamedAfter';4076 }4077 } else {4078 schedArgs = mandatorySchedArgs;4079 scheduleFn = this.scheduleFn;4080 }40814082 const extrinsic = 'api.tx.scheduler.' + scheduleFn;40834084 return super.executeExtrinsic(4085 sender,4086 extrinsic as any,4087 schedArgs,4088 expectSuccess,4089 );4090 }4091 };4092}40934094// eslint-disable-next-line @typescript-eslint/naming-convention4095function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {4096 return class extends Base {4097 constructor(...args: any[]) {4098 super(...args);4099 }41004101 async executeExtrinsic(4102 sender: IKeyringPair,4103 extrinsic: string,4104 params: any[],4105 expectSuccess?: boolean,4106 options: Partial<SignerOptions> | null = null,4107 ): Promise<ITransactionResult> {4108 const call = this.constructApiCall(extrinsic, params);4109 const result = await super.executeExtrinsic(4110 sender,4111 'api.tx.sudo.sudo',4112 [call],4113 expectSuccess,4114 options,4115 );41164117 if(result.status === 'Fail') return result;41184119 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;4120 if(data.isErr) {4121 if(data.asErr.isModule) {4122 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;4123 const metaError = super.getApi()?.registry.findMetaError(error);4124 throw new Error(`${metaError.section}.${metaError.name}`);4125 } else if(data.asErr.isToken) {4126 throw new Error(`Token: ${data.asErr.asToken}`);4127 }4128 // May be [object Object] in case of unhandled non-unit enum4129 throw new Error(`Misc: ${data.asErr.toHuman()}`);4130 }4131 return result;4132 }4133 async executeExtrinsicUncheckedWeight(4134 sender: IKeyringPair,4135 extrinsic: string,4136 params: any[],4137 expectSuccess?: boolean,4138 options: Partial<SignerOptions> | null = null,4139 ): Promise<ITransactionResult> {4140 const call = this.constructApiCall(extrinsic, params);4141 const result = await super.executeExtrinsic(4142 sender,4143 'api.tx.sudo.sudoUncheckedWeight',4144 [call, {refTime: 0, proofSize: 0}],4145 expectSuccess,4146 options,4147 );41484149 if(result.status === 'Fail') return result;41504151 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;4152 if(data.isErr) {4153 if(data.asErr.isModule) {4154 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;4155 const metaError = super.getApi()?.registry.findMetaError(error);4156 throw new Error(`${metaError.section}.${metaError.name}`);4157 } else if(data.asErr.isToken) {4158 throw new Error(`Token: ${data.asErr.asToken}`);4159 }4160 // May be [object Object] in case of unhandled non-unit enum4161 throw new Error(`Misc: ${data.asErr.toHuman()}`);4162 }4163 return result;4164 }4165 };4166}416729764168export class UniqueBaseCollection {2977export class UniqueBaseCollection {4169 helper: UniqueHelper;2978 helper: UniqueHelper;4274 return await this.helper.collection.burn(signer, this.collectionId);3083 return await this.helper.collection.burn(signer, this.collectionId);4275 }3084 }42764277 scheduleAt<T extends UniqueHelper>(4278 executionBlockNumber: number,4279 options: ISchedulerOptions = {},4280 ) {4281 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4282 return new UniqueBaseCollection(this.collectionId, scheduledHelper);4283 }42844285 scheduleAfter<T extends UniqueHelper>(4286 blocksBeforeExecution: number,4287 options: ISchedulerOptions = {},4288 ) {4289 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4290 return new UniqueBaseCollection(this.collectionId, scheduledHelper);4291 }42924293 getSudo<T extends UniqueHelper>() {4294 return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());4295 }4296}3085}4297429830864388 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3176 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);4389 }3177 }43904391 scheduleAt<T extends UniqueHelper>(4392 executionBlockNumber: number,4393 options: ISchedulerOptions = {},4394 ) {4395 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4396 return new UniqueNFTCollection(this.collectionId, scheduledHelper);4397 }43984399 scheduleAfter<T extends UniqueHelper>(4400 blocksBeforeExecution: number,4401 options: ISchedulerOptions = {},4402 ) {4403 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4404 return new UniqueNFTCollection(this.collectionId, scheduledHelper);4405 }44064407 getSudo<T extends UniqueHelper>() {4408 return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());4409 }4410}3178}4411441231794514 return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3281 return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);4515 }3282 }45164517 scheduleAt<T extends UniqueHelper>(4518 executionBlockNumber: number,4519 options: ISchedulerOptions = {},4520 ) {4521 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4522 return new UniqueRFTCollection(this.collectionId, scheduledHelper);4523 }45244525 scheduleAfter<T extends UniqueHelper>(4526 blocksBeforeExecution: number,4527 options: ISchedulerOptions = {},4528 ) {4529 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4530 return new UniqueRFTCollection(this.collectionId, scheduledHelper);4531 }45324533 getSudo<T extends UniqueHelper>() {4534 return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());4535 }4536}3283}4537453832844581 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3327 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);4582 }3328 }45834584 scheduleAt<T extends UniqueHelper>(4585 executionBlockNumber: number,4586 options: ISchedulerOptions = {},4587 ) {4588 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4589 return new UniqueFTCollection(this.collectionId, scheduledHelper);4590 }45914592 scheduleAfter<T extends UniqueHelper>(4593 blocksBeforeExecution: number,4594 options: ISchedulerOptions = {},4595 ) {4596 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4597 return new UniqueFTCollection(this.collectionId, scheduledHelper);4598 }45994600 getSudo<T extends UniqueHelper>() {4601 return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());4602 }4603}3329}4604460533304642 return this.collection.helper.util.getTokenAccount(this);3367 return this.collection.helper.util.getTokenAccount(this);4643 }3368 }46444645 scheduleAt<T extends UniqueHelper>(4646 executionBlockNumber: number,4647 options: ISchedulerOptions = {},4648 ) {4649 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4650 return new UniqueBaseToken(this.tokenId, scheduledCollection);4651 }46524653 scheduleAfter<T extends UniqueHelper>(4654 blocksBeforeExecution: number,4655 options: ISchedulerOptions = {},4656 ) {4657 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4658 return new UniqueBaseToken(this.tokenId, scheduledCollection);4659 }46604661 getSudo<T extends UniqueHelper>() {4662 return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());4663 }4664}3369}4665466633704720 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3424 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);4721 }3425 }47224723 scheduleAt<T extends UniqueHelper>(4724 executionBlockNumber: number,4725 options: ISchedulerOptions = {},4726 ) {4727 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4728 return new UniqueNFToken(this.tokenId, scheduledCollection);4729 }47304731 scheduleAfter<T extends UniqueHelper>(4732 blocksBeforeExecution: number,4733 options: ISchedulerOptions = {},4734 ) {4735 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4736 return new UniqueNFToken(this.tokenId, scheduledCollection);4737 }47384739 getSudo<T extends UniqueHelper>() {4740 return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());4741 }4742}3426}474334274744export class UniqueRFToken extends UniqueBaseToken {3428export class UniqueRFToken extends UniqueBaseToken {4809 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3493 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);4810 }3494 }48114812 scheduleAt<T extends UniqueHelper>(4813 executionBlockNumber: number,4814 options: ISchedulerOptions = {},4815 ) {4816 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4817 return new UniqueRFToken(this.tokenId, scheduledCollection);4818 }48194820 scheduleAfter<T extends UniqueHelper>(4821 blocksBeforeExecution: number,4822 options: ISchedulerOptions = {},4823 ) {4824 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4825 return new UniqueRFToken(this.tokenId, scheduledCollection);4826 }48274828 getSudo<T extends UniqueHelper>() {4829 return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());4830 }4831}3495}48323496tests/src/util/playgrounds/unique.xcm.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/util/playgrounds/unique.xcm.ts
@@ -0,0 +1,371 @@
+import {ApiPromise, WsProvider} from '@polkadot/api';
+import {IKeyringPair} from '@polkadot/types/types';
+import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from './unique';
+import {ILogger, TSigner, TSubstrateAccount} from './types';
+import {AcalaAssetMetadata, DemocracyStandardAccountVote, IForeignAssetMetadata, MoonbeamAssetInfo} from './types.xcm';
+
+
+export class XcmChainHelper extends ChainHelperBase {
+ async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
+ const wsProvider = new WsProvider(wsEndpoint);
+ this.api = new ApiPromise({
+ provider: wsProvider,
+ });
+ await this.api.isReadyOrError;
+ this.network = await UniqueHelper.detectNetwork(this.api);
+ }
+}
+
+class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {
+ async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);
+ }
+}
+
+class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {
+ makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {
+ const apiPrefix = 'api.tx.assetManager.';
+
+ const registerTx = this.helper.constructApiCall(
+ apiPrefix + 'registerForeignAsset',
+ [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],
+ );
+
+ const setUnitsTx = this.helper.constructApiCall(
+ apiPrefix + 'setAssetUnitsPerSecond',
+ [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],
+ );
+
+ const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);
+ const encodedProposal = batchCall?.method.toHex() || '';
+ return encodedProposal;
+ }
+
+ async assetTypeId(location: any) {
+ return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);
+ }
+}
+
+class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {
+ notePreimagePallet: string;
+
+ constructor(helper: MoonbeamHelper, options: { [key: string]: any } = {}) {
+ super(helper);
+ this.notePreimagePallet = options.notePreimagePallet;
+ }
+
+ async notePreimage(signer: TSigner, encodedProposal: string) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true);
+ }
+
+ externalProposeMajority(proposal: any) {
+ return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]);
+ }
+
+ fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {
+ return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
+ }
+
+ async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);
+ }
+}
+
+class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {
+ collective: string;
+
+ constructor(helper: MoonbeamHelper, collective: string) {
+ super(helper);
+
+ this.collective = collective;
+ }
+
+ async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);
+ }
+
+ async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);
+ }
+
+ async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);
+ }
+
+ async proposalCount() {
+ return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));
+ }
+}
+
+class PolkadexXcmHelperGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ async whitelistToken(signer: TSigner, assetId: any) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);
+ }
+}
+
+export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
+ async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
+ await this.helper.executeExtrinsic(
+ signer,
+ 'api.tx.foreignAssets.registerForeignAsset',
+ [ownerAddress, location, metadata],
+ true,
+ );
+ }
+
+ async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
+ await this.helper.executeExtrinsic(
+ signer,
+ 'api.tx.foreignAssets.updateForeignAsset',
+ [foreignAssetId, location, metadata],
+ true,
+ );
+ }
+}
+
+export class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ palletName: string;
+
+ constructor(helper: T, palletName: string) {
+ super(helper);
+
+ this.palletName = palletName;
+ }
+
+ async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);
+ }
+
+ async setSafeXcmVersion(signer: TSigner, version: number) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true);
+ }
+
+ async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);
+ }
+
+ async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) {
+ const destinationContent = {
+ parents: 0,
+ interior: {
+ X1: {
+ Parachain: destinationParaId,
+ },
+ },
+ };
+
+ const beneficiaryContent = {
+ parents: 0,
+ interior: {
+ X1: {
+ AccountId32: {
+ network: 'Any',
+ id: targetAccount,
+ },
+ },
+ },
+ };
+
+ const assetsContent = [
+ {
+ id: {
+ Concrete: {
+ parents: 0,
+ interior: 'Here',
+ },
+ },
+ fun: {
+ Fungible: amount,
+ },
+ },
+ ];
+
+ let destination;
+ let beneficiary;
+ let assets;
+
+ if(xcmVersion == 2) {
+ destination = {V1: destinationContent};
+ beneficiary = {V1: beneficiaryContent};
+ assets = {V1: assetsContent};
+
+ } else if(xcmVersion == 3) {
+ destination = {V2: destinationContent};
+ beneficiary = {V2: beneficiaryContent};
+ assets = {V2: assetsContent};
+
+ } else {
+ throw Error('Unknown XCM version: ' + xcmVersion);
+ }
+
+ const feeAssetItem = 0;
+
+ await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);
+ }
+
+ async send(signer: IKeyringPair, destination: any, message: any) {
+ await this.helper.executeExtrinsic(
+ signer,
+ `api.tx.${this.palletName}.send`,
+ [
+ destination,
+ message,
+ ],
+ true,
+ );
+ }
+}
+
+export class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);
+ }
+
+ async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);
+ }
+
+ async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);
+ }
+}
+
+
+
+export class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ async accounts(address: string, currencyId: any) {
+ const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;
+ return BigInt(free);
+ }
+}
+
+export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
+ }
+
+ async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);
+ }
+
+ async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
+ }
+
+ async account(assetId: string | number, address: string) {
+ const accountAsset = (
+ await this.helper.callRpc('api.query.assets.account', [assetId, address])
+ ).toJSON()! as any;
+
+ if(accountAsset !== null) {
+ return BigInt(accountAsset['balance']);
+ } else {
+ return null;
+ }
+ }
+}
+
+export class RelayHelper extends XcmChainHelper {
+ balance: SubstrateBalanceGroup<RelayHelper>;
+ xcm: XcmGroup<RelayHelper>;
+
+ constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+ super(logger, options.helperBase ?? RelayHelper);
+
+ this.balance = new SubstrateBalanceGroup(this);
+ this.xcm = new XcmGroup(this, 'xcmPallet');
+ }
+}
+
+export class WestmintHelper extends XcmChainHelper {
+ balance: SubstrateBalanceGroup<WestmintHelper>;
+ xcm: XcmGroup<WestmintHelper>;
+ assets: AssetsGroup<WestmintHelper>;
+ xTokens: XTokensGroup<WestmintHelper>;
+
+ constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+ super(logger, options.helperBase ?? WestmintHelper);
+
+ this.balance = new SubstrateBalanceGroup(this);
+ this.xcm = new XcmGroup(this, 'polkadotXcm');
+ this.assets = new AssetsGroup(this);
+ this.xTokens = new XTokensGroup(this);
+ }
+}
+
+export class MoonbeamHelper extends XcmChainHelper {
+ balance: EthereumBalanceGroup<MoonbeamHelper>;
+ assetManager: MoonbeamAssetManagerGroup;
+ assets: AssetsGroup<MoonbeamHelper>;
+ xTokens: XTokensGroup<MoonbeamHelper>;
+ democracy: MoonbeamDemocracyGroup;
+ collective: {
+ council: MoonbeamCollectiveGroup,
+ techCommittee: MoonbeamCollectiveGroup,
+ };
+
+ constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+ super(logger, options.helperBase ?? MoonbeamHelper);
+
+ this.balance = new EthereumBalanceGroup(this);
+ this.assetManager = new MoonbeamAssetManagerGroup(this);
+ this.assets = new AssetsGroup(this);
+ this.xTokens = new XTokensGroup(this);
+ this.democracy = new MoonbeamDemocracyGroup(this, options);
+ this.collective = {
+ council: new MoonbeamCollectiveGroup(this, 'councilCollective'),
+ techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),
+ };
+ }
+}
+
+export class AstarHelper extends XcmChainHelper {
+ balance: SubstrateBalanceGroup<AstarHelper>;
+ assets: AssetsGroup<AstarHelper>;
+ xcm: XcmGroup<AstarHelper>;
+
+ constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+ super(logger, options.helperBase ?? AstarHelper);
+
+ this.balance = new SubstrateBalanceGroup(this);
+ this.assets = new AssetsGroup(this);
+ this.xcm = new XcmGroup(this, 'polkadotXcm');
+ }
+}
+
+export class AcalaHelper extends XcmChainHelper {
+ balance: SubstrateBalanceGroup<AcalaHelper>;
+ assetRegistry: AcalaAssetRegistryGroup;
+ xTokens: XTokensGroup<AcalaHelper>;
+ tokens: TokensGroup<AcalaHelper>;
+ xcm: XcmGroup<AcalaHelper>;
+
+ constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+ super(logger, options.helperBase ?? AcalaHelper);
+
+ this.balance = new SubstrateBalanceGroup(this);
+ this.assetRegistry = new AcalaAssetRegistryGroup(this);
+ this.xTokens = new XTokensGroup(this);
+ this.tokens = new TokensGroup(this);
+ this.xcm = new XcmGroup(this, 'polkadotXcm');
+ }
+}
+
+export class PolkadexHelper extends XcmChainHelper {
+ assets: AssetsGroup<PolkadexHelper>;
+ balance: SubstrateBalanceGroup<PolkadexHelper>;
+ xTokens: XTokensGroup<PolkadexHelper>;
+ xcm: XcmGroup<PolkadexHelper>;
+ xcmHelper: PolkadexXcmHelperGroup<PolkadexHelper>;
+
+ constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+ super(logger, options.helperBase ?? PolkadexHelper);
+
+ this.assets = new AssetsGroup(this);
+ this.balance = new SubstrateBalanceGroup(this);
+ this.xTokens = new XTokensGroup(this);
+ this.xcm = new XcmGroup(this, 'polkadotXcm');
+ this.xcmHelper = new PolkadexXcmHelperGroup(this);
+ }
+}
+