From ed0719fab73dc2017e47878a32bde025ebfc6288 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Wed, 5 Jun 2024 14:13:51 +0400 Subject: [PATCH] Tests/fin council (#1070) test: add techComm+finCouncil tests, remove duplicates --- js-packages/.vscode/settings.json | 2 + js-packages/test-utils/index.ts | 22 + js-packages/test-utils/util.ts | 1 + .../tests/sub/governance/council.test.ts | 37 +- .../sub/governance/financialCouncil.test.ts | 406 ++++++++++++++++++ .../sub/governance/technicalCommittee.test.ts | 44 ++ js-packages/tests/sub/governance/util.ts | 50 +++ 7 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 js-packages/tests/sub/governance/financialCouncil.test.ts diff --git a/js-packages/.vscode/settings.json b/js-packages/.vscode/settings.json index f4f218362b..6cd8f90873 100644 --- a/js-packages/.vscode/settings.json +++ b/js-packages/.vscode/settings.json @@ -5,6 +5,8 @@ }, "mochaExplorer.files": "tests/**/*.test.ts", "mochaExplorer.require": "ts-node/register", + "mochaExplorer.esmLoader": true, + "mochaExplorer.nodeArgv": ["--loader", "ts-node/esm"], "eslint.format.enable": true, "[javascript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" diff --git a/js-packages/test-utils/index.ts b/js-packages/test-utils/index.ts index ec928610e7..5f7831acfb 100644 --- a/js-packages/test-utils/index.ts +++ b/js-packages/test-utils/index.ts @@ -214,6 +214,23 @@ export class Event { })); }; + static FinCouncil = class extends EventSection('financialCouncil') { + static Proposed = this.Method('Proposed', data => ({ + account: eventHumanData(data, 0), + proposalIndex: eventJsonData(data, 1), + proposalHash: eventHumanData(data, 2), + threshold: eventJsonData(data, 3), + })); + static Closed = this.Method('Closed', data => ({ + proposalHash: eventHumanData(data, 0), + yes: eventJsonData(data, 1), + no: eventJsonData(data, 2), + })); + static Executed = this.Method('Executed', data => ({ + proposalHash: eventHumanData(data, 0), + })); + }; + static TechnicalCommittee = class extends EventSection('technicalCommittee') { static Proposed = this.Method('Proposed', data => ({ account: eventHumanData(data, 0), @@ -475,6 +492,7 @@ export class DevUniqueHelper extends UniqueHelper { scheduler: SchedulerGroup; collatorSelection: CollatorSelectionGroup; council: ICollectiveGroup; + finCouncil: ICollectiveGroup; technicalCommittee: ICollectiveGroup; fellowship: IFellowshipGroup; democracy: DemocracyGroup; @@ -498,6 +516,10 @@ export class DevUniqueHelper extends UniqueHelper { collective: new CollectiveGroup(this, 'council'), membership: new CollectiveMembershipGroup(this, 'councilMembership'), }; + this.finCouncil = { + collective: new CollectiveGroup(this, 'financialCouncil'), + membership: new CollectiveMembershipGroup(this, 'financialCouncilMembership'), + }; this.technicalCommittee = { collective: new CollectiveGroup(this, 'technicalCommittee'), membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'), diff --git a/js-packages/test-utils/util.ts b/js-packages/test-utils/util.ts index 8836c1136f..d2a3e0042d 100644 --- a/js-packages/test-utils/util.ts +++ b/js-packages/test-utils/util.ts @@ -117,6 +117,7 @@ export enum Pallets { Identity = 'identity', Democracy = 'democracy', Council = 'council', + FinancialCouncil = 'financialcouncil', //CouncilMembership = 'councilmembership', TechnicalCommittee = 'technicalcommittee', Fellowship = 'fellowshipcollective', diff --git a/js-packages/tests/sub/governance/council.test.ts b/js-packages/tests/sub/governance/council.test.ts index ce17963793..285fc464d8 100644 --- a/js-packages/tests/sub/governance/council.test.ts +++ b/js-packages/tests/sub/governance/council.test.ts @@ -2,7 +2,7 @@ import type {IKeyringPair} from '@polkadot/types/types'; import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js'; import {Event} from '@unique/test-utils'; -import {initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyEnactmentPeriod, councilMotionDuration, democracyFastTrackVotingPeriod, fellowshipRankLimit, clearCouncil, clearTechComm, initTechComm, clearFellowship, dummyProposal, dummyProposalCall, initFellowship, defaultEnactmentMoment, fellowshipPropositionOrigin} from './util.js'; +import {initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyEnactmentPeriod, councilMotionDuration, democracyFastTrackVotingPeriod, fellowshipRankLimit, clearCouncil, clearTechComm, initTechComm, clearFellowship, dummyProposal, dummyProposalCall, initFellowship, defaultEnactmentMoment, fellowshipPropositionOrigin, initFinCouncil} from './util.js'; import type {ICounselors} from './util.js'; describeGov('Governance: Council tests', () => { @@ -192,6 +192,25 @@ describeGov('Governance: Council tests', () => { expect(techCommMembers).to.not.contains(techComm.andy.address); }); + itSub('Council can remove FinCouncil member', async ({helper}) => { + const finCouncil = await initFinCouncil(donor, sudoer); + const removeMemberPrpoposal = helper.finCouncil.membership.removeMemberCall(finCouncil.andy.address); + await proposalFromMoreThanHalfCouncil(removeMemberPrpoposal); + + const finCouncilMembers = await helper.finCouncil.membership.getMembers(); + expect(finCouncilMembers).to.not.contains(finCouncil.andy.address); + }); + + itSub('Council can add FinCouncil member', async ({helper}) => { + await initFinCouncil(donor, sudoer); + const newFinCouncilMember = helper.arrange.createEmptyAccount(); + const addMemberPrpoposal = helper.finCouncil.membership.addMemberCall(newFinCouncilMember.address); + await proposalFromMoreThanHalfCouncil(addMemberPrpoposal); + + const finCouncilMembers = await helper.finCouncil.membership.getMembers(); + expect(finCouncilMembers).to.contains(newFinCouncilMember.address); + }); + itSub.skip('Council member can add Fellowship member', async ({helper}) => { const newFellowshipMember = helper.arrange.createEmptyAccount(); await expect(helper.council.collective.execute( @@ -328,6 +347,22 @@ describeGov('Governance: Council tests', () => { )).to.be.rejectedWith('BadOrigin'); }); + itSub('[Negative] Council member can\'t add FinCouncil member', async ({helper}) => { + const newFinCouncilMember = helper.arrange.createEmptyAccount(); + await expect(helper.council.collective.execute( + counselors.alex, + helper.finCouncil.membership.addMemberCall(newFinCouncilMember.address), + )).rejectedWith('BadOrigin'); + }); + + itSub('[Negative] Council member can\'t remove FinCouncil member', async ({helper}) => { + const finCouncil = await initFinCouncil(donor, sudoer); + await expect(helper.council.collective.execute( + counselors.alex, + helper.finCouncil.membership.removeMemberCall(finCouncil.ildar.address), + )).rejectedWith('BadOrigin'); + }); + itSub('[Negative] Council member cannot promote/demote a Fellowship member', async ({helper}) => { const fellowship = await initFellowship(donor, sudoer); const memberWithRankOne = fellowship[1][0]; diff --git a/js-packages/tests/sub/governance/financialCouncil.test.ts b/js-packages/tests/sub/governance/financialCouncil.test.ts new file mode 100644 index 0000000000..6d01f5262e --- /dev/null +++ b/js-packages/tests/sub/governance/financialCouncil.test.ts @@ -0,0 +1,406 @@ +import type {IKeyringPair} from '@polkadot/types/types'; +import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js'; +import {Event} from '@unique/test-utils'; +import {democracyFastTrackVotingPeriod, IFinCounselors, clearTechComm, dummyProposalCall, initFinCouncil, clearFinCouncil, democracyLaunchPeriod, initFellowship, dummyProposal, fellowshipPropositionOrigin, defaultEnactmentMoment, initCouncil, clearCouncil, clearFellowship} from './util.js'; + + +describeGov('Governance: Financial Council tests', () => { + let donor: IKeyringPair; + let finCounselors: IFinCounselors; + let sudoer: IKeyringPair; + + const moreThanHalfCouncilThreshold = 2; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.FinancialCouncil]); + sudoer = await privateKey('//Alice'); + donor = await privateKey({url: import.meta.url}); + }); + }); + + beforeEach(async () => { + finCounselors = await initFinCouncil(donor, sudoer); + }); + + afterEach(async () => { + await clearFinCouncil(sudoer); + await clearTechComm(sudoer); + }); + + async function proposalFromMoreThanHalfCouncil(proposal: any) { + return await usingPlaygrounds(async (helper) => { + expect((await helper.finCouncil.membership.getMembers()).length).to.be.equal(3); + const proposeResult = await helper.finCouncil.collective.propose( + finCounselors.ildar, + proposal, + moreThanHalfCouncilThreshold, + ); + + const councilProposedEvent = Event.FinCouncil.Proposed.expect(proposeResult); + const proposalIndex = councilProposedEvent.proposalIndex; + const proposalHash = councilProposedEvent.proposalHash; + + await helper.finCouncil.collective.vote(finCounselors.greg, proposalHash, proposalIndex, true); + await helper.finCouncil.collective.vote(finCounselors.ildar, proposalHash, proposalIndex, true); + + return await helper.finCouncil.collective.close(finCounselors.ildar, proposalHash, proposalIndex); + }); + } + + async function proposalFromAllCouncil(proposal: any) { + return await usingPlaygrounds(async (helper) => { + expect((await helper.finCouncil.membership.getMembers()).length).to.be.equal(3); + const proposeResult = await helper.finCouncil.collective.propose( + finCounselors.ildar, + proposal, + moreThanHalfCouncilThreshold, + ); + + const councilProposedEvent = Event.FinCouncil.Proposed.expect(proposeResult); + const proposalIndex = councilProposedEvent.proposalIndex; + const proposalHash = councilProposedEvent.proposalHash; + + await helper.finCouncil.collective.vote(finCounselors.greg, proposalHash, proposalIndex, true); + await helper.finCouncil.collective.vote(finCounselors.ildar, proposalHash, proposalIndex, true); + await helper.finCouncil.collective.vote(finCounselors.andy, proposalHash, proposalIndex, true); + + return await helper.finCouncil.collective.close(finCounselors.andy, proposalHash, proposalIndex); + }); + } + + itSub('FinCouncil member can register foreign asset', async ({helper}) => { + const location = { + parents: 1, + interior: {X3: [ + { + Parachain: 1000, + }, + { + PalletInstance: 50, + }, + { + GeneralIndex: 1984, + }, + ]}, + }; + const assetId = {Concrete: location}; + + const registerForeignAssetCall = helper.constructApiCall( + 'api.tx.foreignAssets.forceRegisterForeignAsset', + [{V3: assetId}, helper.util.str2vec('New Asset'), 'NEW', {Fungible: 10}], + ); + + await helper.finCouncil.collective.execute(finCounselors.andy, registerForeignAssetCall); + + const asset = await helper.foreignAssets.foreignCollectionId(location); + expect(asset).not.null; + }); + + itSub('[Negative] FinCouncil can\'t fast-track Democracy proposals', async ({helper}) => { + const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true); + await helper.wait.parachainBlockMultiplesOf(35n); + + await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash); + + await expect(proposalFromAllCouncil(helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0))) + .rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot fast-track Democracy proposals', async ({helper}) => { + const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true); + await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0), + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil can\'t cancel Democracy proposals', async ({helper}) => { + const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n); + const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex; + + await expect(proposalFromAllCouncil(helper.democracy.cancelProposalCall(proposalIndex))) + .rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot cancel Democracy proposals', async ({helper}) => { + const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n); + const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex; + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + helper.democracy.cancelProposalCall(proposalIndex), + )) + .to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil can\'t cancel ongoing Democracy referendums', async ({helper}) => { + await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper)); + const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started); + const referendumIndex = startedEvent.referendumIndex; + + await expect(proposalFromAllCouncil(helper.democracy.emergencyCancelCall(referendumIndex))) + .rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot cancel ongoing Democracy referendums', async ({helper}) => { + await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper)); + const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started); + const referendumIndex = startedEvent.referendumIndex; + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + helper.democracy.emergencyCancelCall(referendumIndex), + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member can\'t veto Democracy proposals', async ({helper}) => { + const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true); + await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + helper.democracy.vetoExternalCall(preimageHash), + )).rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil cannot blacklist Democracy proposals', async ({helper}) => { + const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true); + await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash); + + await expect(proposalFromAllCouncil(helper.democracy.blacklistCall(preimageHash))).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot blacklist Democracy proposals', async ({helper}) => { + const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true); + await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + helper.democracy.blacklistCall(preimageHash), + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil can\' cancel Fellowship referendums', async ({helper}) => { + const fellowship = await initFellowship(donor, sudoer); + const fellowshipProposer = fellowship[5][0]; + const proposal = dummyProposal(helper); + + const submitResult = await helper.fellowship.referenda.submit( + fellowshipProposer, + fellowshipPropositionOrigin, + proposal, + defaultEnactmentMoment, + ); + const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex; + await expect(proposalFromAllCouncil(helper.fellowship.referenda.cancelCall(referendumIndex))) + .rejectedWith('BadOrigin'); + }); + + itSub('[Negative] TechComm member cannot cancel Fellowship referendums', async ({helper}) => { + const fellowship = await initFellowship(donor, sudoer); + const fellowshipProposer = fellowship[5][0]; + const proposal = dummyProposal(helper); + + const submitResult = await helper.fellowship.referenda.submit( + fellowshipProposer, + fellowshipPropositionOrigin, + proposal, + defaultEnactmentMoment, + ); + + const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex; + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + helper.fellowship.referenda.cancelCall(referendumIndex), + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member can\' add a Fellowship member', async ({helper}) => { + const newFellowshipMember = helper.arrange.createEmptyAccount(); + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + helper.fellowship.collective.addMemberCall(newFellowshipMember.address), + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil cannot submit regular democracy proposal', async ({helper}) => { + const councilProposal = await helper.democracy.proposeCall(dummyProposalCall(helper), 0n); + + await expect(proposalFromAllCouncil(councilProposal)).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil cannot externally propose SuperMajorityAgainst', async ({helper}) => { + const commiteeProposal = await helper.democracy.externalProposeDefaultCall(dummyProposalCall(helper)); + + await expect(proposalFromAllCouncil(commiteeProposal)).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot submit regular democracy proposal', async ({helper}) => { + const memberProposal = await helper.democracy.proposeCall(dummyProposalCall(helper), 0n); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + memberProposal, + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil cannot externally propose SimpleMajority', async ({helper}) => { + const commiteeProposal = await helper.democracy.externalProposeMajorityCall(dummyProposalCall(helper)); + + await expect(proposalFromAllCouncil(commiteeProposal)).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil cannot externally propose SuperMajorityApprove', async ({helper}) => { + const commiteeProposal = await helper.democracy.externalProposeCall(dummyProposalCall(helper)); + + await expect(proposalFromAllCouncil(commiteeProposal)).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot externally propose SuperMajorityAgainst', async ({helper}) => { + const memberProposal = await helper.democracy.externalProposeDefaultCall(dummyProposalCall(helper)); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + memberProposal, + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot externally propose SimpleMajority', async ({helper}) => { + const memberProposal = await helper.democracy.externalProposeMajorityCall(dummyProposalCall(helper)); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + memberProposal, + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot externally propose SuperMajorityApprove', async ({helper}) => { + const memberProposal = await helper.democracy.externalProposeCall(dummyProposalCall(helper)); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + memberProposal, + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil cannot add/remove a Council member', async ({helper}) => { + const newCouncilMember = helper.arrange.createEmptyAccount(); + const addMemberProposal = helper.council.membership.addMemberCall(newCouncilMember.address); + const removeMemberProposal = helper.council.membership.removeMemberCall(newCouncilMember.address); + + await expect(proposalFromAllCouncil(addMemberProposal)).to.be.rejectedWith('BadOrigin'); + await expect(proposalFromAllCouncil(removeMemberProposal)).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot add/remove a Council member', async ({helper}) => { + const newCouncilMember = helper.arrange.createEmptyAccount(); + const addMemberProposal = helper.council.membership.addMemberCall(newCouncilMember.address); + const removeMemberProposal = helper.council.membership.removeMemberCall(newCouncilMember.address); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + addMemberProposal, + )).to.be.rejectedWith('BadOrigin'); + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + removeMemberProposal, + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil cannot add/remove a FinCouncil member', async ({helper}) => { + const newCouncilMember = helper.arrange.createEmptyAccount(); + const addMemberProposal = helper.finCouncil.membership.addMemberCall(newCouncilMember.address); + const removeMemberProposal = helper.finCouncil.membership.removeMemberCall(finCounselors.ildar.address); + + await expect(proposalFromAllCouncil(addMemberProposal)).to.be.rejectedWith('BadOrigin'); + await expect(proposalFromAllCouncil(removeMemberProposal)).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot add/remove a FinCouncil member', async ({helper}) => { + const newCouncilMember = helper.arrange.createEmptyAccount(); + const addMemberProposal = helper.finCouncil.membership.addMemberCall(newCouncilMember.address); + const removeMemberProposal = helper.finCouncil.membership.removeMemberCall(finCounselors.ildar.address); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + addMemberProposal, + )).to.be.rejectedWith('BadOrigin'); + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + removeMemberProposal, + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil cannot set/clear Council prime member', async ({helper}) => { + const counselors = await initCouncil(donor, sudoer); + const proposalForSet = await helper.council.membership.setPrimeCall(counselors.charu.address); + const proposalForClear = await helper.council.membership.clearPrimeCall(); + + await expect(proposalFromAllCouncil(proposalForSet)).to.be.rejectedWith('BadOrigin'); + await expect(proposalFromAllCouncil(proposalForClear)).to.be.rejectedWith('BadOrigin'); + await clearCouncil(sudoer); + }); + + itSub('[Negative] FinCouncil member cannot set/clear Council prime member', async ({helper}) => { + const counselors = await initCouncil(donor, sudoer); + const proposalForSet = await helper.council.membership.setPrimeCall(counselors.charu.address); + const proposalForClear = await helper.council.membership.clearPrimeCall(); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + proposalForSet, + )).to.be.rejectedWith('BadOrigin'); + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + proposalForClear, + )).to.be.rejectedWith('BadOrigin'); + await clearCouncil(sudoer); + }); + + itSub('[Negative] FinCouncil cannot add/remove a TechComm member', async ({helper}) => { + const newCommMember = helper.arrange.createEmptyAccount(); + const addMemberProposal = helper.council.membership.addMemberCall(newCommMember.address); + const removeMemberProposal = helper.council.membership.removeMemberCall(newCommMember.address); + + await expect(proposalFromAllCouncil(addMemberProposal)).to.be.rejectedWith('BadOrigin'); + await expect(proposalFromAllCouncil(removeMemberProposal)).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil member cannot add/remove a TechComm member', async ({helper}) => { + const newCommMember = helper.arrange.createEmptyAccount(); + const addMemberProposal = helper.council.membership.addMemberCall(newCommMember.address); + const removeMemberProposal = helper.council.membership.removeMemberCall(newCommMember.address); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + addMemberProposal, + )).to.be.rejectedWith('BadOrigin'); + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + removeMemberProposal, + )).to.be.rejectedWith('BadOrigin'); + }); + + itSub('[Negative] FinCouncil cannot remove a Fellowship member', async ({helper}) => { + const fellowship = await initFellowship(donor, sudoer); + + await expect(proposalFromAllCouncil(helper.fellowship.collective.removeMemberCall(fellowship[5][0].address, 5))).to.be.rejectedWith('BadOrigin'); + await clearFellowship(sudoer); + }); + + itSub('[Negative] FinCouncil member cannot remove a Fellowship member', async ({helper}) => { + const fellowship = await initFellowship(donor, sudoer); + + await expect(helper.finCouncil.collective.execute( + finCounselors.andy, + helper.fellowship.collective.removeMemberCall(fellowship[5][0].address, 5), + )).to.be.rejectedWith('BadOrigin'); + await clearFellowship(sudoer); + }); + + +}); diff --git a/js-packages/tests/sub/governance/technicalCommittee.test.ts b/js-packages/tests/sub/governance/technicalCommittee.test.ts index 1fe8cc65d0..703908e2bf 100644 --- a/js-packages/tests/sub/governance/technicalCommittee.test.ts +++ b/js-packages/tests/sub/governance/technicalCommittee.test.ts @@ -130,6 +130,50 @@ describeGov('Governance: Technical Committee tests', () => { await clearFellowship(sudoer); }); + itSub('[Negative] TechComm can\'t add FinCouncil member', async ({helper}) => { + const newFinCouncilMember = helper.arrange.createEmptyAccount(); + const addMemberProposal = helper.finCouncil.membership.addMemberCall(newFinCouncilMember.address); + await proposalFromAllCommittee(addMemberProposal); + + const finCouncilMembers = await helper.finCouncil.membership.getMembers(); + expect(finCouncilMembers).to.contains(newFinCouncilMember.address); + }); + + + itSub('[Negative] TechComm member can\'t add FinCouncil member', async ({helper}) => { + const newFinCouncilMember = helper.arrange.createEmptyAccount(); + await expect(helper.technicalCommittee.collective.execute( + techcomms.greg, + helper.finCouncil.membership.addMemberCall(newFinCouncilMember.address), + )).rejectedWith('BadOrigin'); + }); + + itSub('[Negative] TechComm member cannot register foreign asset', async ({helper}) => { + const location = { + parents: 1, + interior: {X3: [ + { + Parachain: 1000, + }, + { + PalletInstance: 50, + }, + { + GeneralIndex: 1985, + }, + ]}, + }; + const assetId = {Concrete: location}; + + const foreignAssetProposal = helper.constructApiCall( + 'api.tx.foreignAssets.forceRegisterForeignAsset', + [{V3: assetId}, helper.util.str2vec('New Asset2'), 'NEW', {Fungible: 10}], + ); + + await expect(helper.technicalCommittee.collective.execute(techcomms.andy, foreignAssetProposal)) + .to.be.rejectedWith('BadOrigin'); + }); + itSub('[Negative] TechComm cannot submit regular democracy proposal', async ({helper}) => { const councilProposal = await helper.democracy.proposeCall(dummyProposalCall(helper), 0n); diff --git a/js-packages/tests/sub/governance/util.ts b/js-packages/tests/sub/governance/util.ts index 0e57cb3b1a..8e891e45c7 100644 --- a/js-packages/tests/sub/governance/util.ts +++ b/js-packages/tests/sub/governance/util.ts @@ -29,12 +29,62 @@ export interface ICounselors { filip: IKeyringPair; irina: IKeyringPair; } + +export interface IFinCounselors { + greg: IKeyringPair; + ildar: IKeyringPair; + andy: IKeyringPair; +} + export interface ITechComms { greg: IKeyringPair; andy: IKeyringPair; constantine: IKeyringPair; } +export function initFinCouncil(donor: IKeyringPair, superuser: IKeyringPair): Promise { + return usingPlaygrounds(async (helper) => { + const [greg, ildar, andy] = await helper.arrange.createAccounts([10_000n, 10_000n, 10_000n], donor); + const sudo = helper.getSudo(); + { + const members = (await helper.callRpc('api.query.financialCouncilMembership.members')).toJSON() as []; + if(members.length != 0) { + await clearFinCouncil(superuser); + } + } + const expectedMembers = [greg, ildar, andy]; + for(const member of expectedMembers) { + await sudo.executeExtrinsic(superuser, 'api.tx.financialCouncilMembership.addMember', [member.address]); + } + await sudo.executeExtrinsic(superuser, 'api.tx.financialCouncilMembership.setPrime', [greg.address]); + { + const members = (await helper.callRpc('api.query.financialCouncilMembership.members')).toJSON(); + expect(members).to.containSubset(expectedMembers.map((x: IKeyringPair) => x.address)); + expect(members.length).to.be.equal(expectedMembers.length); + } + + return { + greg, + ildar, + andy, + }; + }); +} + +export async function clearFinCouncil(superuser: IKeyringPair) { + await usingPlaygrounds(async (helper) => { + let members = (await helper.callRpc('api.query.financialCouncilMembership.members')).toJSON(); + if(members.length) { + const sudo = helper.getSudo(); + for(const address of members) { + await sudo.executeExtrinsic(superuser, 'api.tx.financialCouncilMembership.removeMember', [address]); + } + members = (await helper.callRpc('api.query.financialCouncilMembership.members')).toJSON(); + } + expect(members).to.be.deep.equal([]); + }); +} + export async function initCouncil(donor: IKeyringPair, superuser: IKeyringPair) { let counselors: IKeyringPair[] = [];