difftreelog
Merge pull request #1059 from UniqueNetwork/feature/financial-council
in: master
15 files changed
js-packages/.vscode/settings.jsondiffbeforeafterboth--- a/js-packages/.vscode/settings.json
+++ b/js-packages/.vscode/settings.json
@@ -4,7 +4,8 @@
"RUN_XCM_TESTS": "1"
},
"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"
js-packages/test-utils/index.tsdiffbeforeafterboth--- a/js-packages/test-utils/index.ts
+++ b/js-packages/test-utils/index.ts
@@ -214,6 +214,23 @@
}));
};
+ static FinCouncil = class extends EventSection('financialCouncil') {
+ static Proposed = this.Method('Proposed', data => ({
+ account: eventHumanData(data, 0),
+ proposalIndex: eventJsonData<number>(data, 1),
+ proposalHash: eventHumanData(data, 2),
+ threshold: eventJsonData<number>(data, 3),
+ }));
+ static Closed = this.Method('Closed', data => ({
+ proposalHash: eventHumanData(data, 0),
+ yes: eventJsonData<number>(data, 1),
+ no: eventJsonData<number>(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 @@
scheduler: SchedulerGroup;
collatorSelection: CollatorSelectionGroup;
council: ICollectiveGroup;
+ finCouncil: ICollectiveGroup;
technicalCommittee: ICollectiveGroup;
fellowship: IFellowshipGroup;
democracy: DemocracyGroup;
@@ -498,6 +516,10 @@
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'),
js-packages/tests/pallet-presence.test.tsdiffbeforeafterboth--- a/js-packages/tests/pallet-presence.test.ts
+++ b/js-packages/tests/pallet-presence.test.ts
@@ -80,6 +80,8 @@
'scheduler',
'technicalcommittee',
'technicalcommitteemembership',
+ 'financialcouncil',
+ 'financialcouncilmembership',
'identity',
];
const testUtils = 'testutils';
js-packages/tests/sub/governance/council.test.tsdiffbeforeafterboth--- 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 @@
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 @@
)).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];
@@ -454,4 +489,21 @@
await expect(helper.council.collective.close(counselors.filip, proposalHash, proposalIndex)).to.be.rejectedWith('TooEarly');
});
+ itSub('[Negative] Council 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(proposalFromAllCouncil(helper.democracy.vetoExternalCall(preimageHash)))
+ .rejectedWith('BadOrigin');
+ });
+
+ itSub('[Negative] Council 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.council.collective.execute(
+ counselors.charu,
+ helper.democracy.vetoExternalCall(preimageHash),
+ )).rejectedWith('BadOrigin');
+ });
});
js-packages/tests/sub/governance/financialCouncil.test.tsdiffbeforeafterboth1import type {IKeyringPair} from '@polkadot/types/types';2import {usingPlaygrounds, itSub, expect, describeGov} from '@unique/test-utils/util.js';3import {Event} from '@unique/test-utils';4import {democracyFastTrackVotingPeriod, IFinCounselors, clearTechComm, dummyProposalCall, initFinCouncil, clearFinCouncil, democracyLaunchPeriod, initFellowship, dummyProposal, fellowshipPropositionOrigin, defaultEnactmentMoment, initCouncil, clearCouncil, clearFellowship} from './util.js';567describeGov('Governance: Financial Council tests', () => {8 let donor: IKeyringPair;9 let finCounselors: IFinCounselors;10 let sudoer: IKeyringPair;1112 const moreThanHalfCouncilThreshold = 2;1314 before(async function() {15 await usingPlaygrounds(async (helper, privateKey) => {16 sudoer = await privateKey('//Alice');17 donor = await privateKey({url: import.meta.url});18 });19 });2021 beforeEach(async () => {22 finCounselors = await initFinCouncil(donor, sudoer);23 });2425 afterEach(async () => {26 await clearFinCouncil(sudoer);27 await clearTechComm(sudoer);28 });2930 async function proposalFromMoreThanHalfFinCouncil(proposal: any) {31 return await usingPlaygrounds(async (helper) => {32 expect((await helper.finCouncil.membership.getMembers()).length).to.be.equal(3);33 const proposeResult = await helper.finCouncil.collective.propose(34 finCounselors.ildar,35 proposal,36 moreThanHalfCouncilThreshold,37 );3839 const councilProposedEvent = Event.FinCouncil.Proposed.expect(proposeResult);40 const proposalIndex = councilProposedEvent.proposalIndex;41 const proposalHash = councilProposedEvent.proposalHash;4243 await helper.finCouncil.collective.vote(finCounselors.greg, proposalHash, proposalIndex, true);44 await helper.finCouncil.collective.vote(finCounselors.ildar, proposalHash, proposalIndex, true);4546 return await helper.finCouncil.collective.close(finCounselors.ildar, proposalHash, proposalIndex);47 });48 }4950 async function proposalFromAllFinCouncil(proposal: any) {51 return await usingPlaygrounds(async (helper) => {52 expect((await helper.finCouncil.membership.getMembers()).length).to.be.equal(3);53 const proposeResult = await helper.finCouncil.collective.propose(54 finCounselors.ildar,55 proposal,56 moreThanHalfCouncilThreshold,57 );5859 const councilProposedEvent = Event.FinCouncil.Proposed.expect(proposeResult);60 const proposalIndex = councilProposedEvent.proposalIndex;61 const proposalHash = councilProposedEvent.proposalHash;6263 await helper.finCouncil.collective.vote(finCounselors.greg, proposalHash, proposalIndex, true);64 await helper.finCouncil.collective.vote(finCounselors.ildar, proposalHash, proposalIndex, true);65 await helper.finCouncil.collective.vote(finCounselors.andy, proposalHash, proposalIndex, true);6667 return await helper.finCouncil.collective.close(finCounselors.andy, proposalHash, proposalIndex);68 });69 }7071 itSub('FinCouncil member can register foreign asset', async ({helper}) => {72 const location = {73 parents: 1,74 interior: {X3: [75 {76 Parachain: 1000,77 },78 {79 PalletInstance: 50,80 },81 {82 GeneralIndex: 1984,83 },84 ]},85 };86 const assetId = {Concrete: location};8788 const registerForeignAssetCall = helper.constructApiCall(89 'api.tx.foreignAssets.forceRegisterForeignAsset',90 [{V3: assetId}, helper.util.str2vec('New Asset'), 'NEW', {Fungible: 10}],91 );9293 await helper.finCouncil.collective.execute(finCounselors.andy, registerForeignAssetCall);9495 const asset = await helper.foreignAssets.foreignCollectionId(location);96 expect(asset).not.null;97 });9899 itSub('[Negative] FinCouncil can\'t fast-track Democracy proposals', async ({helper}) => {100 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);101 await helper.wait.parachainBlockMultiplesOf(35n);102103 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);104105 await expect(proposalFromAllFinCouncil(helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0)))106 .rejectedWith('BadOrigin');107 });108109 itSub('[Negative] FinCouncil member cannot fast-track Democracy proposals', async ({helper}) => {110 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);111 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);112113 await expect(helper.finCouncil.collective.execute(114 finCounselors.andy,115 helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0),116 )).to.be.rejectedWith('BadOrigin');117 });118119 itSub('[Negative] FinCouncil can\'t cancel Democracy proposals', async ({helper}) => {120 const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);121 const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;122123 await expect(proposalFromAllFinCouncil(helper.democracy.cancelProposalCall(proposalIndex)))124 .rejectedWith('BadOrigin');125 });126127 itSub('[Negative] FinCouncil member cannot cancel Democracy proposals', async ({helper}) => {128 const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);129 const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;130131 await expect(helper.finCouncil.collective.execute(132 finCounselors.andy,133 helper.democracy.cancelProposalCall(proposalIndex),134 ))135 .to.be.rejectedWith('BadOrigin');136 });137138 itSub('[Negative] FinCouncil can\'t cancel ongoing Democracy referendums', async ({helper}) => {139 await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper));140 const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);141 const referendumIndex = startedEvent.referendumIndex;142143 await expect(proposalFromAllFinCouncil(helper.democracy.emergencyCancelCall(referendumIndex)))144 .rejectedWith('BadOrigin');145 });146147 itSub('[Negative] FinCouncil member cannot cancel ongoing Democracy referendums', async ({helper}) => {148 await helper.getSudo().democracy.externalProposeDefault(sudoer, dummyProposalCall(helper));149 const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);150 const referendumIndex = startedEvent.referendumIndex;151152 await expect(helper.finCouncil.collective.execute(153 finCounselors.andy,154 helper.democracy.emergencyCancelCall(referendumIndex),155 )).to.be.rejectedWith('BadOrigin');156 });157158 itSub('[Negative] FinCouncil can\'t veto Democracy proposals', async ({helper}) => {159 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);160 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);161162 await expect(proposalFromAllFinCouncil(helper.democracy.vetoExternalCall(preimageHash)))163 .rejectedWith('BadOrigin');164 });165166 itSub('[Negative] FinCouncil member can\'t veto Democracy proposals', async ({helper}) => {167 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);168 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);169170 await expect(helper.finCouncil.collective.execute(171 finCounselors.andy,172 helper.democracy.vetoExternalCall(preimageHash),173 )).rejectedWith('BadOrigin');174 });175176 itSub('[Negative] FinCouncil cannot blacklist Democracy proposals', async ({helper}) => {177 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);178 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);179180 await expect(proposalFromAllFinCouncil(helper.democracy.blacklistCall(preimageHash))).to.be.rejectedWith('BadOrigin');181 });182183 itSub('[Negative] FinCouncil member cannot blacklist Democracy proposals', async ({helper}) => {184 const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);185 await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);186187 await expect(helper.finCouncil.collective.execute(188 finCounselors.andy,189 helper.democracy.blacklistCall(preimageHash),190 )).to.be.rejectedWith('BadOrigin');191 });192193 itSub('[Negative] FinCouncil can\'t cancel Fellowship referendums', async ({helper}) => {194 const fellowship = await initFellowship(donor, sudoer);195 const fellowshipProposer = fellowship[5][0];196 const proposal = dummyProposal(helper);197198 const submitResult = await helper.fellowship.referenda.submit(199 fellowshipProposer,200 fellowshipPropositionOrigin,201 proposal,202 defaultEnactmentMoment,203 );204 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;205 await expect(proposalFromAllFinCouncil(helper.fellowship.referenda.cancelCall(referendumIndex)))206 .rejectedWith('BadOrigin');207 });208209 itSub('[Negative] FinCouncil member cannot cancel Fellowship referendums', async ({helper}) => {210 const fellowship = await initFellowship(donor, sudoer);211 const fellowshipProposer = fellowship[5][0];212 const proposal = dummyProposal(helper);213214 const submitResult = await helper.fellowship.referenda.submit(215 fellowshipProposer,216 fellowshipPropositionOrigin,217 proposal,218 defaultEnactmentMoment,219 );220221 const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;222223 await expect(helper.finCouncil.collective.execute(224 finCounselors.andy,225 helper.fellowship.referenda.cancelCall(referendumIndex),226 )).to.be.rejectedWith('BadOrigin');227 });228229 itSub('[Negative] FinCouncil cannot add a Fellowship member', async ({helper}) => {230 const newFellowshipMember = helper.arrange.createEmptyAccount();231 const addMemberProposal = helper.fellowship.collective.addMemberCall(newFellowshipMember.address);232233 await expect(proposalFromAllFinCouncil(addMemberProposal)).to.be.rejectedWith('BadOrigin');234 });235236 itSub('[Negative] FinCouncil member cannot add a Fellowship member', async ({helper}) => {237 const newFellowshipMember = helper.arrange.createEmptyAccount();238 await expect(helper.finCouncil.collective.execute(239 finCounselors.andy,240 helper.fellowship.collective.addMemberCall(newFellowshipMember.address),241 )).to.be.rejectedWith('BadOrigin');242 });243244 itSub('[Negative] FinCouncil cannot submit regular democracy proposal', async ({helper}) => {245 const councilProposal = await helper.democracy.proposeCall(dummyProposalCall(helper), 0n);246247 await expect(proposalFromAllFinCouncil(councilProposal)).to.be.rejectedWith('BadOrigin');248 });249250 itSub('[Negative] FinCouncil cannot externally propose SuperMajorityAgainst', async ({helper}) => {251 const commiteeProposal = await helper.democracy.externalProposeDefaultCall(dummyProposalCall(helper));252253 await expect(proposalFromAllFinCouncil(commiteeProposal)).to.be.rejectedWith('BadOrigin');254 });255256 itSub('[Negative] FinCouncil member cannot submit regular democracy proposal', async ({helper}) => {257 const memberProposal = await helper.democracy.proposeCall(dummyProposalCall(helper), 0n);258259 await expect(helper.finCouncil.collective.execute(260 finCounselors.andy,261 memberProposal,262 )).to.be.rejectedWith('BadOrigin');263 });264265 itSub('[Negative] FinCouncil cannot externally propose SimpleMajority', async ({helper}) => {266 const commiteeProposal = await helper.democracy.externalProposeMajorityCall(dummyProposalCall(helper));267268 await expect(proposalFromAllFinCouncil(commiteeProposal)).to.be.rejectedWith('BadOrigin');269 });270271 itSub('[Negative] FinCouncil cannot externally propose SuperMajorityApprove', async ({helper}) => {272 const commiteeProposal = await helper.democracy.externalProposeCall(dummyProposalCall(helper));273274 await expect(proposalFromAllFinCouncil(commiteeProposal)).to.be.rejectedWith('BadOrigin');275 });276277 itSub('[Negative] FinCouncil member cannot externally propose SuperMajorityAgainst', async ({helper}) => {278 const memberProposal = await helper.democracy.externalProposeDefaultCall(dummyProposalCall(helper));279280 await expect(helper.finCouncil.collective.execute(281 finCounselors.andy,282 memberProposal,283 )).to.be.rejectedWith('BadOrigin');284 });285286 itSub('[Negative] FinCouncil member cannot externally propose SimpleMajority', async ({helper}) => {287 const memberProposal = await helper.democracy.externalProposeMajorityCall(dummyProposalCall(helper));288289 await expect(helper.finCouncil.collective.execute(290 finCounselors.andy,291 memberProposal,292 )).to.be.rejectedWith('BadOrigin');293 });294295 itSub('[Negative] FinCouncil member cannot externally propose SuperMajorityApprove', async ({helper}) => {296 const memberProposal = await helper.democracy.externalProposeCall(dummyProposalCall(helper));297298 await expect(helper.finCouncil.collective.execute(299 finCounselors.andy,300 memberProposal,301 )).to.be.rejectedWith('BadOrigin');302 });303304 itSub('[Negative] FinCouncil cannot add/remove a Council member', async ({helper}) => {305 const newCouncilMember = helper.arrange.createEmptyAccount();306 const addMemberProposal = helper.council.membership.addMemberCall(newCouncilMember.address);307 const removeMemberProposal = helper.council.membership.removeMemberCall(newCouncilMember.address);308309 await expect(proposalFromAllFinCouncil(addMemberProposal)).to.be.rejectedWith('BadOrigin');310 await expect(proposalFromAllFinCouncil(removeMemberProposal)).to.be.rejectedWith('BadOrigin');311 });312313 itSub('[Negative] FinCouncil member cannot add/remove a Council member', async ({helper}) => {314 const newCouncilMember = helper.arrange.createEmptyAccount();315 const addMemberProposal = helper.council.membership.addMemberCall(newCouncilMember.address);316 const removeMemberProposal = helper.council.membership.removeMemberCall(newCouncilMember.address);317318 await expect(helper.finCouncil.collective.execute(319 finCounselors.andy,320 addMemberProposal,321 )).to.be.rejectedWith('BadOrigin');322 await expect(helper.finCouncil.collective.execute(323 finCounselors.andy,324 removeMemberProposal,325 )).to.be.rejectedWith('BadOrigin');326 });327328 itSub('[Negative] FinCouncil cannot add/remove a FinCouncil member', async ({helper}) => {329 const newCouncilMember = helper.arrange.createEmptyAccount();330 const addMemberProposal = helper.finCouncil.membership.addMemberCall(newCouncilMember.address);331 const removeMemberProposal = helper.finCouncil.membership.removeMemberCall(finCounselors.ildar.address);332333 await expect(proposalFromAllFinCouncil(addMemberProposal)).to.be.rejectedWith('BadOrigin');334 await expect(proposalFromAllFinCouncil(removeMemberProposal)).to.be.rejectedWith('BadOrigin');335 });336337 itSub('[Negative] FinCouncil cannot add/remove a FinCouncil prime member', async ({helper}) => {338 const setPrimeCall = helper.finCouncil.membership.setPrimeCall(finCounselors.andy.address);339 const clearPrimeCall = helper.finCouncil.membership.clearPrimeCall();340341 await expect(proposalFromAllFinCouncil(setPrimeCall)).to.be.rejectedWith('BadOrigin');342 await expect(proposalFromAllFinCouncil(clearPrimeCall)).to.be.rejectedWith('BadOrigin');343 });344345 itSub('[Negative] FinCouncil member cannot add/remove a FinCouncil prime member', async ({helper}) => {346 await expect(helper.finCouncil.collective.execute(347 finCounselors.andy,348 helper.finCouncil.membership.setPrimeCall(finCounselors.andy.address),349 )).to.be.rejectedWith('BadOrigin');350351 await expect(helper.finCouncil.collective.execute(352 finCounselors.andy,353 helper.finCouncil.membership.clearPrimeCall(),354 )).to.be.rejectedWith('BadOrigin');355 });356357 itSub('[Negative] FinCouncil member cannot add/remove a FinCouncil member', async ({helper}) => {358 const newCouncilMember = helper.arrange.createEmptyAccount();359 const addMemberProposal = helper.finCouncil.membership.addMemberCall(newCouncilMember.address);360 const removeMemberProposal = helper.finCouncil.membership.removeMemberCall(finCounselors.ildar.address);361362 await expect(helper.finCouncil.collective.execute(363 finCounselors.andy,364 addMemberProposal,365 )).to.be.rejectedWith('BadOrigin');366 await expect(helper.finCouncil.collective.execute(367 finCounselors.andy,368 removeMemberProposal,369 )).to.be.rejectedWith('BadOrigin');370 });371372 itSub('[Negative] FinCouncil cannot set/clear Council prime member', async ({helper}) => {373 const counselors = await initCouncil(donor, sudoer);374 const proposalForSet = await helper.council.membership.setPrimeCall(counselors.charu.address);375 const proposalForClear = await helper.council.membership.clearPrimeCall();376377 await expect(proposalFromAllFinCouncil(proposalForSet)).to.be.rejectedWith('BadOrigin');378 await expect(proposalFromAllFinCouncil(proposalForClear)).to.be.rejectedWith('BadOrigin');379 await clearCouncil(sudoer);380 });381382 itSub('[Negative] FinCouncil member cannot set/clear Council prime member', async ({helper}) => {383 const counselors = await initCouncil(donor, sudoer);384 const proposalForSet = await helper.council.membership.setPrimeCall(counselors.charu.address);385 const proposalForClear = await helper.council.membership.clearPrimeCall();386387 await expect(helper.finCouncil.collective.execute(388 finCounselors.andy,389 proposalForSet,390 )).to.be.rejectedWith('BadOrigin');391 await expect(helper.finCouncil.collective.execute(392 finCounselors.andy,393 proposalForClear,394 )).to.be.rejectedWith('BadOrigin');395 await clearCouncil(sudoer);396 });397398 itSub('[Negative] FinCouncil cannot add/remove a TechComm member', async ({helper}) => {399 const newCommMember = helper.arrange.createEmptyAccount();400 const addMemberProposal = helper.technicalCommittee.membership.addMemberCall(newCommMember.address);401 const removeMemberProposal = helper.technicalCommittee.membership.removeMemberCall(newCommMember.address);402403 await expect(proposalFromAllFinCouncil(addMemberProposal)).to.be.rejectedWith('BadOrigin');404 await expect(proposalFromAllFinCouncil(removeMemberProposal)).to.be.rejectedWith('BadOrigin');405 });406407 itSub('[Negative] FinCouncil member cannot add/remove a TechComm member', async ({helper}) => {408 const newCommMember = helper.arrange.createEmptyAccount();409 const addMemberProposal = helper.technicalCommittee.membership.addMemberCall(newCommMember.address);410 const removeMemberProposal = helper.technicalCommittee.membership.removeMemberCall(newCommMember.address);411412 await expect(helper.finCouncil.collective.execute(413 finCounselors.andy,414 addMemberProposal,415 )).to.be.rejectedWith('BadOrigin');416 await expect(helper.finCouncil.collective.execute(417 finCounselors.andy,418 removeMemberProposal,419 )).to.be.rejectedWith('BadOrigin');420 });421422 itSub('[Negative] FinCouncil cannot remove a Fellowship member', async ({helper}) => {423 const fellowship = await initFellowship(donor, sudoer);424425 await expect(proposalFromAllFinCouncil(helper.fellowship.collective.removeMemberCall(fellowship[5][0].address, 5))).to.be.rejectedWith('BadOrigin');426 await clearFellowship(sudoer);427 });428429 itSub('[Negative] FinCouncil member cannot remove a Fellowship member', async ({helper}) => {430 const fellowship = await initFellowship(donor, sudoer);431432 await expect(helper.finCouncil.collective.execute(433 finCounselors.andy,434 helper.fellowship.collective.removeMemberCall(fellowship[5][0].address, 5),435 )).to.be.rejectedWith('BadOrigin');436 await clearFellowship(sudoer);437 });438439440});js-packages/tests/sub/governance/technicalCommittee.test.tsdiffbeforeafterboth--- a/js-packages/tests/sub/governance/technicalCommittee.test.ts
+++ b/js-packages/tests/sub/governance/technicalCommittee.test.ts
@@ -130,6 +130,50 @@
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 expect(proposalFromAllCommittee(addMemberProposal)).rejectedWith('BadOrigin');
+
+ const finCouncilMembers = await helper.finCouncil.membership.getMembers();
+ expect(finCouncilMembers).to.not.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);
js-packages/tests/sub/governance/util.tsdiffbeforeafterboth--- a/js-packages/tests/sub/governance/util.ts
+++ b/js-packages/tests/sub/governance/util.ts
@@ -29,12 +29,62 @@
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<IFinCounselors> {
+ 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[] = [];
pallets/foreign-assets/src/lib.rsdiffbeforeafterboth--- a/pallets/foreign-assets/src/lib.rs
+++ b/pallets/foreign-assets/src/lib.rs
@@ -107,7 +107,7 @@
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Origin for force registering of a foreign asset.
- type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
+ type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The ID of the foreign assets pallet.
type PalletId: Get<PalletId>;
@@ -197,7 +197,7 @@
token_prefix: CollectionTokenPrefix,
mode: ForeignCollectionMode,
) -> DispatchResult {
- T::ForceRegisterOrigin::ensure_origin(origin.clone())?;
+ T::ManagerOrigin::ensure_origin(origin.clone())?;
let asset_id: AssetId = versioned_asset_id
.as_ref()
runtime/common/config/governance/financial_council.rsdiffbeforeafterboth--- /dev/null
+++ b/runtime/common/config/governance/financial_council.rs
@@ -0,0 +1,56 @@
+use super::*;
+
+parameter_types! {
+ pub FinancialCouncilMaxProposals: u32 = 100;
+ pub FinancialCouncilMaxMembers: u32 = 100;
+}
+
+#[cfg(not(feature = "gov-test-timings"))]
+use crate::governance_timings::financial_council as financial_council_timings;
+
+#[cfg(feature = "gov-test-timings")]
+pub mod financial_council_timings {
+ use super::*;
+
+ parameter_types! {
+ pub FinancialCouncilMotionDuration: BlockNumber = 35;
+ }
+}
+
+pub type FinancialCollective = pallet_collective::Instance3;
+impl pallet_collective::Config<FinancialCollective> for Runtime {
+ type RuntimeOrigin = RuntimeOrigin;
+ type Proposal = RuntimeCall;
+ type RuntimeEvent = RuntimeEvent;
+ type MotionDuration = financial_council_timings::FinancialCouncilMotionDuration;
+ type MaxProposals = FinancialCouncilMaxProposals;
+ type MaxMembers = FinancialCouncilMaxMembers;
+ type DefaultVote = pallet_collective::PrimeDefaultVote;
+ type WeightInfo = pallet_collective::weights::SubstrateWeight<Runtime>;
+ type SetMembersOrigin = EnsureRoot<AccountId>;
+ type MaxProposalWeight = MaxCollectivesProposalWeight;
+}
+
+pub type FinancialCollectiveMembership = pallet_membership::Instance3;
+impl pallet_membership::Config<FinancialCollectiveMembership> for Runtime {
+ type RuntimeEvent = RuntimeEvent;
+ type AddOrigin = RootOrMoreThanHalfCouncil;
+ type RemoveOrigin = RootOrMoreThanHalfCouncil;
+ type SwapOrigin = RootOrMoreThanHalfCouncil;
+ type ResetOrigin = EnsureRoot<AccountId>;
+ type PrimeOrigin = RootOrMoreThanHalfCouncil;
+ type MembershipInitialized = FinancialCouncil;
+ type MembershipChanged = FinancialCouncil;
+ type MaxMembers = FinancialCouncilMaxMembers;
+ type WeightInfo = pallet_membership::weights::SubstrateWeight<Runtime>;
+}
+
+pub type FinancialCouncilMember = pallet_collective::EnsureMember<AccountId, FinancialCollective>;
+
+pub type RootOrFinancialCouncilMember =
+ EitherOfDiverse<EnsureRoot<AccountId>, FinancialCouncilMember>;
+
+pub type AllFinancialCouncil =
+ pallet_collective::EnsureProportionAtLeast<AccountId, FinancialCollective, 1, 1>;
+
+pub type RootOrAllFinancialCouncil = EitherOfDiverse<EnsureRoot<AccountId>, AllFinancialCouncil>;
runtime/common/config/governance/mod.rsdiffbeforeafterboth--- a/runtime/common/config/governance/mod.rs
+++ b/runtime/common/config/governance/mod.rs
@@ -36,8 +36,8 @@
};
use crate::{
- Balances, Council, OriginCaller, Preimage, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
- Scheduler, TechnicalCommittee, Treasury,
+ Balances, Council, FinancialCouncil, OriginCaller, Preimage, Runtime, RuntimeCall,
+ RuntimeEvent, RuntimeOrigin, Scheduler, TechnicalCommittee, Treasury,
};
pub mod council;
@@ -49,6 +49,9 @@
pub mod technical_committee;
pub use technical_committee::*;
+pub mod financial_council;
+pub use financial_council::*;
+
pub mod fellowship;
pub use fellowship::*;
runtime/common/config/pallets/foreign_asset.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/foreign_asset.rs
+++ b/runtime/common/config/pallets/foreign_asset.rs
@@ -40,10 +40,10 @@
type RuntimeEvent = RuntimeEvent;
#[cfg(feature = "governance")]
- type ForceRegisterOrigin = governance::RootOrTechnicalCommitteeMember;
+ type ManagerOrigin = governance::RootOrFinancialCouncilMember;
#[cfg(not(feature = "governance"))]
- type ForceRegisterOrigin = EnsureRoot<Self::AccountId>;
+ type ManagerOrigin = EnsureRoot<Self::AccountId>;
type PalletId = ForeignAssetPalletId;
type SelfLocation = SelfLocation;
runtime/common/construct_runtime.rsdiffbeforeafterboth--- a/runtime/common/construct_runtime.rs
+++ b/runtime/common/construct_runtime.rs
@@ -81,6 +81,12 @@
Scheduler: pallet_scheduler = 49,
#[cfg(feature = "governance")]
+ FinancialCouncil: pallet_collective::<Instance3> = 97,
+
+ #[cfg(feature = "governance")]
+ FinancialCouncilMembership: pallet_membership::<Instance3> = 98,
+
+ #[cfg(feature = "governance")]
Origins: pallet_gov_origins = 99,
// XCM helpers.
runtime/opal/src/governance_timings.rsdiffbeforeafterboth--- a/runtime/opal/src/governance_timings.rs
+++ b/runtime/opal/src/governance_timings.rs
@@ -52,3 +52,11 @@
pub TechnicalMotionDuration: BlockNumber = 15 * MINUTES;
}
}
+
+pub mod financial_council {
+ use super::*;
+
+ parameter_types! {
+ pub FinancialCouncilMotionDuration: BlockNumber = 15 * MINUTES;
+ }
+}
runtime/quartz/src/governance_timings.rsdiffbeforeafterboth--- a/runtime/quartz/src/governance_timings.rs
+++ b/runtime/quartz/src/governance_timings.rs
@@ -52,3 +52,11 @@
pub TechnicalMotionDuration: BlockNumber = 3 * DAYS;
}
}
+
+pub mod financial_council {
+ use super::*;
+
+ parameter_types! {
+ pub FinancialCouncilMotionDuration: BlockNumber = 3 * DAYS;
+ }
+}
runtime/unique/src/governance_timings.rsdiffbeforeafterboth--- a/runtime/unique/src/governance_timings.rs
+++ b/runtime/unique/src/governance_timings.rs
@@ -52,3 +52,11 @@
pub TechnicalMotionDuration: BlockNumber = 3 * DAYS;
}
}
+
+pub mod financial_council {
+ use super::*;
+
+ parameter_types! {
+ pub FinancialCouncilMotionDuration: BlockNumber = 3 * DAYS;
+ }
+}