git.delta.rocks / unique-network / refs/commits / ed0719fab73d

difftreelog

Tests/fin council (#1070)

Max Andreev2024-06-05parent: #c9b1f9a.patch.diff
in: master

7 files changed

modifiedjs-packages/.vscode/settings.jsondiffbeforeafterboth
--- 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"
modifiedjs-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'),
modifiedjs-packages/test-utils/util.tsdiffbeforeafterboth
--- a/js-packages/test-utils/util.ts
+++ b/js-packages/test-utils/util.ts
@@ -117,6 +117,7 @@
   Identity = 'identity',
   Democracy = 'democracy',
   Council = 'council',
+  FinancialCouncil = 'financialcouncil',
   //CouncilMembership = 'councilmembership',
   TechnicalCommittee = 'technicalcommittee',
   Fellowship = 'fellowshipcollective',
modifiedjs-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];
addedjs-packages/tests/sub/governance/financialCouncil.test.tsdiffbeforeafterboth
--- /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);
+  });
+
+
+});
modifiedjs-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 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);
 
modifiedjs-packages/tests/sub/governance/util.tsdiffbeforeafterboth
before · js-packages/tests/sub/governance/util.ts
1import type {IKeyringPair} from '@polkadot/types/types';2import {xxhashAsHex} from '@polkadot/util-crypto';3import type {u32} from '@polkadot/types-codec';4import {usingPlaygrounds, expect} from '@unique/test-utils/util.js';5import {UniqueHelper} from '@unique-nft/playgrounds/unique.js';6import {DevUniqueHelper} from '@unique/test-utils';78export const democracyLaunchPeriod = 35;9export const democracyVotingPeriod = 35;10export const councilMotionDuration = 35;11export const democracyEnactmentPeriod = 40;12export const democracyFastTrackVotingPeriod = 5;1314export const fellowshipRankLimit = 7;15export const fellowshipPropositionOrigin = 'FellowshipProposition';16export const fellowshipPreparePeriod = 3;17export const fellowshipConfirmPeriod = 3;18export const fellowshipMinEnactPeriod = 1;1920export const defaultEnactmentMoment = {After: 0};2122export const democracyTrackId = 10;23export const democracyTrackMinRank = 3;24const twox128 = (data: any) => xxhashAsHex(data, 128);25export interface ICounselors {26  alex: IKeyringPair;27  ildar: IKeyringPair;28  charu: IKeyringPair;29  filip: IKeyringPair;30  irina: IKeyringPair;31}32export interface ITechComms {33    greg: IKeyringPair;34    andy: IKeyringPair;35    constantine: IKeyringPair;36}3738export async function initCouncil(donor: IKeyringPair, superuser: IKeyringPair) {39  let counselors: IKeyringPair[] = [];4041  await usingPlaygrounds(async (helper) => {42    const [alex, ildar, charu, filip, irina] = await helper.arrange.createAccounts([10_000n, 10_000n, 10_000n, 10_000n, 10_000n], donor);43    const sudo = helper.getSudo();44    {45      const members = (await helper.callRpc('api.query.councilMembership.members')).toJSON() as [];46      if(members.length != 0) {47        await clearCouncil(superuser);48      }49    }50    const expectedMembers = [alex, ildar, charu, filip, irina];51    for(const member of expectedMembers) {52      await sudo.executeExtrinsic(superuser, 'api.tx.councilMembership.addMember', [member.address]);53    }54    await sudo.executeExtrinsic(superuser, 'api.tx.councilMembership.setPrime', [alex.address]);55    {56      const members = (await helper.callRpc('api.query.councilMembership.members')).toJSON();57      expect(members).to.containSubset(expectedMembers.map((x: IKeyringPair) => x.address));58      expect(members.length).to.be.equal(expectedMembers.length);59    }6061    counselors = [alex, ildar, charu, filip, irina];62  });63  return {64    alex: counselors[0],65    ildar: counselors[1],66    charu: counselors[2],67    filip: counselors[3],68    irina: counselors[4],69  };70}7172export async function clearCouncil(superuser: IKeyringPair) {73  await usingPlaygrounds(async (helper) => {74    let members = (await helper.callRpc('api.query.councilMembership.members')).toJSON();75    if(members.length) {76      const sudo = helper.getSudo();77      for(const address of members) {78        await sudo.executeExtrinsic(superuser, 'api.tx.councilMembership.removeMember', [address]);79      }80      members = (await helper.callRpc('api.query.councilMembership.members')).toJSON();81    }82    expect(members).to.be.deep.equal([]);83  });84}858687export async function initTechComm(donor: IKeyringPair, superuser: IKeyringPair) {88  let techcomms: IKeyringPair[] = [];8990  await usingPlaygrounds(async (helper) => {91    const [greg, andy, constantine] = await helper.arrange.createAccounts([10_000n, 10_000n, 10_000n], donor);92    const sudo = helper.getSudo();93    {94      const members = (await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON() as [];95      if(members.length != 0) {96        await clearTechComm(superuser);97      }98    }99    await sudo.executeExtrinsic(superuser, 'api.tx.technicalCommitteeMembership.addMember', [greg.address]);100    await sudo.executeExtrinsic(superuser, 'api.tx.technicalCommitteeMembership.addMember', [andy.address]);101    await sudo.executeExtrinsic(superuser, 'api.tx.technicalCommitteeMembership.addMember', [constantine.address]);102    await sudo.executeExtrinsic(superuser, 'api.tx.technicalCommitteeMembership.setPrime', [greg.address]);103    {104      const members = (await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON();105      expect(members).to.containSubset([greg.address, andy.address, constantine.address]);106      expect(members.length).to.be.equal(3);107    }108109    techcomms = [greg, andy, constantine];110  });111112  return {113    greg: techcomms[0],114    andy: techcomms[1],115    constantine: techcomms[2],116  };117}118119export async function clearTechComm(superuser: IKeyringPair) {120  await usingPlaygrounds(async (helper) => {121    let members = (await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON();122    if(members.length) {123      const sudo = helper.getSudo();124      for(const address of members) {125        await sudo.executeExtrinsic(superuser, 'api.tx.technicalCommitteeMembership.removeMember', [address]);126      }127      members = (await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON();128    }129    expect(members).to.be.deep.equal([]);130  });131}132133export async function initFellowship(donor: IKeyringPair, sudoer: IKeyringPair) {134  const numMembersInRank = 3;135  const memberBalance = 5000n;136  const members: IKeyringPair[][] = [];137138  await usingPlaygrounds(async (helper) => {139    const currentFellows = await helper.getApi().query.fellowshipCollective.members.keys();140141    if(currentFellows.length != 0) {142      await clearFellowship(sudoer);143    }144    for(let i = 0; i < fellowshipRankLimit; i++) {145      const rankMembers = await helper.arrange.createAccounts(146        Array(numMembersInRank).fill(memberBalance),147        donor,148      );149150      for(const member of rankMembers) {151        await helper.getSudo().fellowship.collective.addMember(sudoer, member.address);152153        for(let rank = 0; rank < i; rank++) {154          await helper.getSudo().fellowship.collective.promote(sudoer, member.address);155        }156      }157158      members.push(rankMembers);159    }160  });161162  return members;163}164165export async function clearFellowship(sudoer: IKeyringPair) {166  await usingPlaygrounds(async (helper) => {167    const fellowship = (await helper.getApi().query.fellowshipCollective.members.keys())168      .map((key) => key.args[0].toString());169    for(const  member of fellowship) {170      await helper.getSudo().fellowship.collective.removeMember(sudoer, member, fellowshipRankLimit);171    }172  });173}174175export async function clearFellowshipReferenda(sudoer: IKeyringPair) {176  await usingPlaygrounds(async (helper) => {177    const proposalsCount = (await helper.getApi().query.fellowshipReferenda.referendumCount()) as u32;178    for(let i = 0; i < proposalsCount.toNumber(); i++) {179      await helper.getSudo().fellowship.referenda.cancel(sudoer, i);180    }181  });182}183184export async function hardResetFellowshipReferenda(sudoer: IKeyringPair) {185  await usingPlaygrounds(async (helper) => {186    const api = helper.getApi();187    const prefix = twox128('FellowshipReferenda');188    await helper.signTransaction(sudoer, api.tx.sudo.sudo(api.tx.system.killPrefix(prefix, 100)));189  });190}191192export async function hardResetDemocracy(sudoer: IKeyringPair) {193  await usingPlaygrounds(async (helper) => {194    const api = helper.getApi();195    const prefix = twox128('Democracy');196    await helper.signTransaction(sudoer, api.tx.sudo.sudo(api.tx.system.killPrefix(prefix, 100)));197  });198}199200export async function hardResetGovScheduler(sudoer: IKeyringPair) {201  await usingPlaygrounds(async (helper) => {202    const api = helper.getApi();203    const prefix = twox128('GovScheduler');204    await helper.signTransaction(sudoer, api.tx.sudo.sudo(api.tx.system.killPrefix(prefix, 500)));205  });206}207208export async function voteUnanimouslyInFellowship(helper: DevUniqueHelper, fellows: IKeyringPair[][], minRank: number, referendumIndex: number) {209  for(let rank = minRank; rank < fellowshipRankLimit; rank++) {210    for(const member of fellows[rank]) {211      await helper.fellowship.collective.vote(member, referendumIndex, true);212    }213  }214}215216export function dummyProposalCall(helper: UniqueHelper) {217  return helper.constructApiCall('api.tx.system.remark', ['dummy proposal' + (new Date()).getTime()]);218}219220export function dummyProposal(helper: UniqueHelper) {221  return {222    Inline: dummyProposalCall(helper).method.toHex(),223  };224}