1import {IKeyringPair} from '@polkadot/types/types';2import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../util';3import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';4import {ICounselors, initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyFastTrackVotingPeriod, fellowshipRankLimit, clearCouncil, clearTechComm, ITechComms, clearFellowship, defaultEnactmentMoment, dummyProposal, dummyProposalCall, fellowshipPropositionOrigin, initFellowship, initTechComm, voteUnanimouslyInFellowship, democracyTrackMinRank, fellowshipPreparePeriod, fellowshipConfirmPeriod, fellowshipMinEnactPeriod, democracyTrackId, hardResetFellowshipReferenda, hardResetDemocracy, hardResetGovScheduler} from './util';56describeGov('Governance: Fellowship tests', () => {7 let members: IKeyringPair[][];89 let rank1Proposer: IKeyringPair;1011 let sudoer: any;12 let donor: any;13 let counselors: ICounselors;14 let techcomms: ITechComms;1516 const submissionDeposit = 1000n;1718 async function testBadFellowshipProposal(19 helper: DevUniqueHelper,20 proposalCall: any,21 ) {22 const badProposal = {23 Inline: proposalCall.method.toHex(),24 };25 const submitResult = await helper.fellowship.referenda.submit(26 rank1Proposer,27 fellowshipPropositionOrigin,28 badProposal,29 defaultEnactmentMoment,30 );3132 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;33 await voteUnanimouslyInFellowship(helper, members, democracyTrackMinRank, referendumIndex);34 await helper.fellowship.referenda.placeDecisionDeposit(donor, referendumIndex);3536 const enactmentId = await helper.fellowship.referenda.enactmentEventId(referendumIndex);37 const dispatchedEvent = await helper.wait.expectEvent(38 fellowshipPreparePeriod + fellowshipConfirmPeriod + defaultEnactmentMoment.After,39 Event.Scheduler.Dispatched,40 (event: any) => event.id == enactmentId,41 );4243 expect(dispatchedEvent.result.isErr, 'Bad Fellowship must fail')44 .to.be.true;4546 expect(dispatchedEvent.result.asErr.isBadOrigin, 'Bad Fellowship must fail with BadOrigin')47 .to.be.true;48 }4950 before(async function() {51 await usingPlaygrounds(async (helper, privateKey) => {52 requirePalletsOrSkip(this, helper, [Pallets.Democracy, Pallets.Fellowship, Pallets.TechnicalCommittee, Pallets.Council]);5354 sudoer = await privateKey('//Alice');55 donor = await privateKey({url: import.meta.url});56 });5758 counselors = await initCouncil(donor, sudoer);59 techcomms = await initTechComm(donor, sudoer);60 members = await initFellowship(donor, sudoer);6162 rank1Proposer = members[1][0];63 });6465 after(async () => {66 await clearFellowship(sudoer);67 await clearTechComm(sudoer);68 await clearCouncil(sudoer);69 await hardResetFellowshipReferenda(sudoer);70 await hardResetDemocracy(sudoer);71 await hardResetGovScheduler(sudoer);72 });7374 itSub('FellowshipProposition can submit regular Democracy proposals', async ({helper}) => {75 const democracyProposalCall = dummyProposalCall(helper);76 const fellowshipProposal = {77 Inline: helper.democracy.proposeCall(democracyProposalCall, 0n).method.toHex(),78 };7980 const submitResult = await helper.fellowship.referenda.submit(81 rank1Proposer,82 fellowshipPropositionOrigin,83 fellowshipProposal,84 defaultEnactmentMoment,85 );8687 const fellowshipReferendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;88 await voteUnanimouslyInFellowship(helper, members, democracyTrackMinRank, fellowshipReferendumIndex);89 await helper.fellowship.referenda.placeDecisionDeposit(donor, fellowshipReferendumIndex);9091 const democracyProposed = await helper.wait.expectEvent(92 fellowshipPreparePeriod + fellowshipConfirmPeriod + fellowshipMinEnactPeriod,93 Event.Democracy.Proposed,94 );9596 const democracyEnqueuedProposal = await helper.democracy.expectPublicProposal(democracyProposed.proposalIndex);97 expect(democracyEnqueuedProposal.inline, 'Fellowship proposal expected to be in the Democracy')98 .to.be.equal(democracyProposalCall.method.toHex());99100 await helper.wait.newBlocks(democracyVotingPeriod);101 });102103 itSub('Fellowship (rank-1 or greater) member can submit Fellowship proposals on the Democracy track', async ({helper}) => {104 for(let rank = 1; rank < fellowshipRankLimit; rank++) {105 const rankMembers = members[rank];106107 for(let memberIdx = 0; memberIdx < rankMembers.length; memberIdx++) {108 const member = rankMembers[memberIdx];109 const newDummyProposal = dummyProposal(helper);110111 const submitResult = await helper.fellowship.referenda.submit(112 member,113 fellowshipPropositionOrigin,114 newDummyProposal,115 defaultEnactmentMoment,116 );117118 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;119 const referendumInfo = await helper.fellowship.referenda.referendumInfo(referendumIndex);120 expect(referendumInfo.ongoing.track, `${memberIdx}-th member of rank #${rank}: proposal #${referendumIndex} is on invalid track`)121 .to.be.equal(democracyTrackId);122 }123 }124 });125126 itSub(`Fellowship (rank-${democracyTrackMinRank} or greater) members can vote on the Democracy track`, async ({helper}) => {127 const proposal = dummyProposal(helper);128129 const submitResult = await helper.fellowship.referenda.submit(130 rank1Proposer,131 fellowshipPropositionOrigin,132 proposal,133 defaultEnactmentMoment,134 );135136 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;137138 let expectedAyes = 0;139 for(let rank = democracyTrackMinRank; rank < fellowshipRankLimit; rank++) {140 const rankMembers = members[rank];141142 for(let memberIdx = 0; memberIdx < rankMembers.length; memberIdx++) {143 const member = rankMembers[memberIdx];144 await helper.fellowship.collective.vote(member, referendumIndex, true);145 expectedAyes += 1;146147 const referendumInfo = await helper.fellowship.referenda.referendumInfo(referendumIndex);148 expect(referendumInfo.ongoing.tally.bareAyes, `Vote from ${memberIdx}-th member of rank #${rank} isn't accounted`)149 .to.be.equal(expectedAyes);150 }151 }152 });153154 itSub('Fellowship rank vote strength is correct', async ({helper}) => {155 const excessRankWeightTable = [156 1,157 3,158 6,159 10,160 15,161 21,162 ];163164 const proposal = dummyProposal(helper);165166 const submitResult = await helper.fellowship.referenda.submit(167 rank1Proposer,168 fellowshipPropositionOrigin,169 proposal,170 defaultEnactmentMoment,171 );172173 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;174175 for(let rank = democracyTrackMinRank; rank < fellowshipRankLimit; rank++) {176 const rankMembers = members[rank];177178 for(let memberIdx = 0; memberIdx < rankMembers.length; memberIdx++) {179 const member = rankMembers[memberIdx];180181 const referendumInfoBefore = await helper.fellowship.referenda.referendumInfo(referendumIndex);182 const ayesBefore = referendumInfoBefore.ongoing.tally.ayes;183184 await helper.fellowship.collective.vote(member, referendumIndex, true);185186 const referendumInfoAfter = await helper.fellowship.referenda.referendumInfo(referendumIndex);187 const ayesAfter = referendumInfoAfter.ongoing.tally.ayes;188189 const expectedVoteWeight = excessRankWeightTable[rank - democracyTrackMinRank];190 const voteWeight = ayesAfter - ayesBefore;191192 expect(voteWeight, `Vote weight of ${memberIdx}-th member of rank #${rank} is invalid`)193 .to.be.equal(expectedVoteWeight);194 }195 }196 });197198 itSub('[Negative] FellowshipProposition cannot externally propose SuperMajorityAgainst', async ({helper}) => {199 await testBadFellowshipProposal(helper, helper.democracy.externalProposeDefaultCall(dummyProposalCall(helper)));200 });201202 itSub('[Negative] FellowshipProposition cannot externally propose SimpleMajority', async ({helper}) => {203 await testBadFellowshipProposal(helper, helper.democracy.externalProposeMajorityCall(dummyProposalCall(helper)));204 });205206 itSub('[Negative] FellowshipProposition cannot externally propose SuperMajorityApprove', async ({helper}) => {207 await testBadFellowshipProposal(helper, helper.democracy.externalProposeCall(dummyProposalCall(helper)));208 });209210 itSub('[Negative] Fellowship (rank-0) member cannot submit Fellowship proposals on the Democracy track', async ({helper}) => {211 const rank0Proposer = members[0][0];212213 const proposal = dummyProposal(helper);214215 const submitResult = helper.fellowship.referenda.submit(216 rank0Proposer,217 fellowshipPropositionOrigin,218 proposal,219 defaultEnactmentMoment,220 );221222 await expect(submitResult).to.be.rejectedWith(/BadOrigin/);223 });224225 itSub('[Negative] Fellowship (rank-1 or greater) member cannot submit if no deposit can be provided', async ({helper}) => {226 const poorMember = rank1Proposer;227228 const balanceBefore = await helper.balance.getSubstrate(poorMember.address);229 await helper.getSudo().balance.setBalanceSubstrate(sudoer, poorMember.address, submissionDeposit - 1n);230231 const proposal = dummyProposal(helper);232233 const submitResult = helper.fellowship.referenda.submit(234 poorMember,235 fellowshipPropositionOrigin,236 proposal,237 defaultEnactmentMoment,238 );239240 await expect(submitResult).to.be.rejectedWith(/account balance too low/);241242 await helper.getSudo().balance.setBalanceSubstrate(sudoer, poorMember.address, balanceBefore);243 });244245 itSub(`[Negative] Fellowship (rank-${democracyTrackMinRank - 1} or less) members cannot vote on the Democracy track`, async ({helper}) => {246 const proposal = dummyProposal(helper);247248 const submitResult = await helper.fellowship.referenda.submit(249 rank1Proposer,250 fellowshipPropositionOrigin,251 proposal,252 defaultEnactmentMoment,253 );254255 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;256257 for(let rank = 0; rank < democracyTrackMinRank; rank++) {258 for(const member of members[rank]) {259 const voteResult = helper.fellowship.collective.vote(member, referendumIndex, true);260 await expect(voteResult).to.be.rejectedWith(/RankTooLow/);261 }262 }263 });264265 itSub('[Negative] FellowshipProposition cannot add/remove a Council member', async ({helper}) => {266 const [councilNonMember] = await helper.arrange.createAccounts([10n], donor);267268 await testBadFellowshipProposal(helper, helper.council.membership.addMemberCall(councilNonMember.address));269 await testBadFellowshipProposal(helper, helper.council.membership.removeMemberCall(counselors.ildar.address));270 });271272 itSub('[Negative] FellowshipProposition cannot set/clear Council prime member', async ({helper}) => {273 await testBadFellowshipProposal(helper, helper.council.membership.setPrimeCall(counselors.ildar.address));274 await testBadFellowshipProposal(helper, helper.council.membership.clearPrimeCall());275 });276277 itSub('[Negative] FellowshipProposition cannot add/remove a TechComm member', async ({helper}) => {278 const [techCommNonMember] = await helper.arrange.createAccounts([10n], donor);279280 await testBadFellowshipProposal(helper, helper.technicalCommittee.membership.addMemberCall(techCommNonMember.address));281 await testBadFellowshipProposal(helper, helper.technicalCommittee.membership.removeMemberCall(techcomms.constantine.address));282 });283284 itSub('[Negative] FellowshipProposition cannot add/remove a Fellowship member', async ({helper}) => {285 const [fellowshipNonMember] = await helper.arrange.createAccounts([10n], donor);286287 await testBadFellowshipProposal(helper, helper.fellowship.collective.addMemberCall(fellowshipNonMember.address));288 await testBadFellowshipProposal(helper, helper.fellowship.collective.removeMemberCall(rank1Proposer.address, 1));289 });290291 itSub('[Negative] FellowshipProposition cannot promote/demote a Fellowship member', async ({helper}) => {292 await testBadFellowshipProposal(helper, helper.fellowship.collective.promoteCall(rank1Proposer.address));293 await testBadFellowshipProposal(helper, helper.fellowship.collective.demoteCall(rank1Proposer.address));294 });295296 itSub('[Negative] FellowshipProposition cannot fast-track Democracy proposals', async ({helper}) => {297 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);298299 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);300301 await testBadFellowshipProposal(helper, helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0));302 });303304 itSub('[Negative] FellowshipProposition cannot cancel Democracy proposals', async ({helper}) => {305 const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);306 const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;307308 await testBadFellowshipProposal(helper, helper.democracy.cancelProposalCall(proposalIndex));309 });310311 itSub('[Negative] FellowshipProposition cannot cancel ongoing Democracy referendums', async ({helper}) => {312 await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper));313 const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);314 const referendumIndex = startedEvent.referendumIndex;315316 await testBadFellowshipProposal(helper, helper.democracy.emergencyCancelCall(referendumIndex));317 });318319 itSub('[Negative] FellowshipProposition cannot blacklist Democracy proposals', async ({helper}) => {320 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);321322 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);323324 await testBadFellowshipProposal(helper, helper.democracy.blacklistCall(preimageHash, null));325 });326327 itSub('[Negative] FellowshipProposition cannot veto external proposals', async ({helper}) => {328 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);329330 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);331332 await testBadFellowshipProposal(helper, helper.democracy.vetoExternalCall(preimageHash));333 });334});