1import type {IKeyringPair} from '@polkadot/types/types';2import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../util/index.js';3import {Event} from '@unique/playgrounds/unique.dev.js';4import {initCouncil, democracyLaunchPeriod, democracyFastTrackVotingPeriod, clearCouncil, clearTechComm, clearFellowship, defaultEnactmentMoment, dummyProposal, dummyProposalCall, fellowshipPropositionOrigin, initFellowship, initTechComm, hardResetFellowshipReferenda, hardResetDemocracy, hardResetGovScheduler} from './util.js';5import type {ITechComms} from './util.js';67describeGov('Governance: Technical Committee tests', () => {8 let sudoer: IKeyringPair;9 let techcomms: ITechComms;10 let donor: IKeyringPair;11 let preImageHash: string;121314 const allTechCommitteeThreshold = 3;1516 before(async function() {17 await usingPlaygrounds(async (helper, privateKey) => {18 requirePalletsOrSkip(this, helper, [Pallets.TechnicalCommittee]);19 sudoer = await privateKey('//Alice');20 donor = await privateKey({url: import.meta.url});2122 techcomms = await initTechComm(donor, sudoer);2324 const proposalCall = await helper.constructApiCall('api.tx.balances.forceSetBalance', [donor.address, 20n * 10n ** 25n]);25 preImageHash = await helper.preimage.notePreimageFromCall(sudoer, proposalCall, true);26 });27 });2829 after(async () => {30 await usingPlaygrounds(async (helper) => {31 await clearTechComm(sudoer);3233 await helper.preimage.unnotePreimage(sudoer, preImageHash);34 await hardResetFellowshipReferenda(sudoer);35 await hardResetDemocracy(sudoer);36 await hardResetGovScheduler(sudoer);37 });38 });3940 function proposalFromAllCommittee(proposal: any) {41 return usingPlaygrounds(async (helper) => {42 expect((await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON().length).to.be.equal(allTechCommitteeThreshold);43 const proposeResult = await helper.technicalCommittee.collective.propose(44 techcomms.andy,45 proposal,46 allTechCommitteeThreshold,47 );4849 const commiteeProposedEvent = Event.TechnicalCommittee.Proposed.expect(proposeResult);50 const proposalIndex = commiteeProposedEvent.proposalIndex;51 const proposalHash = commiteeProposedEvent.proposalHash;525354 await helper.technicalCommittee.collective.vote(techcomms.andy, proposalHash, proposalIndex, true);55 await helper.technicalCommittee.collective.vote(techcomms.constantine, proposalHash, proposalIndex, true);56 await helper.technicalCommittee.collective.vote(techcomms.greg, proposalHash, proposalIndex, true);5758 const closeResult = await helper.technicalCommittee.collective.close(techcomms.andy, proposalHash, proposalIndex);59 Event.TechnicalCommittee.Closed.expect(closeResult);60 Event.TechnicalCommittee.Approved.expect(closeResult);61 const {result} = Event.TechnicalCommittee.Executed.expect(closeResult);62 expect(result).to.eq('Ok');6364 return closeResult;65 });66 }6768 itSub('TechComm can fast-track Democracy proposals', async ({helper}) => {69 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);70 await helper.wait.parachainBlockMultiplesOf(35n);7172 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);7374 const fastTrackProposal = await proposalFromAllCommittee(helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0));75 Event.Democracy.Started.expect(fastTrackProposal);76 });7778 itSub('TechComm can cancel Democracy proposals', async ({helper}) => {79 const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);80 const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;8182 const cancelProposal = await proposalFromAllCommittee(helper.democracy.cancelProposalCall(proposalIndex));83 Event.Democracy.ProposalCanceled.expect(cancelProposal);84 });8586 itSub('TechComm can cancel ongoing Democracy referendums', async ({helper}) => {87 await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper));88 const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);89 const referendumIndex = startedEvent.referendumIndex;9091 const emergencyCancelProposal = await proposalFromAllCommittee(helper.democracy.emergencyCancelCall(referendumIndex));92 Event.Democracy.Cancelled.expect(emergencyCancelProposal);93 });9495 itSub('TechComm member can veto Democracy proposals', async ({helper}) => {96 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);97 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);9899 const vetoExternalCall = await helper.technicalCommittee.collective.execute(100 techcomms.andy,101 helper.democracy.vetoExternalCall(preimageHash),102 );103 Event.Democracy.Vetoed.expect(vetoExternalCall);104 });105106 itSub('TechComm can cancel Fellowship referendums', async ({helper}) => {107 const fellowship = await initFellowship(donor, sudoer);108 const fellowshipProposer = fellowship[5][0];109 const proposal = dummyProposal(helper);110111 const submitResult = await helper.fellowship.referenda.submit(112 fellowshipProposer,113 fellowshipPropositionOrigin,114 proposal,115 defaultEnactmentMoment,116 );117 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;118 const cancelProposal = await proposalFromAllCommittee(helper.fellowship.referenda.cancelCall(referendumIndex));119 Event.FellowshipReferenda.Cancelled.expect(cancelProposal);120 });121122 itSub.skip('TechComm member can add a Fellowship member', async ({helper}) => {123 const newFellowshipMember = helper.arrange.createEmptyAccount();124 await expect(helper.technicalCommittee.collective.execute(125 techcomms.andy,126 helper.fellowship.collective.addMemberCall(newFellowshipMember.address),127 )).to.be.fulfilled;128 const fellowshipMembers = (await helper.callRpc('api.query.fellowshipCollective.members')).toJSON();129 expect(fellowshipMembers).to.contains(newFellowshipMember.address);130 await clearFellowship(sudoer);131 });132133 itSub('[Negative] TechComm cannot submit regular democracy proposal', async ({helper}) => {134 const councilProposal = await helper.democracy.proposeCall(dummyProposalCall(helper), 0n);135136 await expect(proposalFromAllCommittee(councilProposal)).to.be.rejectedWith('BadOrigin');137 });138139 itSub('[Negative] TechComm cannot externally propose SuperMajorityAgainst', async ({helper}) => {140 const commiteeProposal = await helper.democracy.externalProposeDefaultCall(dummyProposalCall(helper));141142 await expect(proposalFromAllCommittee(commiteeProposal)).to.be.rejectedWith('BadOrigin');143 });144145 itSub('[Negative] TechComm cannot externally propose SimpleMajority', async ({helper}) => {146 const commiteeProposal = await helper.democracy.externalProposeMajorityCall(dummyProposalCall(helper));147148 await expect(proposalFromAllCommittee(commiteeProposal)).to.be.rejectedWith('BadOrigin');149 });150151 itSub('[Negative] TechComm cannot externally propose SuperMajorityApprove', async ({helper}) => {152 const commiteeProposal = await helper.democracy.externalProposeCall(dummyProposalCall(helper));153154 await expect(proposalFromAllCommittee(commiteeProposal)).to.be.rejectedWith('BadOrigin');155 });156157 itSub('[Negative] TechComm member cannot submit regular democracy proposal', async ({helper}) => {158 const memberProposal = await helper.democracy.proposeCall(dummyProposalCall(helper), 0n);159160 await expect(helper.technicalCommittee.collective.execute(161 techcomms.andy,162 memberProposal,163 )).to.be.rejectedWith('BadOrigin');164 });165166 itSub('[Negative] TechComm member cannot externally propose SuperMajorityAgainst', async ({helper}) => {167 const memberProposal = await helper.democracy.externalProposeDefaultCall(dummyProposalCall(helper));168169 await expect(helper.technicalCommittee.collective.execute(170 techcomms.andy,171 memberProposal,172 )).to.be.rejectedWith('BadOrigin');173 });174175 itSub('[Negative] TechComm member cannot externally propose SimpleMajority', async ({helper}) => {176 const memberProposal = await helper.democracy.externalProposeMajorityCall(dummyProposalCall(helper));177178 await expect(helper.technicalCommittee.collective.execute(179 techcomms.andy,180 memberProposal,181 )).to.be.rejectedWith('BadOrigin');182 });183184 itSub('[Negative] TechComm member cannot externally propose SuperMajorityApprove', async ({helper}) => {185 const memberProposal = await helper.democracy.externalProposeCall(dummyProposalCall(helper));186187 await expect(helper.technicalCommittee.collective.execute(188 techcomms.andy,189 memberProposal,190 )).to.be.rejectedWith('BadOrigin');191 });192193194 itSub.skip('[Negative] TechComm cannot promote/demote Fellowship member', async () => {195196 });197198 itSub.skip('[Negative] TechComm member cannot promote/demote Fellowship member', async () => {199200 });201202 itSub('[Negative] TechComm cannot add/remove a Council member', async ({helper}) => {203 const newCouncilMember = helper.arrange.createEmptyAccount();204 const addMemberProposal = helper.council.membership.addMemberCall(newCouncilMember.address);205 const removeMemberProposal = helper.council.membership.removeMemberCall(newCouncilMember.address);206207 await expect(proposalFromAllCommittee(addMemberProposal)).to.be.rejectedWith('BadOrigin');208 await expect(proposalFromAllCommittee(removeMemberProposal)).to.be.rejectedWith('BadOrigin');209 });210211 itSub('[Negative] TechComm member cannot add/remove a Council member', async ({helper}) => {212 const newCouncilMember = helper.arrange.createEmptyAccount();213 const addMemberProposal = helper.council.membership.addMemberCall(newCouncilMember.address);214 const removeMemberProposal = helper.council.membership.removeMemberCall(newCouncilMember.address);215216 await expect(helper.technicalCommittee.collective.execute(217 techcomms.andy,218 addMemberProposal,219 )).to.be.rejectedWith('BadOrigin');220 await expect(helper.technicalCommittee.collective.execute(221 techcomms.andy,222 removeMemberProposal,223 )).to.be.rejectedWith('BadOrigin');224 });225226 itSub('[Negative] TechComm cannot set/clear Council prime member', async ({helper}) => {227 const counselors = await initCouncil(donor, sudoer);228 const proposalForSet = await helper.council.membership.setPrimeCall(counselors.charu.address);229 const proposalForClear = await helper.council.membership.clearPrimeCall();230231 await expect(proposalFromAllCommittee(proposalForSet)).to.be.rejectedWith('BadOrigin');232 await expect(proposalFromAllCommittee(proposalForClear)).to.be.rejectedWith('BadOrigin');233 await clearCouncil(sudoer);234 });235236 itSub('[Negative] TechComm member cannot set/clear Council prime member', async ({helper}) => {237 const counselors = await initCouncil(donor, sudoer);238 const proposalForSet = await helper.council.membership.setPrimeCall(counselors.charu.address);239 const proposalForClear = await helper.council.membership.clearPrimeCall();240241 await expect(helper.technicalCommittee.collective.execute(242 techcomms.andy,243 proposalForSet,244 )).to.be.rejectedWith('BadOrigin');245 await expect(helper.technicalCommittee.collective.execute(246 techcomms.andy,247 proposalForClear,248 )).to.be.rejectedWith('BadOrigin');249 await clearCouncil(sudoer);250 });251252 itSub('[Negative] TechComm cannot add/remove a TechComm member', async ({helper}) => {253 const newCommMember = helper.arrange.createEmptyAccount();254 const addMemberProposal = helper.council.membership.addMemberCall(newCommMember.address);255 const removeMemberProposal = helper.council.membership.removeMemberCall(newCommMember.address);256257 await expect(proposalFromAllCommittee(addMemberProposal)).to.be.rejectedWith('BadOrigin');258 await expect(proposalFromAllCommittee(removeMemberProposal)).to.be.rejectedWith('BadOrigin');259 });260261 itSub('[Negative] TechComm member cannot add/remove a TechComm member', async ({helper}) => {262 const newCommMember = helper.arrange.createEmptyAccount();263 const addMemberProposal = helper.council.membership.addMemberCall(newCommMember.address);264 const removeMemberProposal = helper.council.membership.removeMemberCall(newCommMember.address);265266 await expect(helper.technicalCommittee.collective.execute(267 techcomms.andy,268 addMemberProposal,269 )).to.be.rejectedWith('BadOrigin');270 await expect(helper.technicalCommittee.collective.execute(271 techcomms.andy,272 removeMemberProposal,273 )).to.be.rejectedWith('BadOrigin');274 });275276 itSub('[Negative] TechComm cannot remove a Fellowship member', async ({helper}) => {277 const fellowship = await initFellowship(donor, sudoer);278279 await expect(proposalFromAllCommittee(helper.fellowship.collective.removeMemberCall(fellowship[5][0].address, 5))).to.be.rejectedWith('BadOrigin');280 await clearFellowship(sudoer);281 });282283 itSub('[Negative] TechComm member cannot remove a Fellowship member', async ({helper}) => {284 const fellowship = await initFellowship(donor, sudoer);285286 await expect(helper.technicalCommittee.collective.execute(287 techcomms.andy,288 helper.fellowship.collective.removeMemberCall(fellowship[5][0].address, 5),289 )).to.be.rejectedWith('BadOrigin');290 await clearFellowship(sudoer);291 });292293 itSub('[Negative] TechComm member cannot fast-track Democracy proposals', async ({helper}) => {294 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);295 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);296297 await expect(helper.technicalCommittee.collective.execute(298 techcomms.andy,299 helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0),300 )).to.be.rejectedWith('BadOrigin');301 });302303 itSub('[Negative] TechComm member cannot cancel Democracy proposals', async ({helper}) => {304 const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);305 const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;306307 await expect(helper.technicalCommittee.collective.execute(308 techcomms.andy,309 helper.democracy.cancelProposalCall(proposalIndex),310 ))311 .to.be.rejectedWith('BadOrigin');312 });313314 itSub('[Negative] TechComm member cannot cancel ongoing Democracy referendums', async ({helper}) => {315 await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper));316 const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);317 const referendumIndex = startedEvent.referendumIndex;318319 await expect(helper.technicalCommittee.collective.execute(320 techcomms.andy,321 helper.democracy.emergencyCancelCall(referendumIndex),322 )).to.be.rejectedWith('BadOrigin');323 });324325 itSub('[Negative] TechComm cannot blacklist Democracy proposals', async ({helper}) => {326 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);327 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);328329 await expect(proposalFromAllCommittee(helper.democracy.blacklistCall(preimageHash))).to.be.rejectedWith('BadOrigin');330 });331332 itSub('[Negative] TechComm member cannot blacklist Democracy proposals', async ({helper}) => {333 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);334 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);335336 await expect(helper.technicalCommittee.collective.execute(337 techcomms.andy,338 helper.democracy.blacklistCall(preimageHash),339 )).to.be.rejectedWith('BadOrigin');340 });341342 itSub.skip('[Negative] TechComm member cannot veto external Democracy proposals until the cool-off period pass', async () => {343344 });345346 itSub('[Negative] TechComm member cannot cancel Fellowship referendums', async ({helper}) => {347 const fellowship = await initFellowship(donor, sudoer);348 const fellowshipProposer = fellowship[5][0];349 const proposal = dummyProposal(helper);350351 const submitResult = await helper.fellowship.referenda.submit(352 fellowshipProposer,353 fellowshipPropositionOrigin,354 proposal,355 defaultEnactmentMoment,356 );357358 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;359360 await expect(helper.technicalCommittee.collective.execute(361 techcomms.andy,362 helper.fellowship.referenda.cancelCall(referendumIndex),363 )).to.be.rejectedWith('BadOrigin');364 });365366 itSub('[Negative] TechComm referendum cannot be closed until the voting threshold is met', async ({helper}) => {367 const committeeSize = (await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON().length as any as number;368 expect(committeeSize).is.greaterThan(1);369 const proposeResult = await helper.technicalCommittee.collective.propose(370 techcomms.andy,371 dummyProposalCall(helper),372 committeeSize,373 );374375 const committeeProposedEvent = Event.TechnicalCommittee.Proposed.expect(proposeResult);376 const proposalIndex = committeeProposedEvent.proposalIndex;377 const proposalHash = committeeProposedEvent.proposalHash;378379 await helper.technicalCommittee.collective.vote(techcomms.constantine, proposalHash, proposalIndex, true);380381 await expect(helper.technicalCommittee.collective.close(techcomms.andy, proposalHash, proposalIndex)).to.be.rejectedWith('TooEarly');382 });383});