git.delta.rocks / unique-network / refs/commits / 32f66f7e1404

difftreelog

source

js-packages/playgrounds/unique.governance.ts19.6 KiBsourcehistory
1import {blake2AsHex} from '@polkadot/util-crypto';2import type {PalletDemocracyConviction} from '@polkadot/types/lookup';3import type {IPhasicEvent, TSigner} from './types.js';4import {HelperGroup, UniqueHelper} from './unique.js';56export class CollectiveGroup extends HelperGroup<UniqueHelper> {7  /**8   * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee'9   */10  private collective: string;1112  constructor(helper: UniqueHelper, collective: string) {13    super(helper);14    this.collective = collective;15  }1617  /**18   * Check the result of a proposal execution for the success of the underlying proposed extrinsic.19   * @param events events of the proposal execution20   * @returns proposal hash21   */22  private checkExecutedEvent(events: IPhasicEvent[]): string {23    const executionEvents = events.filter(x =>24      x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted'));2526    if(executionEvents.length != 1) {27      if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0)28        throw new Error(`Disapproved by ${this.collective}`);29      else30        throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`);31    }3233    const result = (executionEvents[0].event.data as any).result;3435    if(result.isErr) {36      if(result.asErr.isModule) {37        const error = result.asErr.asModule;38        const metaError = this.helper.getApi()?.registry.findMetaError(error);39        throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`);40      } else {41        throw new Error('Proposal execution failed with ' + result.asErr.toHuman());42      }43    }4445    return (executionEvents[0].event.data as any).proposalHash;46  }4748  /**49   * Returns an array of members' addresses.50   */51  async getMembers() {52    return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman();53  }5455  /**56   * Returns the optional address of the prime member of the collective.57   */58  async getPrimeMember() {59    return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman();60  }6162  /**63   * Returns an array of proposal hashes that are currently active for this collective.64   */65  async getProposals() {66    return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman();67  }6869  /**70   * Returns the call originally encoded under the specified hash.71   * @param hash h256-encoded proposal72   * @returns the optional call that the proposal hash stands for.73   */74  async getProposalCallOf(hash: string) {75    return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman();76  }7778  /**79   * Returns the total number of proposals so far.80   */81  async getTotalProposalsCount() {82    return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber();83  }8485  /**86   * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately.87   * @param signer keyring of the proposer88   * @param proposal constructed call to be executed if the proposal is successful89   * @param voteThreshold minimal number of votes for the proposal to be verified and executed90   * @param lengthBound byte length of the encoded call91   * @returns promise of extrinsic execution and its result92   */93  async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) {94    return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]);95  }9697  /**98   * Casts a vote to either approve or reject a proposal.99   * @param signer keyring of the voter100   * @param proposalHash hash of the proposal to be voted for101   * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors102   * @param approve aye or nay103   * @returns promise of extrinsic execution and its result104   */105  vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {106    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]);107  }108109  /**110   * Executes a call immediately as a member of the collective. Needed for the Member origin.111   * @param signer keyring of the executor member112   * @param proposal constructed call to be executed by the member113   * @param lengthBound byte length of the encoded call114   * @returns promise of extrinsic execution115   */116  async execute(signer: TSigner, proposal: any, lengthBound = 10000) {117    const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]);118    this.checkExecutedEvent(result.result.events);119    return result;120  }121122  /**123   * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing.124   * @param signer keyring of the executor. Can be absolutely anyone.125   * @param proposalHash hash of the proposal to close126   * @param proposalIndex index of the proposal generated on its creation127   * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call.128   * @param lengthBound byte length of the encoded call129   * @returns promise of extrinsic execution and its result130   */131  async close(132    signer: TSigner,133    proposalHash: string,134    proposalIndex: number,135    weightBound: [number, number] | any = [20_000_000_000, 1000_000],136    lengthBound = 10_000,137  ) {138    const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [139      proposalHash,140      proposalIndex,141      weightBound,142      lengthBound,143    ]);144    this.checkExecutedEvent(result.result.events);145    return result;146  }147148  /**149   * Shut down a proposal, regardless of its current state.150   * @param signer keyring of the disapprover. Must be root151   * @param proposalHash hash of the proposal to close152   * @returns promise of extrinsic execution and its result153   */154  disapproveProposal(signer: TSigner, proposalHash: string) {155    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]);156  }157}158159export class CollectiveMembershipGroup extends HelperGroup<UniqueHelper> {160  /**161   * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership'162   */163  private membership: string;164165  constructor(helper: UniqueHelper, membership: string) {166    super(helper);167    this.membership = membership;168  }169170  /**171   * Returns an array of members' addresses according to the membership pallet's perception.172   * Note that it does not recognize the original pallet's members set with `setMembers()`.173   */174  async getMembers() {175    return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman();176  }177178  /**179   * Returns the optional address of the prime member of the collective.180   */181  async getPrimeMember() {182    return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman();183  }184185  /**186   * Add a member to the collective.187   * @param signer keyring of the setter. Must be root188   * @param member address of the member to add189   * @returns promise of extrinsic execution and its result190   */191  addMember(signer: TSigner, member: string) {192    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]);193  }194195  addMemberCall(member: string) {196    return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]);197  }198199  /**200   * Remove a member from the collective.201   * @param signer keyring of the setter. Must be root202   * @param member address of the member to remove203   * @returns promise of extrinsic execution and its result204   */205  removeMember(signer: TSigner, member: string) {206    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]);207  }208209  removeMemberCall(member: string) {210    return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]);211  }212213  /**214   * Set members of the collective to the given list of addresses.215   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)216   * @param members addresses of the members to set217   * @returns promise of extrinsic execution and its result218   */219  resetMembers(signer: TSigner, members: string[]) {220    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]);221  }222223  /**224   * Set the collective's prime member to the given address.225   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)226   * @param prime address of the prime member of the collective227   * @returns promise of extrinsic execution and its result228   */229  setPrime(signer: TSigner, prime: string) {230    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]);231  }232233  setPrimeCall(member: string) {234    return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]);235  }236237  /**238   * Remove the collective's prime member.239   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)240   * @returns promise of extrinsic execution and its result241   */242  clearPrime(signer: TSigner) {243    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []);244  }245246  clearPrimeCall() {247    return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []);248  }249}250251export class RankedCollectiveGroup extends HelperGroup<UniqueHelper> {252  /**253   * Pallet name to make an API call to. Examples: 'FellowshipCollective'254   */255  private collective: string;256257  constructor(helper: UniqueHelper, collective: string) {258    super(helper);259    this.collective = collective;260  }261262  addMember(signer: TSigner, newMember: string) {263    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]);264  }265266  addMemberCall(newMember: string) {267    return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]);268  }269270  removeMember(signer: TSigner, member: string, minRank: number) {271    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]);272  }273274  removeMemberCall(newMember: string, minRank: number) {275    return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]);276  }277278  promote(signer: TSigner, member: string) {279    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]);280  }281282  promoteCall(member: string) {283    return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]);284  }285286  demote(signer: TSigner, member: string) {287    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]);288  }289290  demoteCall(newMember: string) {291    return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]);292  }293294  vote(signer: TSigner, pollIndex: number, aye: boolean) {295    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]);296  }297298  async getMembers() {299    return (await this.helper.getApi().query.fellowshipCollective.members.keys())300      .map((key) => key.args[0].toString());301  }302303  async getMemberRank(member: string) {304    return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank;305  }306}307308export class ReferendaGroup extends HelperGroup<UniqueHelper> {309  /**310   * Pallet name to make an API call to. Examples: 'FellowshipReferenda'311   */312  private referenda: string;313314  constructor(helper: UniqueHelper, referenda: string) {315    super(helper);316    this.referenda = referenda;317  }318319  submit(320    signer: TSigner,321    proposalOrigin: string,322    proposal: any,323    enactmentMoment: any,324  ) {325    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [326      {Origins: proposalOrigin},327      proposal,328      enactmentMoment,329    ]);330  }331332  placeDecisionDeposit(signer: TSigner, referendumIndex: number) {333    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]);334  }335336  cancel(signer: TSigner, referendumIndex: number) {337    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]);338  }339340  cancelCall(referendumIndex: number) {341    return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]);342  }343344  async referendumInfo(referendumIndex: number) {345    return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON();346  }347348  async enactmentEventId(referendumIndex: number) {349    const api = await this.helper.getApi();350351    const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a();352    return blake2AsHex(bytes, 256);353  }354}355356export interface IFellowshipGroup {357  collective: RankedCollectiveGroup;358  referenda: ReferendaGroup;359}360361export interface ICollectiveGroup {362  collective: CollectiveGroup;363  membership: CollectiveMembershipGroup;364}365366export class DemocracyGroup extends HelperGroup<UniqueHelper> {367  // todo displace proposal into types?368  propose(signer: TSigner, call: any, deposit: bigint) {369    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);370  }371372  proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) {373    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]);374  }375376  proposeCall(call: any, deposit: bigint) {377    return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);378  }379380  second(signer: TSigner, proposalIndex: number) {381    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]);382  }383384  externalPropose(signer: TSigner, proposalCall: any) {385    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);386  }387388  externalProposeMajority(signer: TSigner, proposalCall: any) {389    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);390  }391392  externalProposeDefault(signer: TSigner, proposalCall: any) {393    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);394  }395396  externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) {397    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);398  }399400  externalProposeCall(proposalCall: any) {401    return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);402  }403404  externalProposeMajorityCall(proposalCall: any) {405    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);406  }407408  externalProposeDefaultCall(proposalCall: any) {409    return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);410  }411412  externalProposeDefaultWithPreimageCall(preimage: string) {413    return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);414  }415416  // ... and blacklist external proposal hash.417  vetoExternal(signer: TSigner, proposalHash: string) {418    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]);419  }420421  vetoExternalCall(proposalHash: string) {422    return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]);423  }424425  blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) {426    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]);427  }428429  blacklistCall(proposalHash: string, referendumIndex: number | null = null) {430    return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]);431  }432433  // proposal. CancelProposalOrigin (root or all techcom)434  cancelProposal(signer: TSigner, proposalIndex: number) {435    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]);436  }437438  cancelProposalCall(proposalIndex: number) {439    return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]);440  }441442  clearPublicProposals(signer: TSigner) {443    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []);444  }445446  fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) {447    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);448  }449450  fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) {451    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);452  }453454  // referendum. CancellationOrigin (TechCom member)455  emergencyCancel(signer: TSigner, referendumIndex: number) {456    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]);457  }458459  emergencyCancelCall(referendumIndex: number) {460    return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]);461  }462463  vote(signer: TSigner, referendumIndex: number, vote: any) {464    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]);465  }466467  removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) {468    if(targetAccount) {469      return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]);470    } else {471      return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]);472    }473  }474475  unlock(signer: TSigner, targetAccount: string) {476    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]);477  }478479  delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) {480    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]);481  }482483  undelegate(signer: TSigner) {484    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []);485  }486487  async referendumInfo(referendumIndex: number) {488    return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON();489  }490491  async publicProposals() {492    return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON();493  }494495  async findPublicProposal(proposalIndex: number) {496    const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex);497498    return proposalInfo ? proposalInfo[1] : null;499  }500501  async expectPublicProposal(proposalIndex: number) {502    const proposal = await this.findPublicProposal(proposalIndex);503504    if(proposal) {505      return proposal;506    } else {507      throw Error(`Proposal #${proposalIndex} is expected to exist`);508    }509  }510511  async getExternalProposal() {512    return (await this.helper.callRpc('api.query.democracy.nextExternal', []));513  }514515  async expectExternalProposal() {516    const proposal = await this.getExternalProposal();517518    if(proposal) {519      return proposal;520    } else {521      throw Error('An external proposal is expected to exist');522    }523  }524525  /* setMetadata? */526527  /* todo?528  referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {529    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);530  }*/531}