From 04e6d946dd25dbb02df6caeccc922662b8127e05 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 14 Sep 2023 11:02:36 +0000 Subject: [PATCH] tests: move scheduler to dev, move governance to separate file --- --- 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); --- 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, --- 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 --- 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 { + constructor(helper: UniqueHelper) { + super(helper); + } + + cancelScheduled(signer: TSigner, scheduledId: string) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.scheduler.cancelNamed', + [scheduledId], + true, + ); + } + + changePriority(signer: TSigner, scheduledId: string, priority: number) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.scheduler.changeNamedPriority', + [scheduledId, priority], + true, + ); + } + + scheduleAt( + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + return this.schedule('schedule', executionBlockNumber, options); + } + + scheduleAfter( + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + return this.schedule('scheduleAfter', blocksBeforeExecution, options); + } + + schedule( + scheduleFn: 'schedule' | 'scheduleAfter', + blocksNum: number, + options: ISchedulerOptions = {}, + ) { + // eslint-disable-next-line @typescript-eslint/naming-convention + const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase); + return this.helper.clone(ScheduledHelperType, { + scheduleFn, + blocksNum, + options, + }) as T; + } +} + +class CollatorSelectionGroup extends HelperGroup { + //todo:collator documentation + addInvulnerable(signer: TSigner, address: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]); + } + + removeInvulnerable(signer: TSigner, address: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]); + } + + async getInvulnerables(): Promise { + return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman()); + } + + /** and also total max invulnerables */ + maxCollators(): number { + return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number); + } + + async getDesiredCollators(): Promise { + return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber(); + } + + setLicenseBond(signer: TSigner, amount: bigint) { + return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]); + } + + async getLicenseBond(): Promise { + return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt(); + } + + obtainLicense(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []); + } + + releaseLicense(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []); + } + + forceReleaseLicense(signer: TSigner, released: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]); + } + + async hasLicense(address: string): Promise { + return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt(); + } + + onboard(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []); + } + + offboard(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []); + } + + async getCandidates(): Promise { + return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman()); + } +} + export class DevUniqueHelper extends UniqueHelper { /** * Arrange methods for tests @@ -344,6 +457,16 @@ admin: AdminGroup; session: SessionGroup; testUtils: TestUtilGroup; + foreignAssets: ForeignAssetsGroup; + xcm: XcmGroup; + xTokens: XTokensGroup; + tokens: TokensGroup; + scheduler: SchedulerGroup; + collatorSelection: CollatorSelectionGroup; + council: ICollectiveGroup; + technicalCommittee: ICollectiveGroup; + fellowship: IFellowshipGroup; + democracy: DemocracyGroup; constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { options.helperBase = options.helperBase ?? DevUniqueHelper; @@ -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 { @@ -401,7 +543,7 @@ this.network = await UniqueHelper.detectNetwork(this.api); this.wsEndpoint = wsEndpoint; } - getSudo() { + getSudo() { // 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(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 { + 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 --- /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 { + /** + * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee' + */ + private collective: string; + + constructor(helper: UniqueHelper, collective: string) { + super(helper); + this.collective = collective; + } + + /** + * Check the result of a proposal execution for the success of the underlying proposed extrinsic. + * @param events events of the proposal execution + * @returns proposal hash + */ + private checkExecutedEvent(events: IPhasicEvent[]): string { + const executionEvents = events.filter(x => + x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted')); + + if(executionEvents.length != 1) { + if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0) + throw new Error(`Disapproved by ${this.collective}`); + else + throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`); + } + + const result = (executionEvents[0].event.data as any).result; + + if(result.isErr) { + if(result.asErr.isModule) { + const error = result.asErr.asModule; + const metaError = this.helper.getApi()?.registry.findMetaError(error); + throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`); + } else { + throw new Error('Proposal execution failed with ' + result.asErr.toHuman()); + } + } + + return (executionEvents[0].event.data as any).proposalHash; + } + + /** + * Returns an array of members' addresses. + */ + async getMembers() { + return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman(); + } + + /** + * Returns the optional address of the prime member of the collective. + */ + async getPrimeMember() { + return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman(); + } + + /** + * Returns an array of proposal hashes that are currently active for this collective. + */ + async getProposals() { + return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman(); + } + + /** + * Returns the call originally encoded under the specified hash. + * @param hash h256-encoded proposal + * @returns the optional call that the proposal hash stands for. + */ + async getProposalCallOf(hash: string) { + return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman(); + } + + /** + * Returns the total number of proposals so far. + */ + async getTotalProposalsCount() { + return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber(); + } + + /** + * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately. + * @param signer keyring of the proposer + * @param proposal constructed call to be executed if the proposal is successful + * @param voteThreshold minimal number of votes for the proposal to be verified and executed + * @param lengthBound byte length of the encoded call + * @returns promise of extrinsic execution and its result + */ + async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) { + return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]); + } + + /** + * Casts a vote to either approve or reject a proposal. + * @param signer keyring of the voter + * @param proposalHash hash of the proposal to be voted for + * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors + * @param approve aye or nay + * @returns promise of extrinsic execution and its result + */ + vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]); + } + + /** + * Executes a call immediately as a member of the collective. Needed for the Member origin. + * @param signer keyring of the executor member + * @param proposal constructed call to be executed by the member + * @param lengthBound byte length of the encoded call + * @returns promise of extrinsic execution + */ + async execute(signer: TSigner, proposal: any, lengthBound = 10000) { + const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]); + this.checkExecutedEvent(result.result.events); + return result; + } + + /** + * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing. + * @param signer keyring of the executor. Can be absolutely anyone. + * @param proposalHash hash of the proposal to close + * @param proposalIndex index of the proposal generated on its creation + * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call. + * @param lengthBound byte length of the encoded call + * @returns promise of extrinsic execution and its result + */ + async close( + signer: TSigner, + proposalHash: string, + proposalIndex: number, + weightBound: [number, number] | any = [20_000_000_000, 1000_000], + lengthBound = 10_000, + ) { + const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [ + proposalHash, + proposalIndex, + weightBound, + lengthBound, + ]); + this.checkExecutedEvent(result.result.events); + return result; + } + + /** + * Shut down a proposal, regardless of its current state. + * @param signer keyring of the disapprover. Must be root + * @param proposalHash hash of the proposal to close + * @returns promise of extrinsic execution and its result + */ + disapproveProposal(signer: TSigner, proposalHash: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]); + } +} + +export class CollectiveMembershipGroup extends HelperGroup { + /** + * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership' + */ + private membership: string; + + constructor(helper: UniqueHelper, membership: string) { + super(helper); + this.membership = membership; + } + + /** + * Returns an array of members' addresses according to the membership pallet's perception. + * Note that it does not recognize the original pallet's members set with `setMembers()`. + */ + async getMembers() { + return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman(); + } + + /** + * Returns the optional address of the prime member of the collective. + */ + async getPrimeMember() { + return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman(); + } + + /** + * Add a member to the collective. + * @param signer keyring of the setter. Must be root + * @param member address of the member to add + * @returns promise of extrinsic execution and its result + */ + addMember(signer: TSigner, member: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]); + } + + addMemberCall(member: string) { + return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]); + } + + /** + * Remove a member from the collective. + * @param signer keyring of the setter. Must be root + * @param member address of the member to remove + * @returns promise of extrinsic execution and its result + */ + removeMember(signer: TSigner, member: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]); + } + + removeMemberCall(member: string) { + return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]); + } + + /** + * Set members of the collective to the given list of addresses. + * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) + * @param members addresses of the members to set + * @returns promise of extrinsic execution and its result + */ + resetMembers(signer: TSigner, members: string[]) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]); + } + + /** + * Set the collective's prime member to the given address. + * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) + * @param prime address of the prime member of the collective + * @returns promise of extrinsic execution and its result + */ + setPrime(signer: TSigner, prime: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]); + } + + setPrimeCall(member: string) { + return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]); + } + + /** + * Remove the collective's prime member. + * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) + * @returns promise of extrinsic execution and its result + */ + clearPrime(signer: TSigner) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []); + } + + clearPrimeCall() { + return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []); + } +} + +export class RankedCollectiveGroup extends HelperGroup { + /** + * Pallet name to make an API call to. Examples: 'FellowshipCollective' + */ + private collective: string; + + constructor(helper: UniqueHelper, collective: string) { + super(helper); + this.collective = collective; + } + + addMember(signer: TSigner, newMember: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]); + } + + addMemberCall(newMember: string) { + return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]); + } + + removeMember(signer: TSigner, member: string, minRank: number) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]); + } + + removeMemberCall(newMember: string, minRank: number) { + return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]); + } + + promote(signer: TSigner, member: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]); + } + + promoteCall(member: string) { + return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]); + } + + demote(signer: TSigner, member: string) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]); + } + + demoteCall(newMember: string) { + return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]); + } + + vote(signer: TSigner, pollIndex: number, aye: boolean) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]); + } + + async getMembers() { + return (await this.helper.getApi().query.fellowshipCollective.members.keys()) + .map((key) => key.args[0].toString()); + } + + async getMemberRank(member: string) { + return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank; + } +} + +export class ReferendaGroup extends HelperGroup { + /** + * Pallet name to make an API call to. Examples: 'FellowshipReferenda' + */ + private referenda: string; + + constructor(helper: UniqueHelper, referenda: string) { + super(helper); + this.referenda = referenda; + } + + submit( + signer: TSigner, + proposalOrigin: string, + proposal: any, + enactmentMoment: any, + ) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [ + {Origins: proposalOrigin}, + proposal, + enactmentMoment, + ]); + } + + placeDecisionDeposit(signer: TSigner, referendumIndex: number) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]); + } + + cancel(signer: TSigner, referendumIndex: number) { + return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]); + } + + cancelCall(referendumIndex: number) { + return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]); + } + + async referendumInfo(referendumIndex: number) { + return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON(); + } + + async enactmentEventId(referendumIndex: number) { + const api = await this.helper.getApi(); + + const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a(); + return blake2AsHex(bytes, 256); + } +} + +export interface IFellowshipGroup { + collective: RankedCollectiveGroup; + referenda: ReferendaGroup; +} + +export interface ICollectiveGroup { + collective: CollectiveGroup; + membership: CollectiveMembershipGroup; +} + +export class DemocracyGroup extends HelperGroup { + // todo displace proposal into types? + propose(signer: TSigner, call: any, deposit: bigint) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]); + } + + proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]); + } + + proposeCall(call: any, deposit: bigint) { + return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]); + } + + second(signer: TSigner, proposalIndex: number) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]); + } + + externalPropose(signer: TSigner, proposalCall: any) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeMajority(signer: TSigner, proposalCall: any) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeDefault(signer: TSigner, proposalCall: any) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]); + } + + externalProposeCall(proposalCall: any) { + return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeMajorityCall(proposalCall: any) { + return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeDefaultCall(proposalCall: any) { + return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]); + } + + externalProposeDefaultWithPreimageCall(preimage: string) { + return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]); + } + + // ... and blacklist external proposal hash. + vetoExternal(signer: TSigner, proposalHash: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]); + } + + vetoExternalCall(proposalHash: string) { + return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]); + } + + blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]); + } + + blacklistCall(proposalHash: string, referendumIndex: number | null = null) { + return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]); + } + + // proposal. CancelProposalOrigin (root or all techcom) + cancelProposal(signer: TSigner, proposalIndex: number) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]); + } + + cancelProposalCall(proposalIndex: number) { + return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]); + } + + clearPublicProposals(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []); + } + + fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); + } + + fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) { + return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); + } + + // referendum. CancellationOrigin (TechCom member) + emergencyCancel(signer: TSigner, referendumIndex: number) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]); + } + + emergencyCancelCall(referendumIndex: number) { + return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]); + } + + vote(signer: TSigner, referendumIndex: number, vote: any) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]); + } + + removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) { + if(targetAccount) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]); + } else { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]); + } + } + + unlock(signer: TSigner, targetAccount: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]); + } + + delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]); + } + + undelegate(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []); + } + + async referendumInfo(referendumIndex: number) { + return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON(); + } + + async publicProposals() { + return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON(); + } + + async findPublicProposal(proposalIndex: number) { + const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex); + + return proposalInfo ? proposalInfo[1] : null; + } + + async expectPublicProposal(proposalIndex: number) { + const proposal = await this.findPublicProposal(proposalIndex); + + if(proposal) { + return proposal; + } else { + throw Error(`Proposal #${proposalIndex} is expected to exist`); + } + } + + async getExternalProposal() { + return (await this.helper.callRpc('api.query.democracy.nextExternal', [])); + } + + async expectExternalProposal() { + const proposal = await this.getExternalProposal(); + + if(proposal) { + return proposal; + } else { + throw Error('An external proposal is expected to exist'); + } + } + + /* setMetadata? */ + + /* todo? + referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) { + return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true); + }*/ +} --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -39,13 +39,11 @@ TSigner, TSubstrateAccount, TNetworks, - IForeignAssetMetadata, IEthCrossAccountId, - IPhasicEvent, } from './types'; import {RuntimeDispatchInfo} from '@polkadot/types/interfaces'; import type {Vec} from '@polkadot/types-codec'; -import {FrameSystemEventRecord, PalletDemocracyConviction} from '@polkadot/types/lookup'; +import {FrameSystemEventRecord} from '@polkadot/types/lookup'; export class CrossAccountId { Substrate!: TSubstrateAccount; @@ -2757,6 +2755,7 @@ } } + class StakingGroup extends HelperGroup { /** * Stake tokens for App Promotion @@ -2860,648 +2859,10 @@ block: block.toBigInt(), amount: amount.toBigInt(), })); - return result; - } -} - -class SchedulerGroup extends HelperGroup { - constructor(helper: UniqueHelper) { - super(helper); - } - - cancelScheduled(signer: TSigner, scheduledId: string) { - return this.helper.executeExtrinsic( - signer, - 'api.tx.scheduler.cancelNamed', - [scheduledId], - true, - ); - } - - changePriority(signer: TSigner, scheduledId: string, priority: number) { - return this.helper.executeExtrinsic( - signer, - 'api.tx.scheduler.changeNamedPriority', - [scheduledId, priority], - true, - ); - } - - scheduleAt( - executionBlockNumber: number, - options: ISchedulerOptions = {}, - ) { - return this.schedule('schedule', executionBlockNumber, options); - } - - scheduleAfter( - blocksBeforeExecution: number, - options: ISchedulerOptions = {}, - ) { - return this.schedule('scheduleAfter', blocksBeforeExecution, options); - } - - schedule( - scheduleFn: 'schedule' | 'scheduleAfter', - blocksNum: number, - options: ISchedulerOptions = {}, - ) { - // eslint-disable-next-line @typescript-eslint/naming-convention - const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase); - return this.helper.clone(ScheduledHelperType, { - scheduleFn, - blocksNum, - options, - }) as T; - } -} - -class CollatorSelectionGroup extends HelperGroup { - //todo:collator documentation - addInvulnerable(signer: TSigner, address: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]); - } - - removeInvulnerable(signer: TSigner, address: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]); - } - - async getInvulnerables(): Promise { - return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman()); - } - - /** and also total max invulnerables */ - maxCollators(): number { - return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number); - } - - async getDesiredCollators(): Promise { - return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber(); - } - - setLicenseBond(signer: TSigner, amount: bigint) { - return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]); - } - - async getLicenseBond(): Promise { - return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt(); - } - - obtainLicense(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []); - } - - releaseLicense(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []); - } - - forceReleaseLicense(signer: TSigner, released: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]); - } - - async hasLicense(address: string): Promise { - return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt(); - } - - onboard(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []); - } - - offboard(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []); - } - - async getCandidates(): Promise { - return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman()); - } -} - -class CollectiveGroup extends HelperGroup { - /** - * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee' - */ - private collective: string; - - constructor(helper: UniqueHelper, collective: string) { - super(helper); - this.collective = collective; - } - - /** - * Check the result of a proposal execution for the success of the underlying proposed extrinsic. - * @param events events of the proposal execution - * @returns proposal hash - */ - private checkExecutedEvent(events: IPhasicEvent[]): string { - const executionEvents = events.filter(x => - x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted')); - - if(executionEvents.length != 1) { - if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0) - throw new Error(`Disapproved by ${this.collective}`); - else - throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`); - } - - const result = (executionEvents[0].event.data as any).result; - - if(result.isErr) { - if(result.asErr.isModule) { - const error = result.asErr.asModule; - const metaError = this.helper.getApi()?.registry.findMetaError(error); - throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`); - } else { - throw new Error('Proposal execution failed with ' + result.asErr.toHuman()); - } - } - - return (executionEvents[0].event.data as any).proposalHash; - } - - /** - * Returns an array of members' addresses. - */ - async getMembers() { - return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman(); - } - - /** - * Returns the optional address of the prime member of the collective. - */ - async getPrimeMember() { - return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman(); - } - - /** - * Returns an array of proposal hashes that are currently active for this collective. - */ - async getProposals() { - return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman(); - } - - /** - * Returns the call originally encoded under the specified hash. - * @param hash h256-encoded proposal - * @returns the optional call that the proposal hash stands for. - */ - async getProposalCallOf(hash: string) { - return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman(); - } - - /** - * Returns the total number of proposals so far. - */ - async getTotalProposalsCount() { - return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber(); - } - - /** - * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately. - * @param signer keyring of the proposer - * @param proposal constructed call to be executed if the proposal is successful - * @param voteThreshold minimal number of votes for the proposal to be verified and executed - * @param lengthBound byte length of the encoded call - * @returns promise of extrinsic execution and its result - */ - async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) { - return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]); - } - - /** - * Casts a vote to either approve or reject a proposal. - * @param signer keyring of the voter - * @param proposalHash hash of the proposal to be voted for - * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors - * @param approve aye or nay - * @returns promise of extrinsic execution and its result - */ - vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]); - } - - /** - * Executes a call immediately as a member of the collective. Needed for the Member origin. - * @param signer keyring of the executor member - * @param proposal constructed call to be executed by the member - * @param lengthBound byte length of the encoded call - * @returns promise of extrinsic execution - */ - async execute(signer: TSigner, proposal: any, lengthBound = 10000) { - const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]); - this.checkExecutedEvent(result.result.events); - return result; - } - - /** - * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing. - * @param signer keyring of the executor. Can be absolutely anyone. - * @param proposalHash hash of the proposal to close - * @param proposalIndex index of the proposal generated on its creation - * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call. - * @param lengthBound byte length of the encoded call - * @returns promise of extrinsic execution and its result - */ - async close( - signer: TSigner, - proposalHash: string, - proposalIndex: number, - weightBound: [number, number] | any = [20_000_000_000, 1000_000], - lengthBound = 10_000, - ) { - const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [ - proposalHash, - proposalIndex, - weightBound, - lengthBound, - ]); - this.checkExecutedEvent(result.result.events); return result; - } - - /** - * Shut down a proposal, regardless of its current state. - * @param signer keyring of the disapprover. Must be root - * @param proposalHash hash of the proposal to close - * @returns promise of extrinsic execution and its result - */ - disapproveProposal(signer: TSigner, proposalHash: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]); - } -} - -class CollectiveMembershipGroup extends HelperGroup { - /** - * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership' - */ - private membership: string; - - constructor(helper: UniqueHelper, membership: string) { - super(helper); - this.membership = membership; - } - - /** - * Returns an array of members' addresses according to the membership pallet's perception. - * Note that it does not recognize the original pallet's members set with `setMembers()`. - */ - async getMembers() { - return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman(); - } - - /** - * Returns the optional address of the prime member of the collective. - */ - async getPrimeMember() { - return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman(); - } - - /** - * Add a member to the collective. - * @param signer keyring of the setter. Must be root - * @param member address of the member to add - * @returns promise of extrinsic execution and its result - */ - addMember(signer: TSigner, member: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]); - } - - addMemberCall(member: string) { - return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]); - } - - /** - * Remove a member from the collective. - * @param signer keyring of the setter. Must be root - * @param member address of the member to remove - * @returns promise of extrinsic execution and its result - */ - removeMember(signer: TSigner, member: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]); - } - - removeMemberCall(member: string) { - return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]); - } - - /** - * Set members of the collective to the given list of addresses. - * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) - * @param members addresses of the members to set - * @returns promise of extrinsic execution and its result - */ - resetMembers(signer: TSigner, members: string[]) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]); - } - - /** - * Set the collective's prime member to the given address. - * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) - * @param prime address of the prime member of the collective - * @returns promise of extrinsic execution and its result - */ - setPrime(signer: TSigner, prime: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]); - } - - setPrimeCall(member: string) { - return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]); - } - - /** - * Remove the collective's prime member. - * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion) - * @returns promise of extrinsic execution and its result - */ - clearPrime(signer: TSigner) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []); - } - - clearPrimeCall() { - return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []); - } -} - -class RankedCollectiveGroup extends HelperGroup { - /** - * Pallet name to make an API call to. Examples: 'FellowshipCollective' - */ - private collective: string; - - constructor(helper: UniqueHelper, collective: string) { - super(helper); - this.collective = collective; - } - - addMember(signer: TSigner, newMember: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]); } - - addMemberCall(newMember: string) { - return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]); - } - - removeMember(signer: TSigner, member: string, minRank: number) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]); - } - - removeMemberCall(newMember: string, minRank: number) { - return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]); - } - - promote(signer: TSigner, member: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]); - } - - promoteCall(member: string) { - return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]); - } - - demote(signer: TSigner, member: string) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]); - } - - demoteCall(newMember: string) { - return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]); - } - - vote(signer: TSigner, pollIndex: number, aye: boolean) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]); - } - - async getMembers() { - return (await this.helper.getApi().query.fellowshipCollective.members.keys()) - .map((key) => key.args[0].toString()); - } - - async getMemberRank(member: string) { - return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank; - } -} - -class ReferendaGroup extends HelperGroup { - /** - * Pallet name to make an API call to. Examples: 'FellowshipReferenda' - */ - private referenda: string; - - constructor(helper: UniqueHelper, referenda: string) { - super(helper); - this.referenda = referenda; - } - - submit( - signer: TSigner, - proposalOrigin: string, - proposal: any, - enactmentMoment: any, - ) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [ - {Origins: proposalOrigin}, - proposal, - enactmentMoment, - ]); - } - - placeDecisionDeposit(signer: TSigner, referendumIndex: number) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]); - } - - cancel(signer: TSigner, referendumIndex: number) { - return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]); - } - - cancelCall(referendumIndex: number) { - return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]); - } - - async referendumInfo(referendumIndex: number) { - return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON(); - } - - async enactmentEventId(referendumIndex: number) { - const api = await this.helper.getApi(); - - const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a(); - return blake2AsHex(bytes, 256); - } -} - -export interface IFellowshipGroup { - collective: RankedCollectiveGroup; - referenda: ReferendaGroup; } - -export interface ICollectiveGroup { - collective: CollectiveGroup; - membership: CollectiveMembershipGroup; -} - -class DemocracyGroup extends HelperGroup { - // todo displace proposal into types? - propose(signer: TSigner, call: any, deposit: bigint) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]); - } - - proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]); - } - - proposeCall(call: any, deposit: bigint) { - return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]); - } - - second(signer: TSigner, proposalIndex: number) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]); - } - - externalPropose(signer: TSigner, proposalCall: any) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeMajority(signer: TSigner, proposalCall: any) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeDefault(signer: TSigner, proposalCall: any) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]); - } - - externalProposeCall(proposalCall: any) { - return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeMajorityCall(proposalCall: any) { - return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeDefaultCall(proposalCall: any) { - return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]); - } - - externalProposeDefaultWithPreimageCall(preimage: string) { - return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]); - } - - // ... and blacklist external proposal hash. - vetoExternal(signer: TSigner, proposalHash: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]); - } - - vetoExternalCall(proposalHash: string) { - return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]); - } - - blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]); - } - - blacklistCall(proposalHash: string, referendumIndex: number | null = null) { - return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]); - } - - // proposal. CancelProposalOrigin (root or all techcom) - cancelProposal(signer: TSigner, proposalIndex: number) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]); - } - - cancelProposalCall(proposalIndex: number) { - return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]); - } - - clearPublicProposals(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []); - } - - fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); - } - - fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) { - return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]); - } - - // referendum. CancellationOrigin (TechCom member) - emergencyCancel(signer: TSigner, referendumIndex: number) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]); - } - - emergencyCancelCall(referendumIndex: number) { - return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]); - } - - vote(signer: TSigner, referendumIndex: number, vote: any) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]); - } - - removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) { - if(targetAccount) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]); - } else { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]); - } - } - - unlock(signer: TSigner, targetAccount: string) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]); - } - - delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]); - } - - undelegate(signer: TSigner) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []); - } - - async referendumInfo(referendumIndex: number) { - return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON(); - } - - async publicProposals() { - return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON(); - } - - async findPublicProposal(proposalIndex: number) { - const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex); - - return proposalInfo ? proposalInfo[1] : null; - } - - async expectPublicProposal(proposalIndex: number) { - const proposal = await this.findPublicProposal(proposalIndex); - - if(proposal) { - return proposal; - } else { - throw Error(`Proposal #${proposalIndex} is expected to exist`); - } - } - - async getExternalProposal() { - return (await this.helper.callRpc('api.query.democracy.nextExternal', [])); - } - - async expectExternalProposal() { - const proposal = await this.getExternalProposal(); - - if(proposal) { - return proposal; - } else { - throw Error('An external proposal is expected to exist'); - } - } - - /* setMetadata? */ - /* todo? - referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) { - return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true); - }*/ -} class PreimageGroup extends HelperGroup { async getPreimageInfo(h256: string) { @@ -3569,171 +2930,9 @@ */ unrequestPreimage(signer: TSigner, h256: string) { return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unrequestPreimage', [h256]); - } -} - -class ForeignAssetsGroup extends HelperGroup { - 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 extends HelperGroup { - palletName: string; - - constructor(helper: T, palletName: string) { - super(helper); - - this.palletName = palletName; - } - - async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true); - } - - async setSafeXcmVersion(signer: TSigner, version: number) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true); - } - - async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) { - await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true); - } - - async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) { - const destinationContent = { - parents: 0, - interior: { - X1: { - Parachain: destinationParaId, - }, - }, - }; - - const beneficiaryContent = { - parents: 0, - interior: { - X1: { - AccountId32: { - network: 'Any', - id: targetAccount, - }, - }, - }, - }; - - const assetsContent = [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: amount, - }, - }, - ]; - - let destination; - let beneficiary; - let assets; - - if(xcmVersion == 2) { - destination = {V1: destinationContent}; - beneficiary = {V1: beneficiaryContent}; - assets = {V1: assetsContent}; - - } else if(xcmVersion == 3) { - destination = {V2: destinationContent}; - beneficiary = {V2: beneficiaryContent}; - assets = {V2: assetsContent}; - - } else { - throw Error('Unknown XCM version: ' + xcmVersion); - } - - const feeAssetItem = 0; - - await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem); - } - - async send(signer: IKeyringPair, destination: any, message: any) { - await this.helper.executeExtrinsic( - signer, - `api.tx.${this.palletName}.send`, - [ - destination, - message, - ], - true, - ); } } -export class XTokensGroup extends HelperGroup { - async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) { - await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true); - } - - async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) { - await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true); - } - - async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) { - await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true); - } -} - - - -export class TokensGroup extends HelperGroup { - async accounts(address: string, currencyId: any) { - const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any; - return BigInt(free); - } -} - -export class AssetsGroup extends HelperGroup { - async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) { - await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true); - } - - async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) { - await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true); - } - - async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) { - await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true); - } - - async account(assetId: string | number, address: string) { - const accountAsset = ( - await this.helper.callRpc('api.query.assets.account', [assetId, address]) - ).toJSON()! as any; - - if(accountAsset !== null) { - return BigInt(accountAsset['balance']); - } else { - return null; - } - } -} - class UtilityGroup extends HelperGroup { async batch(signer: TSigner, txs: any[]) { return await this.helper.executeExtrinsic(signer, 'api.tx.utility.batch', [txs]); @@ -3747,9 +2946,7 @@ return this.helper.constructApiCall('api.tx.utility.batchAll', [txs]); } } - - export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase; export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper; @@ -3760,17 +2957,7 @@ rft: RFTGroup; ft: FTGroup; staking: StakingGroup; - scheduler: SchedulerGroup; - collatorSelection: CollatorSelectionGroup; - council: ICollectiveGroup; - technicalCommittee: ICollectiveGroup; - fellowship: IFellowshipGroup; - democracy: DemocracyGroup; preimage: PreimageGroup; - foreignAssets: ForeignAssetsGroup; - xcm: XcmGroup; - xTokens: XTokensGroup; - tokens: TokensGroup; utility: UtilityGroup; constructor(logger?: ILogger, options: { [key: string]: any } = {}) { @@ -3782,88 +2969,9 @@ this.rft = new RFTGroup(this); this.ft = new FTGroup(this); this.staking = new StakingGroup(this); - this.scheduler = new SchedulerGroup(this); - this.collatorSelection = new CollatorSelectionGroup(this); - this.council = { - collective: new CollectiveGroup(this, 'council'), - membership: new CollectiveMembershipGroup(this, 'councilMembership'), - }; - this.technicalCommittee = { - collective: new CollectiveGroup(this, 'technicalCommittee'), - membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'), - }; - this.fellowship = { - collective: new RankedCollectiveGroup(this, 'fellowshipCollective'), - referenda: new ReferendaGroup(this, 'fellowshipReferenda'), - }; - this.democracy = new DemocracyGroup(this); this.preimage = new PreimageGroup(this); - this.foreignAssets = new ForeignAssetsGroup(this); - this.xcm = new XcmGroup(this, 'polkadotXcm'); - this.xTokens = new XTokensGroup(this); - this.tokens = new TokensGroup(this); this.utility = new UtilityGroup(this); } -} - -// eslint-disable-next-line @typescript-eslint/naming-convention -function ScheduledUniqueHelper(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 { - const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams); - - const mandatorySchedArgs = [ - this.blocksNum, - this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null, - this.options.priority ?? null, - scheduledTx, - ]; - - let schedArgs; - let scheduleFn; - - if(this.options.scheduledId) { - schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs]; - - if(this.scheduleFn == 'schedule') { - scheduleFn = 'scheduleNamed'; - } else if(this.scheduleFn == 'scheduleAfter') { - scheduleFn = 'scheduleNamedAfter'; - } - } else { - schedArgs = mandatorySchedArgs; - scheduleFn = this.scheduleFn; - } - - const extrinsic = 'api.tx.scheduler.' + scheduleFn; - - return super.executeExtrinsic( - sender, - extrinsic as any, - schedArgs, - expectSuccess, - ); - } - }; } export class UniqueBaseCollection { @@ -3973,22 +3081,6 @@ async burn(signer: TSigner) { return await this.helper.collection.burn(signer, this.collectionId); - } - - scheduleAt( - executionBlockNumber: number, - options: ISchedulerOptions = {}, - ) { - const scheduledHelper = this.helper.scheduler.scheduleAt(executionBlockNumber, options); - return new UniqueBaseCollection(this.collectionId, scheduledHelper); - } - - scheduleAfter( - blocksBeforeExecution: number, - options: ISchedulerOptions = {}, - ) { - const scheduledHelper = this.helper.scheduler.scheduleAfter(blocksBeforeExecution, options); - return new UniqueBaseCollection(this.collectionId, scheduledHelper); } } @@ -4083,22 +3175,6 @@ async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) { return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj); } - - scheduleAt( - executionBlockNumber: number, - options: ISchedulerOptions = {}, - ) { - const scheduledHelper = this.helper.scheduler.scheduleAt(executionBlockNumber, options); - return new UniqueNFTCollection(this.collectionId, scheduledHelper); - } - - scheduleAfter( - blocksBeforeExecution: number, - options: ISchedulerOptions = {}, - ) { - const scheduledHelper = this.helper.scheduler.scheduleAfter(blocksBeforeExecution, options); - return new UniqueNFTCollection(this.collectionId, scheduledHelper); - } } export class UniqueRFTCollection extends UniqueBaseCollection { @@ -4204,22 +3280,6 @@ async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) { return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj); } - - scheduleAt( - executionBlockNumber: number, - options: ISchedulerOptions = {}, - ) { - const scheduledHelper = this.helper.scheduler.scheduleAt(executionBlockNumber, options); - return new UniqueRFTCollection(this.collectionId, scheduledHelper); - } - - scheduleAfter( - blocksBeforeExecution: number, - options: ISchedulerOptions = {}, - ) { - const scheduledHelper = this.helper.scheduler.scheduleAfter(blocksBeforeExecution, options); - return new UniqueRFTCollection(this.collectionId, scheduledHelper); - } } export class UniqueFTCollection extends UniqueBaseCollection { @@ -4265,22 +3325,6 @@ async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) { return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount); - } - - scheduleAt( - executionBlockNumber: number, - options: ISchedulerOptions = {}, - ) { - const scheduledHelper = this.helper.scheduler.scheduleAt(executionBlockNumber, options); - return new UniqueFTCollection(this.collectionId, scheduledHelper); - } - - scheduleAfter( - blocksBeforeExecution: number, - options: ISchedulerOptions = {}, - ) { - const scheduledHelper = this.helper.scheduler.scheduleAfter(blocksBeforeExecution, options); - return new UniqueFTCollection(this.collectionId, scheduledHelper); } } @@ -4321,22 +3365,6 @@ nestingAccount() { return this.collection.helper.util.getTokenAccount(this); - } - - scheduleAt( - executionBlockNumber: number, - options: ISchedulerOptions = {}, - ) { - const scheduledCollection = this.collection.scheduleAt(executionBlockNumber, options); - return new UniqueBaseToken(this.tokenId, scheduledCollection); - } - - scheduleAfter( - blocksBeforeExecution: number, - options: ISchedulerOptions = {}, - ) { - const scheduledCollection = this.collection.scheduleAfter(blocksBeforeExecution, options); - return new UniqueBaseToken(this.tokenId, scheduledCollection); } } @@ -4395,22 +3423,6 @@ async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) { return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj); } - - scheduleAt( - executionBlockNumber: number, - options: ISchedulerOptions = {}, - ) { - const scheduledCollection = this.collection.scheduleAt(executionBlockNumber, options); - return new UniqueNFToken(this.tokenId, scheduledCollection); - } - - scheduleAfter( - blocksBeforeExecution: number, - options: ISchedulerOptions = {}, - ) { - const scheduledCollection = this.collection.scheduleAfter(blocksBeforeExecution, options); - return new UniqueNFToken(this.tokenId, scheduledCollection); - } } export class UniqueRFToken extends UniqueBaseToken { @@ -4479,21 +3491,5 @@ async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) { return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount); - } - - scheduleAt( - executionBlockNumber: number, - options: ISchedulerOptions = {}, - ) { - const scheduledCollection = this.collection.scheduleAt(executionBlockNumber, options); - return new UniqueRFToken(this.tokenId, scheduledCollection); - } - - scheduleAfter( - blocksBeforeExecution: number, - options: ISchedulerOptions = {}, - ) { - const scheduledCollection = this.collection.scheduleAfter(blocksBeforeExecution, options); - return new UniqueRFToken(this.tokenId, scheduledCollection); } } --- 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 { @@ -102,6 +102,169 @@ await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true); } } + +export class ForeignAssetsGroup extends HelperGroup { + 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 extends HelperGroup { + palletName: string; + + constructor(helper: T, palletName: string) { + super(helper); + + this.palletName = palletName; + } + + async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true); + } + + async setSafeXcmVersion(signer: TSigner, version: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true); + } + + async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) { + await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true); + } + + async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) { + const destinationContent = { + parents: 0, + interior: { + X1: { + Parachain: destinationParaId, + }, + }, + }; + + const beneficiaryContent = { + parents: 0, + interior: { + X1: { + AccountId32: { + network: 'Any', + id: targetAccount, + }, + }, + }, + }; + + const assetsContent = [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: amount, + }, + }, + ]; + + let destination; + let beneficiary; + let assets; + + if(xcmVersion == 2) { + destination = {V1: destinationContent}; + beneficiary = {V1: beneficiaryContent}; + assets = {V1: assetsContent}; + + } else if(xcmVersion == 3) { + destination = {V2: destinationContent}; + beneficiary = {V2: beneficiaryContent}; + assets = {V2: assetsContent}; + + } else { + throw Error('Unknown XCM version: ' + xcmVersion); + } + + const feeAssetItem = 0; + + await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem); + } + + async send(signer: IKeyringPair, destination: any, message: any) { + await this.helper.executeExtrinsic( + signer, + `api.tx.${this.palletName}.send`, + [ + destination, + message, + ], + true, + ); + } +} + +export class XTokensGroup extends HelperGroup { + async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) { + await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true); + } + + async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) { + await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true); + } + + async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) { + await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true); + } +} + + + +export class TokensGroup extends HelperGroup { + async accounts(address: string, currencyId: any) { + const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any; + return BigInt(free); + } +} + +export class AssetsGroup extends HelperGroup { + async create(signer: TSigner, assetId: number, 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; xcm: XcmGroup; -- gitstuff