1import type {IKeyringPair} from '@polkadot/types/types';2import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../util/index.js';3import {DevUniqueHelper, Event} from '@unique/playgrounds/src/unique.dev.js';4import {initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyFastTrackVotingPeriod, fellowshipRankLimit, clearCouncil, clearTechComm, clearFellowship, defaultEnactmentMoment, dummyProposal, dummyProposalCall, fellowshipPropositionOrigin, initFellowship, initTechComm, voteUnanimouslyInFellowship, democracyTrackMinRank, fellowshipPreparePeriod, fellowshipConfirmPeriod, fellowshipMinEnactPeriod, democracyTrackId, hardResetFellowshipReferenda, hardResetDemocracy, hardResetGovScheduler} from './util.js';5import type {ICounselors, ITechComms} from './util.js';67describeGov('Governance: Fellowship tests', () => {8 let members: IKeyringPair[][];910 let rank1Proposer: IKeyringPair;1112 let sudoer: any;13 let donor: any;14 let counselors: ICounselors;15 let techcomms: ITechComms;1617 const submissionDeposit = 1000n;1819 async function testBadFellowshipProposal(20 helper: DevUniqueHelper,21 proposalCall: any,22 ) {23 const badProposal = {24 Inline: proposalCall.method.toHex(),25 };26 const submitResult = await helper.fellowship.referenda.submit(27 rank1Proposer,28 fellowshipPropositionOrigin,29 badProposal,30 defaultEnactmentMoment,31 );3233 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;34 await voteUnanimouslyInFellowship(helper, members, democracyTrackMinRank, referendumIndex);35 await helper.fellowship.referenda.placeDecisionDeposit(donor, referendumIndex);3637 const enactmentId = await helper.fellowship.referenda.enactmentEventId(referendumIndex);38 const dispatchedEvent = await helper.wait.expectEvent(39 fellowshipPreparePeriod + fellowshipConfirmPeriod + defaultEnactmentMoment.After,40 Event.Scheduler.Dispatched,41 (event: any) => event.id == enactmentId,42 );4344 expect(dispatchedEvent.result.isErr, 'Bad Fellowship must fail')45 .to.be.true;4647 expect(dispatchedEvent.result.asErr.isBadOrigin, 'Bad Fellowship must fail with BadOrigin')48 .to.be.true;49 }5051 before(async function() {52 await usingPlaygrounds(async (helper, privateKey) => {53 requirePalletsOrSkip(this, helper, [Pallets.Democracy, Pallets.Fellowship, Pallets.TechnicalCommittee, Pallets.Council]);5455 sudoer = await privateKey('//Alice');56 donor = await privateKey({url: import.meta.url});57 });5859 counselors = await initCouncil(donor, sudoer);60 techcomms = await initTechComm(donor, sudoer);61 members = await initFellowship(donor, sudoer);6263 rank1Proposer = members[1][0];64 });6566 after(async () => {67 await clearFellowship(sudoer);68 await clearTechComm(sudoer);69 await clearCouncil(sudoer);70 await hardResetFellowshipReferenda(sudoer);71 await hardResetDemocracy(sudoer);72 await hardResetGovScheduler(sudoer);73 });7475 itSub('FellowshipProposition can submit regular Democracy proposals', async ({helper}) => {76 const democracyProposalCall = dummyProposalCall(helper);77 const fellowshipProposal = {78 Inline: helper.democracy.proposeCall(democracyProposalCall, 0n).method.toHex(),79 };8081 const submitResult = await helper.fellowship.referenda.submit(82 rank1Proposer,83 fellowshipPropositionOrigin,84 fellowshipProposal,85 defaultEnactmentMoment,86 );8788 const fellowshipReferendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;89 await voteUnanimouslyInFellowship(helper, members, democracyTrackMinRank, fellowshipReferendumIndex);90 await helper.fellowship.referenda.placeDecisionDeposit(donor, fellowshipReferendumIndex);9192 const democracyProposed = await helper.wait.expectEvent(93 fellowshipPreparePeriod + fellowshipConfirmPeriod + fellowshipMinEnactPeriod,94 Event.Democracy.Proposed,95 );9697 const democracyEnqueuedProposal = await helper.democracy.expectPublicProposal(democracyProposed.proposalIndex);98 expect(democracyEnqueuedProposal.inline, 'Fellowship proposal expected to be in the Democracy')99 .to.be.equal(democracyProposalCall.method.toHex());100101 await helper.wait.newBlocks(democracyVotingPeriod);102 });103104 itSub('Fellowship (rank-1 or greater) member can submit Fellowship proposals on the Democracy track', async ({helper}) => {105 for(let rank = 1; rank < fellowshipRankLimit; rank++) {106 const rankMembers = members[rank];107108 for(let memberIdx = 0; memberIdx < rankMembers.length; memberIdx++) {109 const member = rankMembers[memberIdx];110 const newDummyProposal = dummyProposal(helper);111112 const submitResult = await helper.fellowship.referenda.submit(113 member,114 fellowshipPropositionOrigin,115 newDummyProposal,116 defaultEnactmentMoment,117 );118119 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;120 const referendumInfo = await helper.fellowship.referenda.referendumInfo(referendumIndex);121 expect(referendumInfo.ongoing.track, `${memberIdx}-th member of rank #${rank}: proposal #${referendumIndex} is on invalid track`)122 .to.be.equal(democracyTrackId);123 }124 }125 });126127 itSub(`Fellowship (rank-${democracyTrackMinRank} or greater) members can vote on the Democracy track`, async ({helper}) => {128 const proposal = dummyProposal(helper);129130 const submitResult = await helper.fellowship.referenda.submit(131 rank1Proposer,132 fellowshipPropositionOrigin,133 proposal,134 defaultEnactmentMoment,135 );136137 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;138139 let expectedAyes = 0;140 for(let rank = democracyTrackMinRank; rank < fellowshipRankLimit; rank++) {141 const rankMembers = members[rank];142143 for(let memberIdx = 0; memberIdx < rankMembers.length; memberIdx++) {144 const member = rankMembers[memberIdx];145 await helper.fellowship.collective.vote(member, referendumIndex, true);146 expectedAyes += 1;147148 const referendumInfo = await helper.fellowship.referenda.referendumInfo(referendumIndex);149 expect(referendumInfo.ongoing.tally.bareAyes, `Vote from ${memberIdx}-th member of rank #${rank} isn't accounted`)150 .to.be.equal(expectedAyes);151 }152 }153 });154155 itSub('Fellowship rank vote strength is correct', async ({helper}) => {156 const excessRankWeightTable = [157 1,158 3,159 6,160 10,161 15,162 21,163 ];164165 const proposal = dummyProposal(helper);166167 const submitResult = await helper.fellowship.referenda.submit(168 rank1Proposer,169 fellowshipPropositionOrigin,170 proposal,171 defaultEnactmentMoment,172 );173174 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;175176 for(let rank = democracyTrackMinRank; rank < fellowshipRankLimit; rank++) {177 const rankMembers = members[rank];178179 for(let memberIdx = 0; memberIdx < rankMembers.length; memberIdx++) {180 const member = rankMembers[memberIdx];181182 const referendumInfoBefore = await helper.fellowship.referenda.referendumInfo(referendumIndex);183 const ayesBefore = referendumInfoBefore.ongoing.tally.ayes;184185 await helper.fellowship.collective.vote(member, referendumIndex, true);186187 const referendumInfoAfter = await helper.fellowship.referenda.referendumInfo(referendumIndex);188 const ayesAfter = referendumInfoAfter.ongoing.tally.ayes;189190 const expectedVoteWeight = excessRankWeightTable[rank - democracyTrackMinRank];191 const voteWeight = ayesAfter - ayesBefore;192193 expect(voteWeight, `Vote weight of ${memberIdx}-th member of rank #${rank} is invalid`)194 .to.be.equal(expectedVoteWeight);195 }196 }197 });198199 itSub('[Negative] FellowshipProposition cannot externally propose SuperMajorityAgainst', async ({helper}) => {200 await testBadFellowshipProposal(helper, helper.democracy.externalProposeDefaultCall(dummyProposalCall(helper)));201 });202203 itSub('[Negative] FellowshipProposition cannot externally propose SimpleMajority', async ({helper}) => {204 await testBadFellowshipProposal(helper, helper.democracy.externalProposeMajorityCall(dummyProposalCall(helper)));205 });206207 itSub('[Negative] FellowshipProposition cannot externally propose SuperMajorityApprove', async ({helper}) => {208 await testBadFellowshipProposal(helper, helper.democracy.externalProposeCall(dummyProposalCall(helper)));209 });210211 itSub('[Negative] Fellowship (rank-0) member cannot submit Fellowship proposals on the Democracy track', async ({helper}) => {212 const rank0Proposer = members[0][0];213214 const proposal = dummyProposal(helper);215216 const submitResult = helper.fellowship.referenda.submit(217 rank0Proposer,218 fellowshipPropositionOrigin,219 proposal,220 defaultEnactmentMoment,221 );222223 await expect(submitResult).to.be.rejectedWith(/BadOrigin/);224 });225226 itSub('[Negative] Fellowship (rank-1 or greater) member cannot submit if no deposit can be provided', async ({helper}) => {227 const poorMember = rank1Proposer;228229 const balanceBefore = await helper.balance.getSubstrate(poorMember.address);230 await helper.getSudo().balance.setBalanceSubstrate(sudoer, poorMember.address, submissionDeposit - 1n);231232 const proposal = dummyProposal(helper);233234 const submitResult = helper.fellowship.referenda.submit(235 poorMember,236 fellowshipPropositionOrigin,237 proposal,238 defaultEnactmentMoment,239 );240241 await expect(submitResult).to.be.rejectedWith(/account balance too low/);242243 await helper.getSudo().balance.setBalanceSubstrate(sudoer, poorMember.address, balanceBefore);244 });245246 itSub(`[Negative] Fellowship (rank-${democracyTrackMinRank - 1} or less) members cannot vote on the Democracy track`, async ({helper}) => {247 const proposal = dummyProposal(helper);248249 const submitResult = await helper.fellowship.referenda.submit(250 rank1Proposer,251 fellowshipPropositionOrigin,252 proposal,253 defaultEnactmentMoment,254 );255256 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;257258 for(let rank = 0; rank < democracyTrackMinRank; rank++) {259 for(const member of members[rank]) {260 const voteResult = helper.fellowship.collective.vote(member, referendumIndex, true);261 await expect(voteResult).to.be.rejectedWith(/RankTooLow/);262 }263 }264 });265266 itSub('[Negative] FellowshipProposition cannot add/remove a Council member', async ({helper}) => {267 const [councilNonMember] = await helper.arrange.createAccounts([10n], donor);268269 await testBadFellowshipProposal(helper, helper.council.membership.addMemberCall(councilNonMember.address));270 await testBadFellowshipProposal(helper, helper.council.membership.removeMemberCall(counselors.ildar.address));271 });272273 itSub('[Negative] FellowshipProposition cannot set/clear Council prime member', async ({helper}) => {274 await testBadFellowshipProposal(helper, helper.council.membership.setPrimeCall(counselors.ildar.address));275 await testBadFellowshipProposal(helper, helper.council.membership.clearPrimeCall());276 });277278 itSub('[Negative] FellowshipProposition cannot add/remove a TechComm member', async ({helper}) => {279 const [techCommNonMember] = await helper.arrange.createAccounts([10n], donor);280281 await testBadFellowshipProposal(helper, helper.technicalCommittee.membership.addMemberCall(techCommNonMember.address));282 await testBadFellowshipProposal(helper, helper.technicalCommittee.membership.removeMemberCall(techcomms.constantine.address));283 });284285 itSub('[Negative] FellowshipProposition cannot add/remove a Fellowship member', async ({helper}) => {286 const [fellowshipNonMember] = await helper.arrange.createAccounts([10n], donor);287288 await testBadFellowshipProposal(helper, helper.fellowship.collective.addMemberCall(fellowshipNonMember.address));289 await testBadFellowshipProposal(helper, helper.fellowship.collective.removeMemberCall(rank1Proposer.address, 1));290 });291292 itSub('[Negative] FellowshipProposition cannot promote/demote a Fellowship member', async ({helper}) => {293 await testBadFellowshipProposal(helper, helper.fellowship.collective.promoteCall(rank1Proposer.address));294 await testBadFellowshipProposal(helper, helper.fellowship.collective.demoteCall(rank1Proposer.address));295 });296297 itSub('[Negative] FellowshipProposition cannot fast-track Democracy proposals', async ({helper}) => {298 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);299300 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);301302 await testBadFellowshipProposal(helper, helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0));303 });304305 itSub('[Negative] FellowshipProposition cannot cancel Democracy proposals', async ({helper}) => {306 const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);307 const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;308309 await testBadFellowshipProposal(helper, helper.democracy.cancelProposalCall(proposalIndex));310 });311312 itSub('[Negative] FellowshipProposition cannot cancel ongoing Democracy referendums', async ({helper}) => {313 await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper));314 const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);315 const referendumIndex = startedEvent.referendumIndex;316317 await testBadFellowshipProposal(helper, helper.democracy.emergencyCancelCall(referendumIndex));318 });319320 itSub('[Negative] FellowshipProposition cannot blacklist Democracy proposals', async ({helper}) => {321 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);322323 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);324325 await testBadFellowshipProposal(helper, helper.democracy.blacklistCall(preimageHash, null));326 });327328 itSub('[Negative] FellowshipProposition cannot veto external proposals', async ({helper}) => {329 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);330331 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);332333 await testBadFellowshipProposal(helper, helper.democracy.vetoExternalCall(preimageHash));334 });335});