difftreelog
tests: move scheduler to dev, move governance to separate file
in: master
7 files changed
tests/src/governance/util.tsdiffbeforeafterboth--- a/tests/src/governance/util.ts
+++ b/tests/src/governance/util.ts
@@ -2,6 +2,7 @@
import {xxhashAsHex} from '@polkadot/util-crypto';
import {usingPlaygrounds, expect} from '../util';
import {UniqueHelper} from '../util/playgrounds/unique';
+import {DevUniqueHelper} from '../util/playgrounds/unique.dev';
export const democracyLaunchPeriod = 35;
export const democracyVotingPeriod = 35;
@@ -203,7 +204,7 @@
});
}
-export async function voteUnanimouslyInFellowship(helper: UniqueHelper, fellows: IKeyringPair[][], minRank: number, referendumIndex: number) {
+export async function voteUnanimouslyInFellowship(helper: DevUniqueHelper, fellows: IKeyringPair[][], minRank: number, referendumIndex: number) {
for(let rank = minRank; rank < fellowshipRankLimit; rank++) {
for(const member of fellows[rank]) {
await helper.fellowship.collective.vote(member, referendumIndex, true);
tests/src/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -216,13 +216,6 @@
},
}
-export interface IForeignAssetMetadata {
- name?: number | Uint8Array,
- symbol?: string,
- decimals?: number,
- minimalBalance?: bigint,
-}
-
export interface DemocracySplitAccount {
aye: bigint,
nay: bigint,
tests/src/util/playgrounds/types.xcm.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/types.xcm.ts
+++ b/tests/src/util/playgrounds/types.xcm.ts
@@ -26,4 +26,11 @@
aye: boolean,
conviction: number,
},
+}
+
+export interface IForeignAssetMetadata {
+ name?: number | Uint8Array,
+ symbol?: string,
+ decimals?: number,
+ minimalBalance?: bigint,
}
\ No newline at end of file
tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -3,17 +3,18 @@
import {stringToU8a} from '@polkadot/util';
import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
-import {UniqueHelper, ChainHelperBase, ChainHelperBaseConstructor} 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 {SignerOptions, VoidFn} from '@polkadot/api/types';
import {Pallets} from '..';
import {spawnSync} from 'child_process';
-import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper} from './unique.xcm';
+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 { }
@@ -335,6 +336,118 @@
};
}
+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
@@ -344,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;
@@ -354,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> {
@@ -401,7 +543,7 @@
this.network = await UniqueHelper.detectNetwork(this.api);
this.wsEndpoint = wsEndpoint;
}
- getSudo<T extends UniqueHelper>() {
+ getSudo<T extends DevUniqueHelper>() {
// eslint-disable-next-line @typescript-eslint/naming-convention
const SudoHelperType = SudoHelper(this.helperBase);
return this.clone(SudoHelperType) as T;
@@ -1308,3 +1450,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 IEthCrossAccountId,42 IEthCrossAccountId,44 IPhasicEvent,45} from './types';43} from './types';46import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';44import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';47import type {Vec} from '@polkadot/types-codec';45import type {Vec} from '@polkadot/types-codec';48import {FrameSystemEventRecord, PalletDemocracyConviction} from '@polkadot/types/lookup';46import {FrameSystemEventRecord} from '@polkadot/types/lookup';494750export class CrossAccountId {48export class CrossAccountId {51 Substrate!: TSubstrateAccount;49 Substrate!: TSubstrateAccount;2864 }2863 }2865}2864}286628652867class SchedulerGroup extends HelperGroup<UniqueHelper> {2868 constructor(helper: UniqueHelper) {2869 super(helper);2870 }28712872 cancelScheduled(signer: TSigner, scheduledId: string) {2873 return this.helper.executeExtrinsic(2874 signer,2875 'api.tx.scheduler.cancelNamed',2876 [scheduledId],2877 true,2878 );2879 }28802881 changePriority(signer: TSigner, scheduledId: string, priority: number) {2882 return this.helper.executeExtrinsic(2883 signer,2884 'api.tx.scheduler.changeNamedPriority',2885 [scheduledId, priority],2886 true,2887 );2888 }28892890 scheduleAt<T extends UniqueHelper>(2891 executionBlockNumber: number,2892 options: ISchedulerOptions = {},2893 ) {2894 return this.schedule<T>('schedule', executionBlockNumber, options);2895 }28962897 scheduleAfter<T extends UniqueHelper>(2898 blocksBeforeExecution: number,2899 options: ISchedulerOptions = {},2900 ) {2901 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2902 }29032904 schedule<T extends UniqueHelper>(2905 scheduleFn: 'schedule' | 'scheduleAfter',2906 blocksNum: number,2907 options: ISchedulerOptions = {},2908 ) {2909 // eslint-disable-next-line @typescript-eslint/naming-convention2910 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2911 return this.helper.clone(ScheduledHelperType, {2912 scheduleFn,2913 blocksNum,2914 options,2915 }) as T;2916 }2917}29182919class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {2920 //todo:collator documentation2921 addInvulnerable(signer: TSigner, address: string) {2922 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);2923 }29242925 removeInvulnerable(signer: TSigner, address: string) {2926 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);2927 }29282929 async getInvulnerables(): Promise<string[]> {2930 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());2931 }29322933 /** and also total max invulnerables */2934 maxCollators(): number {2935 return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);2936 }29372938 async getDesiredCollators(): Promise<number> {2939 return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();2940 }29412942 setLicenseBond(signer: TSigner, amount: bigint) {2943 return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);2944 }29452946 async getLicenseBond(): Promise<bigint> {2947 return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();2948 }29492950 obtainLicense(signer: TSigner) {2951 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);2952 }29532954 releaseLicense(signer: TSigner) {2955 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);2956 }29572958 forceReleaseLicense(signer: TSigner, released: string) {2959 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);2960 }29612962 async hasLicense(address: string): Promise<bigint> {2963 return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();2964 }29652966 onboard(signer: TSigner) {2967 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);2968 }29692970 offboard(signer: TSigner) {2971 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);2972 }29732974 async getCandidates(): Promise<string[]> {2975 return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());2976 }2977}29782979class CollectiveGroup extends HelperGroup<UniqueHelper> {2980 /**2981 * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee'2982 */2983 private collective: string;29842985 constructor(helper: UniqueHelper, collective: string) {2986 super(helper);2987 this.collective = collective;2988 }29892990 /**2991 * Check the result of a proposal execution for the success of the underlying proposed extrinsic.2992 * @param events events of the proposal execution2993 * @returns proposal hash2994 */2995 private checkExecutedEvent(events: IPhasicEvent[]): string {2996 const executionEvents = events.filter(x =>2997 x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted'));29982999 if(executionEvents.length != 1) {3000 if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0)3001 throw new Error(`Disapproved by ${this.collective}`);3002 else3003 throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`);3004 }30053006 const result = (executionEvents[0].event.data as any).result;30073008 if(result.isErr) {3009 if(result.asErr.isModule) {3010 const error = result.asErr.asModule;3011 const metaError = this.helper.getApi()?.registry.findMetaError(error);3012 throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`);3013 } else {3014 throw new Error('Proposal execution failed with ' + result.asErr.toHuman());3015 }3016 }30173018 return (executionEvents[0].event.data as any).proposalHash;3019 }30203021 /**3022 * Returns an array of members' addresses.3023 */3024 async getMembers() {3025 return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman();3026 }30273028 /**3029 * Returns the optional address of the prime member of the collective.3030 */3031 async getPrimeMember() {3032 return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman();3033 }30343035 /**3036 * Returns an array of proposal hashes that are currently active for this collective.3037 */3038 async getProposals() {3039 return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman();3040 }30413042 /**3043 * Returns the call originally encoded under the specified hash.3044 * @param hash h256-encoded proposal3045 * @returns the optional call that the proposal hash stands for.3046 */3047 async getProposalCallOf(hash: string) {3048 return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman();3049 }30503051 /**3052 * Returns the total number of proposals so far.3053 */3054 async getTotalProposalsCount() {3055 return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber();3056 }30573058 /**3059 * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately.3060 * @param signer keyring of the proposer3061 * @param proposal constructed call to be executed if the proposal is successful3062 * @param voteThreshold minimal number of votes for the proposal to be verified and executed3063 * @param lengthBound byte length of the encoded call3064 * @returns promise of extrinsic execution and its result3065 */3066 async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) {3067 return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]);3068 }30693070 /**3071 * Casts a vote to either approve or reject a proposal.3072 * @param signer keyring of the voter3073 * @param proposalHash hash of the proposal to be voted for3074 * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors3075 * @param approve aye or nay3076 * @returns promise of extrinsic execution and its result3077 */3078 vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {3079 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]);3080 }30813082 /**3083 * Executes a call immediately as a member of the collective. Needed for the Member origin.3084 * @param signer keyring of the executor member3085 * @param proposal constructed call to be executed by the member3086 * @param lengthBound byte length of the encoded call3087 * @returns promise of extrinsic execution3088 */3089 async execute(signer: TSigner, proposal: any, lengthBound = 10000) {3090 const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]);3091 this.checkExecutedEvent(result.result.events);3092 return result;3093 }30943095 /**3096 * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing.3097 * @param signer keyring of the executor. Can be absolutely anyone.3098 * @param proposalHash hash of the proposal to close3099 * @param proposalIndex index of the proposal generated on its creation3100 * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call.3101 * @param lengthBound byte length of the encoded call3102 * @returns promise of extrinsic execution and its result3103 */3104 async close(3105 signer: TSigner,3106 proposalHash: string,3107 proposalIndex: number,3108 weightBound: [number, number] | any = [20_000_000_000, 1000_000],3109 lengthBound = 10_000,3110 ) {3111 const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [3112 proposalHash,3113 proposalIndex,3114 weightBound,3115 lengthBound,3116 ]);3117 this.checkExecutedEvent(result.result.events);3118 return result;3119 }31203121 /**3122 * Shut down a proposal, regardless of its current state.3123 * @param signer keyring of the disapprover. Must be root3124 * @param proposalHash hash of the proposal to close3125 * @returns promise of extrinsic execution and its result3126 */3127 disapproveProposal(signer: TSigner, proposalHash: string) {3128 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]);3129 }3130}31313132class CollectiveMembershipGroup extends HelperGroup<UniqueHelper> {3133 /**3134 * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership'3135 */3136 private membership: string;31373138 constructor(helper: UniqueHelper, membership: string) {3139 super(helper);3140 this.membership = membership;3141 }31423143 /**3144 * Returns an array of members' addresses according to the membership pallet's perception.3145 * Note that it does not recognize the original pallet's members set with `setMembers()`.3146 */3147 async getMembers() {3148 return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman();3149 }31503151 /**3152 * Returns the optional address of the prime member of the collective.3153 */3154 async getPrimeMember() {3155 return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman();3156 }31573158 /**3159 * Add a member to the collective.3160 * @param signer keyring of the setter. Must be root3161 * @param member address of the member to add3162 * @returns promise of extrinsic execution and its result3163 */3164 addMember(signer: TSigner, member: string) {3165 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]);3166 }31673168 addMemberCall(member: string) {3169 return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]);3170 }31713172 /**3173 * Remove a member from the collective.3174 * @param signer keyring of the setter. Must be root3175 * @param member address of the member to remove3176 * @returns promise of extrinsic execution and its result3177 */3178 removeMember(signer: TSigner, member: string) {3179 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]);3180 }31813182 removeMemberCall(member: string) {3183 return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]);3184 }31853186 /**3187 * Set members of the collective to the given list of addresses.3188 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3189 * @param members addresses of the members to set3190 * @returns promise of extrinsic execution and its result3191 */3192 resetMembers(signer: TSigner, members: string[]) {3193 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]);3194 }31953196 /**3197 * Set the collective's prime member to the given address.3198 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3199 * @param prime address of the prime member of the collective3200 * @returns promise of extrinsic execution and its result3201 */3202 setPrime(signer: TSigner, prime: string) {3203 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]);3204 }32053206 setPrimeCall(member: string) {3207 return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]);3208 }32093210 /**3211 * Remove the collective's prime member.3212 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3213 * @returns promise of extrinsic execution and its result3214 */3215 clearPrime(signer: TSigner) {3216 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []);3217 }32183219 clearPrimeCall() {3220 return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []);3221 }3222}32233224class RankedCollectiveGroup extends HelperGroup<UniqueHelper> {3225 /**3226 * Pallet name to make an API call to. Examples: 'FellowshipCollective'3227 */3228 private collective: string;32293230 constructor(helper: UniqueHelper, collective: string) {3231 super(helper);3232 this.collective = collective;3233 }32343235 addMember(signer: TSigner, newMember: string) {3236 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]);3237 }32383239 addMemberCall(newMember: string) {3240 return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]);3241 }32423243 removeMember(signer: TSigner, member: string, minRank: number) {3244 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]);3245 }32463247 removeMemberCall(newMember: string, minRank: number) {3248 return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]);3249 }32503251 promote(signer: TSigner, member: string) {3252 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]);3253 }32543255 promoteCall(member: string) {3256 return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]);3257 }32583259 demote(signer: TSigner, member: string) {3260 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]);3261 }32623263 demoteCall(newMember: string) {3264 return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]);3265 }32663267 vote(signer: TSigner, pollIndex: number, aye: boolean) {3268 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]);3269 }32703271 async getMembers() {3272 return (await this.helper.getApi().query.fellowshipCollective.members.keys())3273 .map((key) => key.args[0].toString());3274 }32753276 async getMemberRank(member: string) {3277 return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank;3278 }3279}32803281class ReferendaGroup extends HelperGroup<UniqueHelper> {3282 /**3283 * Pallet name to make an API call to. Examples: 'FellowshipReferenda'3284 */3285 private referenda: string;32863287 constructor(helper: UniqueHelper, referenda: string) {3288 super(helper);3289 this.referenda = referenda;3290 }32913292 submit(3293 signer: TSigner,3294 proposalOrigin: string,3295 proposal: any,3296 enactmentMoment: any,3297 ) {3298 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [3299 {Origins: proposalOrigin},3300 proposal,3301 enactmentMoment,3302 ]);3303 }33043305 placeDecisionDeposit(signer: TSigner, referendumIndex: number) {3306 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]);3307 }33083309 cancel(signer: TSigner, referendumIndex: number) {3310 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]);3311 }33123313 cancelCall(referendumIndex: number) {3314 return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]);3315 }33163317 async referendumInfo(referendumIndex: number) {3318 return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON();3319 }33203321 async enactmentEventId(referendumIndex: number) {3322 const api = await this.helper.getApi();33233324 const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a();3325 return blake2AsHex(bytes, 256);3326 }3327}33283329export interface IFellowshipGroup {3330 collective: RankedCollectiveGroup;3331 referenda: ReferendaGroup;3332}33333334export interface ICollectiveGroup {3335 collective: CollectiveGroup;3336 membership: CollectiveMembershipGroup;3337}33383339class DemocracyGroup extends HelperGroup<UniqueHelper> {3340 // todo displace proposal into types?3341 propose(signer: TSigner, call: any, deposit: bigint) {3342 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3343 }33443345 proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) {3346 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]);3347 }33483349 proposeCall(call: any, deposit: bigint) {3350 return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3351 }33523353 second(signer: TSigner, proposalIndex: number) {3354 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]);3355 }33563357 externalPropose(signer: TSigner, proposalCall: any) {3358 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3359 }33603361 externalProposeMajority(signer: TSigner, proposalCall: any) {3362 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3363 }33643365 externalProposeDefault(signer: TSigner, proposalCall: any) {3366 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3367 }33683369 externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) {3370 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);3371 }33723373 externalProposeCall(proposalCall: any) {3374 return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3375 }33763377 externalProposeMajorityCall(proposalCall: any) {3378 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3379 }33803381 externalProposeDefaultCall(proposalCall: any) {3382 return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3383 }33843385 externalProposeDefaultWithPreimageCall(preimage: string) {3386 return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);3387 }33883389 // ... and blacklist external proposal hash.3390 vetoExternal(signer: TSigner, proposalHash: string) {3391 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]);3392 }33933394 vetoExternalCall(proposalHash: string) {3395 return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]);3396 }33973398 blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) {3399 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3400 }34013402 blacklistCall(proposalHash: string, referendumIndex: number | null = null) {3403 return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3404 }34053406 // proposal. CancelProposalOrigin (root or all techcom)3407 cancelProposal(signer: TSigner, proposalIndex: number) {3408 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]);3409 }34103411 cancelProposalCall(proposalIndex: number) {3412 return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]);3413 }34143415 clearPublicProposals(signer: TSigner) {3416 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []);3417 }34183419 fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) {3420 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3421 }34223423 fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) {3424 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3425 }34263427 // referendum. CancellationOrigin (TechCom member)3428 emergencyCancel(signer: TSigner, referendumIndex: number) {3429 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]);3430 }34313432 emergencyCancelCall(referendumIndex: number) {3433 return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]);3434 }34353436 vote(signer: TSigner, referendumIndex: number, vote: any) {3437 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]);3438 }34393440 removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) {3441 if(targetAccount) {3442 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]);3443 } else {3444 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]);3445 }3446 }34473448 unlock(signer: TSigner, targetAccount: string) {3449 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]);3450 }34513452 delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) {3453 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]);3454 }34553456 undelegate(signer: TSigner) {3457 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []);3458 }34593460 async referendumInfo(referendumIndex: number) {3461 return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON();3462 }34633464 async publicProposals() {3465 return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON();3466 }34673468 async findPublicProposal(proposalIndex: number) {3469 const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex);34703471 return proposalInfo ? proposalInfo[1] : null;3472 }34733474 async expectPublicProposal(proposalIndex: number) {3475 const proposal = await this.findPublicProposal(proposalIndex);34763477 if(proposal) {3478 return proposal;3479 } else {3480 throw Error(`Proposal #${proposalIndex} is expected to exist`);3481 }3482 }34833484 async getExternalProposal() {3485 return (await this.helper.callRpc('api.query.democracy.nextExternal', []));3486 }34873488 async expectExternalProposal() {3489 const proposal = await this.getExternalProposal();34903491 if(proposal) {3492 return proposal;3493 } else {3494 throw Error('An external proposal is expected to exist');3495 }3496 }34973498 /* setMetadata? */34993500 /* todo?3501 referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {3502 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);3503 }*/3504}350528663506class PreimageGroup extends HelperGroup<UniqueHelper> {2867class PreimageGroup extends HelperGroup<UniqueHelper> {3507 async getPreimageInfo(h256: string) {2868 async getPreimageInfo(h256: string) {3572 }2933 }3573}2934}35743575class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {3576 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {3577 await this.helper.executeExtrinsic(3578 signer,3579 'api.tx.foreignAssets.registerForeignAsset',3580 [ownerAddress, location, metadata],3581 true,3582 );3583 }35843585 async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {3586 await this.helper.executeExtrinsic(3587 signer,3588 'api.tx.foreignAssets.updateForeignAsset',3589 [foreignAssetId, location, metadata],3590 true,3591 );3592 }3593}35943595export class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {3596 palletName: string;35973598 constructor(helper: T, palletName: string) {3599 super(helper);36003601 this.palletName = palletName;3602 }36033604 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {3605 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);3606 }36073608 async setSafeXcmVersion(signer: TSigner, version: number) {3609 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true);3610 }36113612 async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {3613 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);3614 }36153616 async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) {3617 const destinationContent = {3618 parents: 0,3619 interior: {3620 X1: {3621 Parachain: destinationParaId,3622 },3623 },3624 };36253626 const beneficiaryContent = {3627 parents: 0,3628 interior: {3629 X1: {3630 AccountId32: {3631 network: 'Any',3632 id: targetAccount,3633 },3634 },3635 },3636 };36373638 const assetsContent = [3639 {3640 id: {3641 Concrete: {3642 parents: 0,3643 interior: 'Here',3644 },3645 },3646 fun: {3647 Fungible: amount,3648 },3649 },3650 ];36513652 let destination;3653 let beneficiary;3654 let assets;36553656 if(xcmVersion == 2) {3657 destination = {V1: destinationContent};3658 beneficiary = {V1: beneficiaryContent};3659 assets = {V1: assetsContent};36603661 } else if(xcmVersion == 3) {3662 destination = {V2: destinationContent};3663 beneficiary = {V2: beneficiaryContent};3664 assets = {V2: assetsContent};36653666 } else {3667 throw Error('Unknown XCM version: ' + xcmVersion);3668 }36693670 const feeAssetItem = 0;36713672 await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);3673 }36743675 async send(signer: IKeyringPair, destination: any, message: any) {3676 await this.helper.executeExtrinsic(3677 signer,3678 `api.tx.${this.palletName}.send`,3679 [3680 destination,3681 message,3682 ],3683 true,3684 );3685 }3686}36873688export class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3689 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {3690 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);3691 }36923693 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {3694 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);3695 }36963697 async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {3698 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);3699 }3700}3701370237033704export class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3705 async accounts(address: string, currencyId: any) {3706 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;3707 return BigInt(free);3708 }3709}37103711export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {3712 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {3713 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);3714 }37153716 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {3717 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);3718 }37193720 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {3721 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);3722 }37233724 async account(assetId: string | number, address: string) {3725 const accountAsset = (3726 await this.helper.callRpc('api.query.assets.account', [assetId, address])3727 ).toJSON()! as any;37283729 if(accountAsset !== null) {3730 return BigInt(accountAsset['balance']);3731 } else {3732 return null;3733 }3734 }3735}373629353737class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {2936class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {3738 async batch(signer: TSigner, txs: any[]) {2937 async batch(signer: TSigner, txs: any[]) {3760 rft: RFTGroup;2957 rft: RFTGroup;3761 ft: FTGroup;2958 ft: FTGroup;3762 staking: StakingGroup;2959 staking: StakingGroup;3763 scheduler: SchedulerGroup;3764 collatorSelection: CollatorSelectionGroup;3765 council: ICollectiveGroup;3766 technicalCommittee: ICollectiveGroup;3767 fellowship: IFellowshipGroup;3768 democracy: DemocracyGroup;3769 preimage: PreimageGroup;2960 preimage: PreimageGroup;3770 foreignAssets: ForeignAssetsGroup;3771 xcm: XcmGroup<UniqueHelper>;3772 xTokens: XTokensGroup<UniqueHelper>;3773 tokens: TokensGroup<UniqueHelper>;3774 utility: UtilityGroup<UniqueHelper>;2961 utility: UtilityGroup<UniqueHelper>;377529623776 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {2963 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3782 this.rft = new RFTGroup(this);2969 this.rft = new RFTGroup(this);3783 this.ft = new FTGroup(this);2970 this.ft = new FTGroup(this);3784 this.staking = new StakingGroup(this);2971 this.staking = new StakingGroup(this);3785 this.scheduler = new SchedulerGroup(this);3786 this.collatorSelection = new CollatorSelectionGroup(this);3787 this.council = {3788 collective: new CollectiveGroup(this, 'council'),3789 membership: new CollectiveMembershipGroup(this, 'councilMembership'),3790 };3791 this.technicalCommittee = {3792 collective: new CollectiveGroup(this, 'technicalCommittee'),3793 membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),3794 };3795 this.fellowship = {3796 collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),3797 referenda: new ReferendaGroup(this, 'fellowshipReferenda'),3798 };3799 this.democracy = new DemocracyGroup(this);3800 this.preimage = new PreimageGroup(this);2972 this.preimage = new PreimageGroup(this);3801 this.foreignAssets = new ForeignAssetsGroup(this);3802 this.xcm = new XcmGroup(this, 'polkadotXcm');3803 this.xTokens = new XTokensGroup(this);3804 this.tokens = new TokensGroup(this);3805 this.utility = new UtilityGroup(this);2973 this.utility = new UtilityGroup(this);3806 }2974 }3807}2975}38083809// eslint-disable-next-line @typescript-eslint/naming-convention3810function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {3811 return class extends Base {3812 scheduleFn: 'schedule' | 'scheduleAfter';3813 blocksNum: number;3814 options: ISchedulerOptions;38153816 constructor(...args: any[]) {3817 const logger = args[0] as ILogger;3818 const options = args[1] as {3819 scheduleFn: 'schedule' | 'scheduleAfter',3820 blocksNum: number,3821 options: ISchedulerOptions3822 };38233824 super(logger);38253826 this.scheduleFn = options.scheduleFn;3827 this.blocksNum = options.blocksNum;3828 this.options = options.options;3829 }38303831 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {3832 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);38333834 const mandatorySchedArgs = [3835 this.blocksNum,3836 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,3837 this.options.priority ?? null,3838 scheduledTx,3839 ];38403841 let schedArgs;3842 let scheduleFn;38433844 if(this.options.scheduledId) {3845 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];38463847 if(this.scheduleFn == 'schedule') {3848 scheduleFn = 'scheduleNamed';3849 } else if(this.scheduleFn == 'scheduleAfter') {3850 scheduleFn = 'scheduleNamedAfter';3851 }3852 } else {3853 schedArgs = mandatorySchedArgs;3854 scheduleFn = this.scheduleFn;3855 }38563857 const extrinsic = 'api.tx.scheduler.' + scheduleFn;38583859 return super.executeExtrinsic(3860 sender,3861 extrinsic as any,3862 schedArgs,3863 expectSuccess,3864 );3865 }3866 };3867}386829763869export class UniqueBaseCollection {2977export class UniqueBaseCollection {3870 helper: UniqueHelper;2978 helper: UniqueHelper;3975 return await this.helper.collection.burn(signer, this.collectionId);3083 return await this.helper.collection.burn(signer, this.collectionId);3976 }3084 }39773978 scheduleAt<T extends UniqueHelper>(3979 executionBlockNumber: number,3980 options: ISchedulerOptions = {},3981 ) {3982 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3983 return new UniqueBaseCollection(this.collectionId, scheduledHelper);3984 }39853986 scheduleAfter<T extends UniqueHelper>(3987 blocksBeforeExecution: number,3988 options: ISchedulerOptions = {},3989 ) {3990 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3991 return new UniqueBaseCollection(this.collectionId, scheduledHelper);3992 }3993}3085}399430863995export class UniqueNFTCollection extends UniqueBaseCollection {3087export class UniqueNFTCollection extends UniqueBaseCollection {4084 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);4085 }3177 }40864087 scheduleAt<T extends UniqueHelper>(4088 executionBlockNumber: number,4089 options: ISchedulerOptions = {},4090 ) {4091 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4092 return new UniqueNFTCollection(this.collectionId, scheduledHelper);4093 }40944095 scheduleAfter<T extends UniqueHelper>(4096 blocksBeforeExecution: number,4097 options: ISchedulerOptions = {},4098 ) {4099 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4100 return new UniqueNFTCollection(this.collectionId, scheduledHelper);4101 }4102}3178}410331794104export class UniqueRFTCollection extends UniqueBaseCollection {3180export class UniqueRFTCollection extends UniqueBaseCollection {4205 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);4206 }3282 }42074208 scheduleAt<T extends UniqueHelper>(4209 executionBlockNumber: number,4210 options: ISchedulerOptions = {},4211 ) {4212 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4213 return new UniqueRFTCollection(this.collectionId, scheduledHelper);4214 }42154216 scheduleAfter<T extends UniqueHelper>(4217 blocksBeforeExecution: number,4218 options: ISchedulerOptions = {},4219 ) {4220 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4221 return new UniqueRFTCollection(this.collectionId, scheduledHelper);4222 }4223}3283}422432844225export class UniqueFTCollection extends UniqueBaseCollection {3285export class UniqueFTCollection extends UniqueBaseCollection {4267 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3327 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);4268 }3328 }42694270 scheduleAt<T extends UniqueHelper>(4271 executionBlockNumber: number,4272 options: ISchedulerOptions = {},4273 ) {4274 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4275 return new UniqueFTCollection(this.collectionId, scheduledHelper);4276 }42774278 scheduleAfter<T extends UniqueHelper>(4279 blocksBeforeExecution: number,4280 options: ISchedulerOptions = {},4281 ) {4282 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4283 return new UniqueFTCollection(this.collectionId, scheduledHelper);4284 }4285}3329}428633304287export class UniqueBaseToken {3331export class UniqueBaseToken {4323 return this.collection.helper.util.getTokenAccount(this);3367 return this.collection.helper.util.getTokenAccount(this);4324 }3368 }43254326 scheduleAt<T extends UniqueHelper>(4327 executionBlockNumber: number,4328 options: ISchedulerOptions = {},4329 ) {4330 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4331 return new UniqueBaseToken(this.tokenId, scheduledCollection);4332 }43334334 scheduleAfter<T extends UniqueHelper>(4335 blocksBeforeExecution: number,4336 options: ISchedulerOptions = {},4337 ) {4338 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4339 return new UniqueBaseToken(this.tokenId, scheduledCollection);4340 }4341}3369}434233704343export class UniqueNFToken extends UniqueBaseToken {3371export class UniqueNFToken extends UniqueBaseToken {4396 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3424 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);4397 }3425 }43984399 scheduleAt<T extends UniqueHelper>(4400 executionBlockNumber: number,4401 options: ISchedulerOptions = {},4402 ) {4403 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4404 return new UniqueNFToken(this.tokenId, scheduledCollection);4405 }44064407 scheduleAfter<T extends UniqueHelper>(4408 blocksBeforeExecution: number,4409 options: ISchedulerOptions = {},4410 ) {4411 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4412 return new UniqueNFToken(this.tokenId, scheduledCollection);4413 }4414}3426}441534274416export class UniqueRFToken extends UniqueBaseToken {3428export class UniqueRFToken extends UniqueBaseToken {4481 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3493 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);4482 }3494 }44834484 scheduleAt<T extends UniqueHelper>(4485 executionBlockNumber: number,4486 options: ISchedulerOptions = {},4487 ) {4488 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4489 return new UniqueRFToken(this.tokenId, scheduledCollection);4490 }44914492 scheduleAfter<T extends UniqueHelper>(4493 blocksBeforeExecution: number,4494 options: ISchedulerOptions = {},4495 ) {4496 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4497 return new UniqueRFToken(this.tokenId, scheduledCollection);4498 }4499}3495}45003496tests/src/util/playgrounds/unique.xcm.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.xcm.ts
+++ b/tests/src/util/playgrounds/unique.xcm.ts
@@ -1,9 +1,9 @@
-
import {ApiPromise, WsProvider} from '@polkadot/api';
-import {AssetsGroup, ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, TokensGroup, UniqueHelper, XTokensGroup, XcmGroup} from './unique';
-import {ILogger, TSigner} from './types';
-import {SudoHelper} from './unique.dev';
-import {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.xcm';
+import {IKeyringPair} from '@polkadot/types/types';
+import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from './unique';
+import {ILogger, TSigner, TSubstrateAccount} from './types';
+import {AcalaAssetMetadata, DemocracyStandardAccountVote, IForeignAssetMetadata, MoonbeamAssetInfo} from './types.xcm';
+
export class XcmChainHelper extends ChainHelperBase {
async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
@@ -102,6 +102,169 @@
await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);
}
}
+
+export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
+ async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
+ await this.helper.executeExtrinsic(
+ signer,
+ 'api.tx.foreignAssets.registerForeignAsset',
+ [ownerAddress, location, metadata],
+ true,
+ );
+ }
+
+ async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
+ await this.helper.executeExtrinsic(
+ signer,
+ 'api.tx.foreignAssets.updateForeignAsset',
+ [foreignAssetId, location, metadata],
+ true,
+ );
+ }
+}
+
+export class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ palletName: string;
+
+ constructor(helper: T, palletName: string) {
+ super(helper);
+
+ this.palletName = palletName;
+ }
+
+ async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);
+ }
+
+ async setSafeXcmVersion(signer: TSigner, version: number) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true);
+ }
+
+ async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {
+ await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);
+ }
+
+ async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) {
+ const destinationContent = {
+ parents: 0,
+ interior: {
+ X1: {
+ Parachain: destinationParaId,
+ },
+ },
+ };
+
+ const beneficiaryContent = {
+ parents: 0,
+ interior: {
+ X1: {
+ AccountId32: {
+ network: 'Any',
+ id: targetAccount,
+ },
+ },
+ },
+ };
+
+ const assetsContent = [
+ {
+ id: {
+ Concrete: {
+ parents: 0,
+ interior: 'Here',
+ },
+ },
+ fun: {
+ Fungible: amount,
+ },
+ },
+ ];
+
+ let destination;
+ let beneficiary;
+ let assets;
+
+ if(xcmVersion == 2) {
+ destination = {V1: destinationContent};
+ beneficiary = {V1: beneficiaryContent};
+ assets = {V1: assetsContent};
+
+ } else if(xcmVersion == 3) {
+ destination = {V2: destinationContent};
+ beneficiary = {V2: beneficiaryContent};
+ assets = {V2: assetsContent};
+
+ } else {
+ throw Error('Unknown XCM version: ' + xcmVersion);
+ }
+
+ const feeAssetItem = 0;
+
+ await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);
+ }
+
+ async send(signer: IKeyringPair, destination: any, message: any) {
+ await this.helper.executeExtrinsic(
+ signer,
+ `api.tx.${this.palletName}.send`,
+ [
+ destination,
+ message,
+ ],
+ true,
+ );
+ }
+}
+
+export class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);
+ }
+
+ async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);
+ }
+
+ async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);
+ }
+}
+
+
+
+export class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ async accounts(address: string, currencyId: any) {
+ const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;
+ return BigInt(free);
+ }
+}
+
+export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
+ }
+
+ async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);
+ }
+
+ async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
+ }
+
+ async account(assetId: string | number, address: string) {
+ const accountAsset = (
+ await this.helper.callRpc('api.query.assets.account', [assetId, address])
+ ).toJSON()! as any;
+
+ if(accountAsset !== null) {
+ return BigInt(accountAsset['balance']);
+ } else {
+ return null;
+ }
+ }
+}
+
export class RelayHelper extends XcmChainHelper {
balance: SubstrateBalanceGroup<RelayHelper>;
xcm: XcmGroup<RelayHelper>;