12import {IKeyringPair} from '@polkadot/types/types';3import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../util';4import {Event} from '../util/playgrounds/unique.dev';5import {ICounselors, initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyEnactmentPeriod, councilMotionDuration, democracyFastTrackVotingPeriod, fellowshipRankLimit, clearCouncil, clearTechComm, initTechComm, clearFellowship, dummyProposal, dummyProposalCall, initFellowship, defaultEnactmentMoment, fellowshipPropositionOrigin} from './util';67describeGov('Governance: Council tests', () => {8 let donor: IKeyringPair;9 let counselors: ICounselors;10 let sudoer: IKeyringPair;1112 const moreThanHalfCouncilThreshold = 3;1314 before(async function() {15 await usingPlaygrounds(async (helper, privateKey) => {16 requirePalletsOrSkip(this, helper, [Pallets.Council]);1718 donor = await privateKey({url: import.meta.url});19 sudoer = await privateKey('//Alice');20 });21 });2223 beforeEach(async () => {24 counselors = await initCouncil(donor, sudoer);25 });2627 afterEach(async () => {28 await clearCouncil(sudoer);29 await clearTechComm(sudoer);30 });3132 async function proposalFromMoreThanHalfCouncil(proposal: any) {33 return await usingPlaygrounds(async (helper) => {34 expect((await helper.callRpc('api.query.councilMembership.members')).toJSON().length).to.be.equal(5);35 const proposeResult = await helper.council.collective.propose(36 counselors.filip,37 proposal,38 moreThanHalfCouncilThreshold,39 );4041 const councilProposedEvent = Event.Council.Proposed.expect(proposeResult);42 const proposalIndex = councilProposedEvent.proposalIndex;43 const proposalHash = councilProposedEvent.proposalHash;444546 await helper.council.collective.vote(counselors.alex, proposalHash, proposalIndex, true);47 await helper.council.collective.vote(counselors.charu, proposalHash, proposalIndex, true);48 await helper.council.collective.vote(counselors.filip, proposalHash, proposalIndex, true);4950 return await helper.council.collective.close(counselors.filip, proposalHash, proposalIndex);51 });52 }5354 async function proposalFromAllCouncil(proposal: any) {55 return await usingPlaygrounds(async (helper) => {56 expect((await helper.callRpc('api.query.councilMembership.members')).toJSON().length).to.be.equal(5);57 const proposeResult = await helper.council.collective.propose(58 counselors.filip,59 proposal,60 moreThanHalfCouncilThreshold,61 );6263 const councilProposedEvent = Event.Council.Proposed.expect(proposeResult);64 const proposalIndex = councilProposedEvent.proposalIndex;65 const proposalHash = councilProposedEvent.proposalHash;666768 await helper.council.collective.vote(counselors.alex, proposalHash, proposalIndex, true);69 await helper.council.collective.vote(counselors.charu, proposalHash, proposalIndex, true);70 await helper.council.collective.vote(counselors.ildar, proposalHash, proposalIndex, true);71 await helper.council.collective.vote(counselors.irina, proposalHash, proposalIndex, true);72 await helper.council.collective.vote(counselors.filip, proposalHash, proposalIndex, true);7374 return await helper.council.collective.close(counselors.filip, proposalHash, proposalIndex);75 });76 }7778 itSub('>50% of Council can externally propose SuperMajorityAgainst', async ({helper}) => {79 const forceSetBalanceReceiver = helper.arrange.createEmptyAccount();80 const forceSetBalanceTestValue = 20n * 10n ** 25n;8182 const democracyProposal = await helper.constructApiCall('api.tx.balances.forceSetBalance', [83 forceSetBalanceReceiver.address, forceSetBalanceTestValue,84 ]);8586 const councilProposal = await helper.democracy.externalProposeDefaultCall(democracyProposal);8788 const proposeResult = await helper.council.collective.propose(89 counselors.filip,90 councilProposal,91 moreThanHalfCouncilThreshold,92 );9394 const councilProposedEvent = Event.Council.Proposed.expect(proposeResult);95 const proposalIndex = councilProposedEvent.proposalIndex;96 const proposalHash = councilProposedEvent.proposalHash;9798 await helper.council.collective.vote(counselors.alex, proposalHash, proposalIndex, true);99 await helper.council.collective.vote(counselors.charu, proposalHash, proposalIndex, true);100 await helper.council.collective.vote(counselors.filip, proposalHash, proposalIndex, true);101102 await helper.council.collective.close(counselors.filip, proposalHash, proposalIndex);103104 const democracyStartedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);105 const democracyReferendumIndex = democracyStartedEvent.referendumIndex;106 const democracyThreshold = democracyStartedEvent.threshold;107108 expect(democracyThreshold).to.be.equal('SuperMajorityAgainst');109110 await helper.democracy.vote(counselors.filip, democracyReferendumIndex, {111 Standard: {112 vote: {113 aye: true,114 conviction: 1,115 },116 balance: 10_000n,117 },118 });119120 await helper.democracy.vote(counselors.charu, democracyReferendumIndex, {121 Standard: {122 vote: {123 aye: false,124 conviction: 1,125 },126 balance: 50_000n,127 },128 });129130 const passedReferendumEvent = await helper.wait.expectEvent(democracyVotingPeriod, Event.Democracy.Passed);131 expect(passedReferendumEvent.referendumIndex).to.be.equal(democracyReferendumIndex);132133 await helper.wait.expectEvent(democracyEnactmentPeriod, Event.Scheduler.Dispatched);134 const receiverBalance = await helper.balance.getSubstrate(forceSetBalanceReceiver.address);135 expect(receiverBalance).to.be.equal(forceSetBalanceTestValue);136 });137138 itSub('Council prime member vote is the default', async ({helper}) => {139 const newTechCommMember = helper.arrange.createEmptyAccount();140 const addMemberProposal = helper.technicalCommittee.membership.addMemberCall(newTechCommMember.address);141 const proposeResult = await helper.council.collective.propose(142 counselors.filip,143 addMemberProposal,144 moreThanHalfCouncilThreshold,145 );146147 const councilProposedEvent = Event.Council.Proposed.expect(proposeResult);148 const proposalIndex = councilProposedEvent.proposalIndex;149 const proposalHash = councilProposedEvent.proposalHash;150151 await helper.council.collective.vote(counselors.alex, proposalHash, proposalIndex, true);152153 await helper.wait.newBlocks(councilMotionDuration);154 const closeResult = await helper.council.collective.close(counselors.filip, proposalHash, proposalIndex);155 const closeEvent = Event.Council.Closed.expect(closeResult);156 const members = (await helper.callRpc('api.query.councilMembership.members')).toJSON() as string[];157 expect(closeEvent.yes).to.be.equal(members.length);158 });159160 itSub('Superuser can add a member', async ({helper}) => {161 const newMember = helper.arrange.createEmptyAccount();162 await expect(helper.getSudo().council.membership.addMember(sudoer, newMember.address)).to.be.fulfilled;163164 const members = (await helper.callRpc('api.query.councilMembership.members')).toJSON();165 expect(members).to.contains(newMember.address);166 });167168 itSub('Superuser can remove a member', async ({helper}) => {169 await expect(helper.getSudo().council.membership.removeMember(sudoer, counselors.alex.address)).to.be.fulfilled;170171 const members = (await helper.callRpc('api.query.councilMembership.members')).toJSON();172 expect(members).to.not.contains(counselors.alex.address);173 });174175 itSub('>50% Council can add TechComm member', async ({helper}) => {176 const newTechCommMember = helper.arrange.createEmptyAccount();177 const addMemberProposal = helper.technicalCommittee.membership.addMemberCall(newTechCommMember.address);178179 await proposalFromMoreThanHalfCouncil(addMemberProposal);180181 const techCommMembers = (await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON();182 expect(techCommMembers).to.contains(newTechCommMember.address);183 });184185 itSub('Council can remove TechComm member', async ({helper}) => {186 const techComm = await initTechComm(donor, sudoer);187 const removeMemberPrpoposal = helper.technicalCommittee.membership.removeMemberCall(techComm.andy.address);188 await proposalFromMoreThanHalfCouncil(removeMemberPrpoposal);189190 const techCommMembers = (await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON();191 expect(techCommMembers).to.not.contains(techComm.andy.address);192 });193194 itSub.skip('Council member can add Fellowship member', async ({helper}) => {195 const newFellowshipMember = helper.arrange.createEmptyAccount();196 await expect(helper.council.collective.execute(197 counselors.alex,198 helper.fellowship.collective.addMemberCall(newFellowshipMember.address),199 )).to.be.fulfilled;200 const fellowshipMembers = (await helper.callRpc('api.query.fellowshipCollective.members')).toJSON();201 expect(fellowshipMembers).to.contains(newFellowshipMember.address);202 });203204 itSub('>50% Council can promote Fellowship member', async ({helper}) => {205 const fellowship = await initFellowship(donor, sudoer);206 const memberWithZeroRank = fellowship[0][0];207208 const proposal = helper.fellowship.collective.promoteCall(memberWithZeroRank.address);209 await proposalFromMoreThanHalfCouncil(proposal);210 const record = (await helper.callRpc('api.query.fellowshipCollective.members', [memberWithZeroRank.address])).toJSON();211 expect(record).to.be.deep.equal({rank: 1});212213 await clearFellowship(sudoer);214 });215216 itSub('>50% Council can demote Fellowship member', async ({helper}) => {217 const fellowship = await initFellowship(donor, sudoer);218 const memberWithRankOne = fellowship[1][0];219220 const proposal = helper.fellowship.collective.demoteCall(memberWithRankOne.address);221 await proposalFromMoreThanHalfCouncil(proposal);222223 const record = (await helper.callRpc('api.query.fellowshipCollective.members', [memberWithRankOne.address])).toJSON();224 expect(record).to.be.deep.equal({rank: 0});225226 await clearFellowship(sudoer);227 });228229 itSub('>50% Council can add\remove Fellowship member', async ({helper}) => {230 try {231 const newMember = helper.arrange.createEmptyAccount();232233 const proposalAdd = helper.fellowship.collective.addMemberCall(newMember.address);234 const proposalRemove = helper.fellowship.collective.removeMemberCall(newMember.address, fellowshipRankLimit);235 await expect(proposalFromMoreThanHalfCouncil(proposalAdd)).to.be.fulfilled;236 expect(await helper.fellowship.collective.getMembers()).to.be.deep.contain(newMember.address);237 await expect(proposalFromMoreThanHalfCouncil(proposalRemove)).to.be.fulfilled;238 expect(await helper.fellowship.collective.getMembers()).to.be.not.deep.contain(newMember.address);239 }240 finally {241 await clearFellowship(sudoer);242 }243 });244245 itSub('Council can blacklist Democracy proposals', async ({helper}) => {246 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);247 await expect(proposalFromAllCouncil(helper.democracy.blacklistCall(preimageHash, null))).to.be.fulfilled;248 });249250 itSub('Sudo can blacklist Democracy proposals', async ({helper}) => {251 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);252 await expect(helper.getSudo().democracy.blacklist(sudoer, preimageHash)).to.be.fulfilled;253 });254255 itSub('[Negative] Council cannot add Council member', async ({helper}) => {256 const newCouncilMember = helper.arrange.createEmptyAccount();257 const addMemberProposal = helper.council.membership.addMemberCall(newCouncilMember.address);258259 await expect(proposalFromAllCouncil(addMemberProposal)).to.be.rejected;260 });261262 itSub('[Negative] Council cannot remove Council member', async ({helper}) => {263 const removeMemberProposal = helper.council.membership.removeMemberCall(counselors.alex.address);264265 await expect(proposalFromAllCouncil(removeMemberProposal)).to.be.rejected;266 });267268 itSub('[Negative] Council cannot submit regular democracy proposal', async ({helper}) => {269 const councilProposal = await helper.democracy.proposeCall(dummyProposalCall(helper), 0n);270271 await expect(proposalFromAllCouncil(councilProposal)).to.be.rejectedWith(/BadOrigin/);272 });273274 itSub('[Negative] Council cannot externally propose SimpleMajority', async ({helper}) => {275 const councilProposal = await helper.democracy.externalProposeMajorityCall(dummyProposalCall(helper));276277 await expect(proposalFromAllCouncil(councilProposal)).to.be.rejectedWith(/BadOrigin/);278 });279280 itSub('[Negative] Council cannot externally propose SuperMajorityApprove', async ({helper}) => {281 const councilProposal = await helper.democracy.externalProposeCall(dummyProposalCall(helper));282283 await expect(proposalFromAllCouncil(councilProposal)).to.be.rejectedWith(/BadOrigin/);284 });285286 itSub('[Negative] Council member cannot add/remove a Council member', async ({helper}) => {287 const newCouncilMember = helper.arrange.createEmptyAccount();288 await expect(helper.council.collective.execute(289 counselors.alex,290 helper.council.membership.addMemberCall(newCouncilMember.address),291 )).to.be.rejectedWith('BadOrigin');292 await expect(helper.council.collective.execute(293 counselors.alex,294 helper.council.membership.removeMemberCall(counselors.charu.address),295 )).to.be.rejectedWith('BadOrigin');296 });297298 itSub('[Negative] Council cannot set/clear Council prime member', async ({helper}) => {299 const proposalForSet = await helper.council.membership.setPrimeCall(counselors.charu.address);300 const proposalForClear = await helper.council.membership.clearPrimeCall();301302 await expect(proposalFromAllCouncil(proposalForSet)).to.be.rejectedWith(/BadOrigin/);303 await expect(proposalFromAllCouncil(proposalForClear)).to.be.rejectedWith(/BadOrigin/);304305 });306307 itSub('[Negative] Council member cannot set/clear Council prime member', async ({helper}) => {308 await expect(helper.council.collective.execute(309 counselors.alex,310 helper.council.membership.setPrimeCall(counselors.charu.address),311 )).to.be.rejectedWith('BadOrigin');312 await expect(helper.council.collective.execute(313 counselors.alex,314 helper.council.membership.clearPrimeCall(),315 )).to.be.rejectedWith('BadOrigin');316 });317318 itSub('[Negative] Council member cannot add/remove a TechComm member', async ({helper}) => {319 const newTechCommMember = helper.arrange.createEmptyAccount();320 await expect(helper.council.collective.execute(321 counselors.alex,322 helper.technicalCommittee.membership.addMemberCall(newTechCommMember.address),323 )).to.be.rejectedWith('BadOrigin');324 await expect(helper.council.collective.execute(325 counselors.alex,326 helper.technicalCommittee.membership.removeMemberCall(newTechCommMember.address),327 )).to.be.rejectedWith('BadOrigin');328 });329330 itSub('[Negative] Council member cannot promote/demote a Fellowship member', async ({helper}) => {331 const fellowship = await initFellowship(donor, sudoer);332 const memberWithRankOne = fellowship[1][0];333334 await expect(helper.council.collective.execute(335 counselors.alex,336 helper.fellowship.collective.promoteCall(memberWithRankOne.address),337 )).to.be.rejectedWith('BadOrigin');338 await expect(helper.council.collective.execute(339 counselors.alex,340 helper.fellowship.collective.demoteCall(memberWithRankOne.address),341 )).to.be.rejectedWith('BadOrigin');342 await clearFellowship(sudoer);343 });344345 itSub('[Negative] Council cannot fast-track Democracy proposals', async ({helper}) => {346 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);347 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);348349 await expect(proposalFromAllCouncil(helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0)))350 .to.be.rejectedWith(/BadOrigin/);351 });352353 itSub('[Negative] Council member cannot fast-track Democracy proposals', async ({helper}) => {354 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);355 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);356357 await expect(helper.council.collective.execute(358 counselors.alex,359 helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0),360 )).to.be.rejectedWith('BadOrigin');361 });362363 itSub('[Negative] Council cannot cancel Democracy proposals', async ({helper}) => {364 const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);365 const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;366367 await expect(proposalFromAllCouncil(helper.democracy.cancelProposalCall(proposalIndex)))368 .to.be.rejectedWith(/BadOrigin/);369 });370371 itSub('[Negative] Council member cannot cancel Democracy proposals', async ({helper}) => {372373 const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);374 const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;375376 await expect(helper.council.collective.execute(377 counselors.alex,378 helper.democracy.cancelProposalCall(proposalIndex),379 )).to.be.rejectedWith('BadOrigin');380 });381382 itSub('[Negative] Council cannot cancel ongoing Democracy referendums', async ({helper}) => {383 await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper));384 const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);385 const referendumIndex = startedEvent.referendumIndex;386387 await expect(proposalFromAllCouncil(helper.democracy.emergencyCancelCall(referendumIndex)))388 .to.be.rejectedWith(/BadOrigin/);389 });390391 itSub('[Negative] Council member cannot cancel ongoing Democracy referendums', async ({helper}) => {392 await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper));393 const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);394 const referendumIndex = startedEvent.referendumIndex;395396 await expect(helper.council.collective.execute(397 counselors.alex,398 helper.democracy.emergencyCancelCall(referendumIndex),399 )).to.be.rejectedWith('BadOrigin');400 });401402 itSub('[Negative] Council cannot cancel Fellowship referendums', async ({helper}) => {403 const fellowship = await initFellowship(donor, sudoer);404 const fellowshipProposer = fellowship[5][0];405 const proposal = dummyProposal(helper);406407 const submitResult = await helper.fellowship.referenda.submit(408 fellowshipProposer,409 fellowshipPropositionOrigin,410 proposal,411 defaultEnactmentMoment,412 );413414 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;415416 await expect(proposalFromAllCouncil(helper.fellowship.referenda.cancelCall(referendumIndex)))417 .to.be.rejectedWith(/BadOrigin/);418 });419420 itSub('[Negative] Council member cannot cancel Fellowship referendums', async ({helper}) => {421 const fellowship = await initFellowship(donor, sudoer);422 const fellowshipProposer = fellowship[5][0];423 const proposal = dummyProposal(helper);424425 const submitResult = await helper.fellowship.referenda.submit(426 fellowshipProposer,427 fellowshipPropositionOrigin,428 proposal,429 defaultEnactmentMoment,430 );431 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;432 await expect(helper.council.collective.execute(433 counselors.alex,434 helper.fellowship.referenda.cancelCall(referendumIndex),435 )).to.be.rejectedWith('BadOrigin');436 });437438 itSub('[Negative] Council referendum cannot be closed until the voting threshold is met', async ({helper}) => {439 const councilSize = (await helper.callRpc('api.query.councilMembership.members')).toJSON().length as any as number;440 expect(councilSize).is.greaterThan(1);441 const proposeResult = await helper.council.collective.propose(442 counselors.filip,443 dummyProposalCall(helper),444 councilSize,445 );446447 const councilProposedEvent = Event.Council.Proposed.expect(proposeResult);448 const proposalIndex = councilProposedEvent.proposalIndex;449 const proposalHash = councilProposedEvent.proposalHash;450451452 await helper.council.collective.vote(counselors.alex, proposalHash, proposalIndex, true);453 await expect(helper.council.collective.close(counselors.filip, proposalHash, proposalIndex)).to.be.rejectedWith('TooEarly');454 });455456});