Selaa lähdekoodia

Merge branch 'nicaea' into types-readme

Mokhtar Naamani 4 vuotta sitten
vanhempi
commit
1164857204

+ 2 - 1
scripts/create-test-chainspec.sh

@@ -9,4 +9,5 @@ perl -i -pe's/"setContentWorkingGroupMintCapacityProposalGracePeriod":.*/"setCon
 perl -i -pe's/"setLeadProposalGracePeriod":.*/"setLeadProposalGracePeriod": 0,/' .tmp/chainspec.json
 perl -i -pe's/"spendingProposalGracePeriod":.*/"spendingProposalGracePeriod": 0,/' .tmp/chainspec.json
 perl -i -pe's/"evictStorageProviderProposalGracePeriod":.*/"evictStorageProviderProposalGracePeriod": 0,/' .tmp/chainspec.json
-perl -i -pe's/"setStorageRoleParametersProposalGracePeriod":.*/"setStorageRoleParametersProposalGracePeriod": 0/' .tmp/chainspec.json
+perl -i -pe's/"beginReviewWorkingGroupLeaderApplicationsProposalGracePeriod":.*/"beginReviewWorkingGroupLeaderApplicationsProposalGracePeriod": 0,/' .tmp/chainspec.json
+perl -i -pe's/"setStorageRoleParametersProposalGracePeriod":.*/"setStorageRoleParametersProposalGracePeriod": 0,/' .tmp/chainspec.json

+ 11 - 3
tests/network-tests/.env

@@ -31,14 +31,22 @@ WORKING_GROUP_APPLICATION_STAKE = 10
 # Working group role stake
 WORKING_GROUP_ROLE_STAKE = 10
 # Reward interval for working group tests
-LONG_REWARD_INTERWAL = 99999
+LONG_REWARD_INTERVAL = 99999
 # First reward interval for working group reward test
-SHORT_FIRST_REWARD_INTERWAL = 3
+SHORT_FIRST_REWARD_INTERVAL = 3
 # Reward interval for working group reward test
-SHORT_REWARD_INTERWAL = 3
+SHORT_REWARD_INTERVAL = 3
 # Payout amount for working group tests
 PAYOUT_AMOUNT = 3
+# Payout amount for leader-related proposals tests
+ALTERED_PAYOUT_AMOUNT = 7
 # Mint capacity for storage working group
 STORAGE_WORKING_GROUP_MINTING_CAPACITY = 100000
 # Default unstaking period for storage working group
 STORAGE_WORKING_GROUP_UNSTAKING_PERIOD = 1
+# Slash value for manage working group lead testing scenario
+SLASH_AMOUNT = 2
+# Stake decrement amount for manage working group lead testing scenario
+STAKE_DECREMENT = 3
+# Mint capacity increment value for working gorup mint capacity test
+MINT_CAPACITY_INCREMENT = 11

+ 1 - 1
tests/network-tests/package.json

@@ -7,7 +7,7 @@
     "test": "tap --files ts-node/register src/nicaea/tests/proposals/*Test.ts --files ts-node/register src/nicaea/tests/workingGroup/*Test.ts -T",
     "test-migration-constantinople": "tap --files src/rome/tests/romeRuntimeUpgradeTest.ts --files src/constantinople/tests/electingCouncilTest.ts -T",
     "test-migration-nicaea": "tap --files src/constantinople/tests/proposals/updateRuntimeTest.ts --files src/nicaea/tests/electingCouncilTest.ts -T",
-    "debug": "tap --files src/nicaea/tests/workingGroup/atLeastValueBugTest.ts -T",
+    "debug": "tap --files src/nicaea/tests/proposals/manageLeaderRole.ts -T",
     "lint": "eslint . --quiet --ext .ts",
     "checks": "yarn lint && tsc --noEmit --pretty && prettier . --check",
     "format": "prettier ./ --write "

+ 79 - 0
tests/network-tests/src/nicaea/dto/fillOpeningParameters.ts

@@ -0,0 +1,79 @@
+import BN from 'bn.js';
+
+export class FillOpeningParameters {
+  private amountPerPayout!: BN;
+  private nextPaymentAtBlock!: BN;
+  private payoutInterval!: BN;
+  private openingId!: BN;
+  private successfulApplicationId!: BN;
+  private workingGroup!: string;
+
+  public getAmountPerPayout(): BN {
+    return this.amountPerPayout;
+  }
+
+  public getNextPaymentAtBlock(): BN {
+    return this.nextPaymentAtBlock;
+  }
+
+  public getPayoutInterval(): BN {
+    return this.payoutInterval;
+  }
+
+  public getOpeningId(): BN {
+    return this.openingId;
+  }
+
+  public getSuccessfulApplicationId(): BN {
+    return this.successfulApplicationId;
+  }
+
+  public getWorkingGroup(): string {
+    return this.workingGroup;
+  }
+
+  public setAmountPerPayout(value: BN) {
+    this.amountPerPayout = value;
+  }
+
+  public setNextPaymentAtBlock(value: BN) {
+    this.nextPaymentAtBlock = value;
+  }
+
+  public setPayoutInterval(value: BN) {
+    this.payoutInterval = value;
+  }
+
+  public setOpeningId(value: BN) {
+    this.openingId = value;
+  }
+
+  public setSuccessfulApplicationId(value: BN) {
+    this.successfulApplicationId = value;
+  }
+
+  public setWorkingGroup(value: string) {
+    this.workingGroup = value;
+  }
+
+  constructor() {
+    return;
+  }
+
+  public getRewardPolicy() {
+    return {
+      amount_per_payout: this.amountPerPayout,
+      next_payment_at_block: this.nextPaymentAtBlock,
+      payout_interval: this.payoutInterval,
+    };
+  }
+
+  public getFillOpeningParameters() {
+    return {
+      opening_id: this.openingId,
+      successful_application_id: this.successfulApplicationId,
+      reward_policy: this.getRewardPolicy(),
+      working_group: this.workingGroup,
+    };
+  }
+}

+ 28 - 27
tests/network-tests/src/nicaea/dto/workingGroupOpening.ts

@@ -1,5 +1,4 @@
 import BN from 'bn.js';
-import { KeyringPair } from '@polkadot/keyring/types';
 
 export class WorkingGroupOpening {
   private activateAtBlock: BN | undefined;
@@ -16,10 +15,10 @@ export class WorkingGroupOpening {
   private successfulApplicantApplicationStakeUnstakingPeriod!: BN;
   private failedApplicantApplicationStakeUnstakingPeriod!: BN;
   private failedApplicantRoleStakeUnstakingPeriod!: BN;
-  private terminateCuratorApplicationStakeUnstakingPeriod!: BN;
-  private terminateCuratorRoleStakeUnstakingPeriod!: BN;
-  private exitCuratorRoleApplicationStakeUnstakingPeriod!: BN;
-  private exitCuratorRoleStakeUnstakingPeriod!: BN;
+  private terminateApplicationStakeUnstakingPeriod!: BN;
+  private terminateRoleStakeUnstakingPeriod!: BN;
+  private exitRoleApplicationStakeUnstakingPeriod!: BN;
+  private exitRoleStakeUnstakingPeriod!: BN;
   private text!: string;
   private openingType!: string;
 
@@ -79,20 +78,20 @@ export class WorkingGroupOpening {
     return this.failedApplicantRoleStakeUnstakingPeriod;
   }
 
-  public getTerminateCuratorApplicationStakeUnstakingPeriod(): BN {
-    return this.terminateCuratorApplicationStakeUnstakingPeriod;
+  public getTerminateApplicationStakeUnstakingPeriod(): BN {
+    return this.terminateApplicationStakeUnstakingPeriod;
   }
 
-  public getTerminateCuratorRoleStakeUnstakingPeriod(): BN {
-    return this.terminateCuratorRoleStakeUnstakingPeriod;
+  public getTerminateRoleStakeUnstakingPeriod(): BN {
+    return this.terminateRoleStakeUnstakingPeriod;
   }
 
-  public getExitCuratorRoleApplicationStakeUnstakingPeriod(): BN {
-    return this.exitCuratorRoleApplicationStakeUnstakingPeriod;
+  public getExitRoleApplicationStakeUnstakingPeriod(): BN {
+    return this.exitRoleApplicationStakeUnstakingPeriod;
   }
 
-  public getExitCuratorRoleStakeUnstakingPeriod(): BN {
-    return this.exitCuratorRoleStakeUnstakingPeriod;
+  public getExitRoleStakeUnstakingPeriod(): BN {
+    return this.exitRoleStakeUnstakingPeriod;
   }
 
   public getText(): string {
@@ -159,20 +158,20 @@ export class WorkingGroupOpening {
     this.failedApplicantRoleStakeUnstakingPeriod = value;
   }
 
-  public setTerminateCuratorApplicationStakeUnstakingPeriod(value: BN) {
-    this.terminateCuratorApplicationStakeUnstakingPeriod = value;
+  public setTerminateApplicationStakeUnstakingPeriod(value: BN) {
+    this.terminateApplicationStakeUnstakingPeriod = value;
   }
 
-  public setTerminateCuratorRoleStakeUnstakingPeriod(value: BN) {
-    this.terminateCuratorRoleStakeUnstakingPeriod = value;
+  public setTerminateRoleStakeUnstakingPeriod(value: BN) {
+    this.terminateRoleStakeUnstakingPeriod = value;
   }
 
-  public setExitCuratorRoleApplicationStakeUnstakingPeriod(value: BN) {
-    this.exitCuratorRoleApplicationStakeUnstakingPeriod = value;
+  public setExitRoleApplicationStakeUnstakingPeriod(value: BN) {
+    this.exitRoleApplicationStakeUnstakingPeriod = value;
   }
 
-  public setExitCuratorRoleStakeUnstakingPeriod(value: BN) {
-    this.exitCuratorRoleStakeUnstakingPeriod = value;
+  public setExitRoleStakeUnstakingPeriod(value: BN) {
+    this.exitRoleStakeUnstakingPeriod = value;
   }
 
   public setText(value: string) {
@@ -183,10 +182,12 @@ export class WorkingGroupOpening {
     this.openingType = value;
   }
 
-  constructor() {}
+  constructor() {
+    return;
+  }
 
   public getActivateAt() {
-    return this.activateAtBlock == undefined ? 'CurrentBlock' : { ExactBlock: this.activateAtBlock };
+    return this.activateAtBlock === undefined ? 'CurrentBlock' : { ExactBlock: this.activateAtBlock };
   }
 
   public getCommitment() {
@@ -216,10 +217,10 @@ export class WorkingGroupOpening {
       fill_opening_failed_applicant_application_stake_unstaking_period: this
         .failedApplicantApplicationStakeUnstakingPeriod,
       fill_opening_failed_applicant_role_stake_unstaking_period: this.failedApplicantRoleStakeUnstakingPeriod,
-      terminate_curator_application_stake_unstaking_period: this.terminateCuratorApplicationStakeUnstakingPeriod,
-      terminate_curator_role_stake_unstaking_period: this.terminateCuratorRoleStakeUnstakingPeriod,
-      exit_curator_role_application_stake_unstaking_period: this.exitCuratorRoleApplicationStakeUnstakingPeriod,
-      exit_curator_role_stake_unstaking_period: this.exitCuratorRoleStakeUnstakingPeriod,
+      terminate_application_stake_unstaking_period: this.terminateApplicationStakeUnstakingPeriod,
+      terminate_role_stake_unstaking_period: this.terminateRoleStakeUnstakingPeriod,
+      exit_role_application_stake_unstaking_period: this.exitRoleApplicationStakeUnstakingPeriod,
+      exit_role_stake_unstaking_period: this.exitRoleStakeUnstakingPeriod,
     };
   }
 }

+ 41 - 0
tests/network-tests/src/nicaea/tests/proposals/contentWorkingGroupMintCapacityProposalTest.ts

@@ -0,0 +1,41 @@
+import { KeyringPair } from '@polkadot/keyring/types';
+import { membershipTest } from '../impl/membershipCreation';
+import { councilTest } from '../impl/electingCouncil';
+import { workingGroupMintCapacityProposalTest } from './impl/workingGroupMintCapacityProposal';
+import { initConfig } from '../../utils/config';
+import { Keyring, WsProvider } from '@polkadot/api';
+import BN from 'bn.js';
+import { setTestTimeout } from '../../utils/setTestTimeout';
+import tap from 'tap';
+import { registerJoystreamTypes } from '@nicaea/types';
+import { closeApi } from '../impl/closeApi';
+import { ApiWrapper } from '../../utils/apiWrapper';
+
+tap.mocha.describe('Validator count proposal scenario', async () => {
+  initConfig();
+  registerJoystreamTypes();
+
+  const m1KeyPairs: KeyringPair[] = new Array();
+  const m2KeyPairs: KeyringPair[] = new Array();
+
+  const keyring = new Keyring({ type: 'sr25519' });
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!;
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!;
+  const nodeUrl: string = process.env.NODE_URL!;
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
+  const K: number = +process.env.COUNCIL_ELECTION_K!;
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!);
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!);
+  const mintingCapacityIncrement: BN = new BN(+process.env.MINTING_CAPACITY_INCREMENT!);
+  const durationInBlocks: number = 29;
+
+  const provider = new WsProvider(nodeUrl);
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider);
+
+  setTestTimeout(apiWrapper, durationInBlocks);
+  membershipTest(apiWrapper, m1KeyPairs, keyring, N, paidTerms, sudoUri);
+  membershipTest(apiWrapper, m2KeyPairs, keyring, N, paidTerms, sudoUri);
+  councilTest(apiWrapper, m1KeyPairs, m2KeyPairs, keyring, K, sudoUri, greaterStake, lesserStake);
+  workingGroupMintCapacityProposalTest(apiWrapper, m1KeyPairs, m2KeyPairs, keyring, sudoUri, mintingCapacityIncrement);
+  closeApi(apiWrapper);
+});

+ 315 - 0
tests/network-tests/src/nicaea/tests/proposals/impl/proposalsModule.ts

@@ -0,0 +1,315 @@
+import { KeyringPair } from '@polkadot/keyring/types';
+import { ApiWrapper, WorkingGroups } from '../../../utils/apiWrapper';
+import { v4 as uuid } from 'uuid';
+import BN from 'bn.js';
+import { WorkingGroupOpening } from '../../../dto/workingGroupOpening';
+import { FillOpeningParameters } from '../../../dto/fillOpeningParameters';
+
+export async function createWorkingGroupLeaderOpening(
+  apiWrapper: ApiWrapper,
+  m1KeyPairs: KeyringPair[],
+  sudo: KeyringPair,
+  applicationStake: BN,
+  roleStake: BN,
+  workingGroup: string
+): Promise<BN> {
+  // Setup
+  const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8);
+  const description: string = 'Testing working group lead opening proposal ' + uuid().substring(0, 8);
+
+  // Proposal stake calculation
+  const proposalStake: BN = new BN(100000);
+  const proposalFee: BN = apiWrapper.estimateProposeCreateWorkingGroupLeaderOpeningFee();
+  await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, proposalFee.add(proposalStake));
+
+  // Opening construction
+  const opening = new WorkingGroupOpening();
+  opening.setMaxActiveApplicants(new BN(m1KeyPairs.length));
+  opening.setMaxReviewPeriodLength(new BN(32));
+  opening.setApplicationStakingPolicyAmount(new BN(applicationStake));
+  opening.setApplicationCrowdedOutUnstakingPeriodLength(new BN(1));
+  opening.setApplicationExpiredUnstakingPeriodLength(new BN(1));
+  opening.setRoleStakingPolicyAmount(new BN(roleStake));
+  opening.setRoleCrowdedOutUnstakingPeriodLength(new BN(1));
+  opening.setRoleExpiredUnstakingPeriodLength(new BN(1));
+  opening.setSlashableMaxCount(new BN(1));
+  opening.setSlashableMaxPercentPtsPerTime(new BN(100));
+  opening.setSuccessfulApplicantApplicationStakeUnstakingPeriod(new BN(1));
+  opening.setFailedApplicantApplicationStakeUnstakingPeriod(new BN(1));
+  opening.setFailedApplicantRoleStakeUnstakingPeriod(new BN(1));
+  opening.setTerminateApplicationStakeUnstakingPeriod(new BN(1));
+  opening.setTerminateRoleStakeUnstakingPeriod(new BN(1));
+  opening.setExitRoleApplicationStakeUnstakingPeriod(new BN(1));
+  opening.setExitRoleStakeUnstakingPeriod(new BN(1));
+  opening.setText(uuid().substring(0, 8));
+
+  // Proposal creation
+  const proposalPromise = apiWrapper.expectProposalCreated();
+  await apiWrapper.proposeCreateWorkingGroupLeaderOpening(
+    m1KeyPairs[0],
+    proposalTitle,
+    description,
+    proposalStake,
+    opening,
+    workingGroup
+  );
+  const proposalNumber: BN = await proposalPromise;
+  return proposalNumber;
+}
+
+export async function beginWorkingGroupLeaderApplicationReview(
+  apiWrapper: ApiWrapper,
+  m1KeyPairs: KeyringPair[],
+  sudo: KeyringPair,
+  openingId: BN,
+  workingGroup: string
+): Promise<BN> {
+  // Setup
+  const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8);
+  const description: string = 'Testing begin working group lead application review proposal ' + uuid().substring(0, 8);
+
+  // Proposal stake calculation
+  const proposalStake: BN = new BN(25000);
+  const proposalFee: BN = apiWrapper.estimateProposeBeginWorkingGroupLeaderApplicationReviewFee();
+  await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, proposalFee.add(proposalStake));
+
+  // Proposal creation
+  const proposalPromise = apiWrapper.expectProposalCreated();
+  await apiWrapper.proposeBeginWorkingGroupLeaderApplicationReview(
+    m1KeyPairs[0],
+    proposalTitle,
+    description,
+    proposalStake,
+    openingId,
+    workingGroup
+  );
+  const proposalNumber: BN = await proposalPromise;
+  return proposalNumber;
+}
+
+export async function fillLeaderOpeningProposal(
+  apiWrapper: ApiWrapper,
+  m1KeyPairs: KeyringPair[],
+  applicantRoleAccountAddress: string,
+  sudo: KeyringPair,
+  firstRewardInterval: BN,
+  rewardInterval: BN,
+  payoutAmount: BN,
+  openingId: BN,
+  workingGroup: WorkingGroups
+): Promise<BN> {
+  // Setup
+  const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8);
+  const description: string = 'Testing fill opening proposal ' + uuid().substring(0, 8);
+  const workingGroupString: string = apiWrapper.getWorkingGroupString(workingGroup);
+
+  // Proposal stake calculation
+  const proposalStake: BN = new BN(50000);
+  const proposalFee: BN = apiWrapper.estimateProposeFillLeaderOpeningFee();
+  await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, proposalFee.add(proposalStake));
+
+  // Proposal creation
+  const applicationId: BN = (
+    await apiWrapper.getActiveApplicationsIdsByRoleAccount(applicantRoleAccountAddress, workingGroup)
+  )[0];
+  const now = await apiWrapper.getBestBlock();
+  const fillOpeningParameters: FillOpeningParameters = new FillOpeningParameters();
+  fillOpeningParameters.setAmountPerPayout(payoutAmount);
+  fillOpeningParameters.setNextPaymentAtBlock(now.add(firstRewardInterval));
+  fillOpeningParameters.setPayoutInterval(rewardInterval);
+  fillOpeningParameters.setOpeningId(openingId);
+  fillOpeningParameters.setSuccessfulApplicationId(applicationId);
+  fillOpeningParameters.setWorkingGroup(workingGroupString);
+
+  const proposalPromise = apiWrapper.expectProposalCreated();
+  await apiWrapper.proposeFillLeaderOpening(
+    m1KeyPairs[0],
+    proposalTitle,
+    description,
+    proposalStake,
+    fillOpeningParameters
+  );
+  const proposalNumber: BN = await proposalPromise;
+  return proposalNumber;
+}
+
+export async function terminateLeaderRoleProposal(
+  apiWrapper: ApiWrapper,
+  m1KeyPairs: KeyringPair[],
+  leaderRoleAccountAddress: string,
+  sudo: KeyringPair,
+  slash: boolean,
+  workingGroup: WorkingGroups
+) {
+  // Setup
+  const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8);
+  const description: string = 'Testing begin working group lead application review proposal ' + uuid().substring(0, 8);
+  const rationale: string = 'Testing leader termination ' + uuid().substring(0, 8);
+  const workingGroupString: string = apiWrapper.getWorkingGroupString(workingGroup);
+  const workerId: BN = await apiWrapper.getWorkerIdByRoleAccount(leaderRoleAccountAddress, workingGroup);
+
+  // Proposal stake calculation
+  const proposalStake: BN = new BN(100000);
+  const proposalFee: BN = apiWrapper.estimateProposeTerminateLeaderRoleFee();
+  await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, proposalFee.add(proposalStake));
+
+  // Proposal creation
+  const proposalPromise = apiWrapper.expectProposalCreated();
+  await apiWrapper.proposeTerminateLeaderRole(
+    m1KeyPairs[0],
+    proposalTitle,
+    description,
+    proposalStake,
+    workerId,
+    rationale,
+    slash,
+    workingGroupString
+  );
+  const proposalNumber: BN = await proposalPromise;
+  return proposalNumber;
+}
+
+export async function setLeaderRewardProposal(
+  apiWrapper: ApiWrapper,
+  m1KeyPairs: KeyringPair[],
+  sudo: KeyringPair,
+  payoutAmount: BN,
+  workingGroup: WorkingGroups
+): Promise<BN> {
+  // Setup
+  const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8);
+  const description: string = 'Testing set leader reward proposal ' + uuid().substring(0, 8);
+  const workingGroupString: string = apiWrapper.getWorkingGroupString(workingGroup);
+  const workerId: BN = (await apiWrapper.getLeadWorkerId(workingGroup))!;
+
+  // Proposal stake calculation
+  const proposalStake: BN = new BN(50000);
+  const proposalFee: BN = apiWrapper.estimateProposeLeaderRewardFee();
+  await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, proposalFee.add(proposalStake));
+
+  // Proposal creation
+  const proposalPromise = apiWrapper.expectProposalCreated();
+  await apiWrapper.proposeLeaderReward(
+    m1KeyPairs[0],
+    proposalTitle,
+    description,
+    proposalStake,
+    workerId,
+    payoutAmount,
+    workingGroupString
+  );
+  const proposalNumber: BN = await proposalPromise;
+  return proposalNumber;
+}
+
+export async function decreaseLeaderStakeProposal(
+  apiWrapper: ApiWrapper,
+  m1KeyPairs: KeyringPair[],
+  sudo: KeyringPair,
+  stakeDecrement: BN,
+  workingGroup: WorkingGroups
+): Promise<BN> {
+  // Setup
+  const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8);
+  const description: string = 'Testing decrease leader stake proposal ' + uuid().substring(0, 8);
+  const workingGroupString: string = apiWrapper.getWorkingGroupString(workingGroup);
+  const workerId: BN = (await apiWrapper.getLeadWorkerId(workingGroup))!;
+
+  // Proposal stake calculation
+  const proposalStake: BN = new BN(50000);
+  const proposalFee: BN = apiWrapper.estimateProposeDecreaseLeaderStakeFee();
+  await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, proposalFee.add(proposalStake));
+
+  // Proposal creation
+  const proposalPromise = apiWrapper.expectProposalCreated();
+  await apiWrapper.proposeDecreaseLeaderStake(
+    m1KeyPairs[0],
+    proposalTitle,
+    description,
+    proposalStake,
+    workerId,
+    stakeDecrement,
+    workingGroupString
+  );
+  const proposalNumber: BN = await proposalPromise;
+  return proposalNumber;
+}
+
+export async function slashLeaderProposal(
+  apiWrapper: ApiWrapper,
+  m1KeyPairs: KeyringPair[],
+  sudo: KeyringPair,
+  slashAmount: BN,
+  workingGroup: WorkingGroups
+): Promise<BN> {
+  // Setup
+  const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8);
+  const description: string = 'Testing slash leader stake proposal ' + uuid().substring(0, 8);
+  const workingGroupString: string = apiWrapper.getWorkingGroupString(workingGroup);
+  const workerId: BN = (await apiWrapper.getLeadWorkerId(workingGroup))!;
+
+  // Proposal stake calculation
+  const proposalStake: BN = new BN(50000);
+  const proposalFee: BN = apiWrapper.estimateProposeSlashLeaderStakeFee();
+  await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, proposalFee.add(proposalStake));
+
+  // Proposal creation
+  const proposalPromise = apiWrapper.expectProposalCreated();
+  await apiWrapper.proposeSlashLeaderStake(
+    m1KeyPairs[0],
+    proposalTitle,
+    description,
+    proposalStake,
+    workerId,
+    slashAmount,
+    workingGroupString
+  );
+  const proposalNumber: BN = await proposalPromise;
+  return proposalNumber;
+}
+
+export async function workingGroupMintCapacityProposal(
+  apiWrapper: ApiWrapper,
+  m1KeyPairs: KeyringPair[],
+  sudo: KeyringPair,
+  mintCapacity: BN,
+  workingGroup: WorkingGroups
+): Promise<BN> {
+  // Setup
+  const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8);
+  const description: string = 'Testing working group mint capacity proposal ' + uuid().substring(0, 8);
+  const workingGroupString: string = apiWrapper.getWorkingGroupString(workingGroup);
+
+  // Proposal stake calculation
+  const proposalStake: BN = new BN(50000);
+  const proposalFee: BN = apiWrapper.estimateProposeWorkingGroupMintCapacityFee();
+  await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, proposalFee.add(proposalStake));
+
+  // Proposal creation
+  const proposalPromise = apiWrapper.expectProposalCreated();
+  await apiWrapper.proposeWorkingGroupMintCapacity(
+    m1KeyPairs[0],
+    proposalTitle,
+    description,
+    proposalStake,
+    mintCapacity,
+    workingGroupString
+  );
+  const proposalNumber: BN = await proposalPromise;
+  return proposalNumber;
+}
+
+export async function voteForProposal(
+  apiWrapper: ApiWrapper,
+  m2KeyPairs: KeyringPair[],
+  sudo: KeyringPair,
+  proposalNumber: BN
+): Promise<void> {
+  const proposalVoteFee: BN = apiWrapper.estimateVoteForProposalFee();
+  await apiWrapper.transferBalanceToAccounts(sudo, m2KeyPairs, proposalVoteFee);
+
+  // Approving the proposal
+  const proposalExecutionPromise = apiWrapper.expectProposalFinalized();
+  await apiWrapper.batchApproveProposal(m2KeyPairs, proposalNumber);
+  await proposalExecutionPromise;
+}

+ 4 - 4
tests/network-tests/src/nicaea/tests/proposals/impl/workingGroupMintCapacityProposal.ts

@@ -21,11 +21,11 @@ export function workingGroupMintCapacityProposalTest(
     sudo = keyring.addFromUri(sudoUri);
     const description: string = 'spending proposal which is used for API network testing';
     const runtimeVoteFee: BN = apiWrapper.estimateVoteForProposalFee();
-    const initialMintingCapacity: BN = await apiWrapper.getWorkingGroupMintCapacity();
+    const initialMintingCapacity: BN = await apiWrapper.getContentWorkingGroupMintCapacity();
 
     // Topping the balances
     const proposalStake: BN = new BN(50000);
-    const runtimeProposalFee: BN = apiWrapper.estimateProposeWorkingGroupMintCapacityFee(
+    const runtimeProposalFee: BN = apiWrapper.estimateProposeContentWorkingGroupMintCapacityFee(
       description,
       description,
       proposalStake,
@@ -37,7 +37,7 @@ export function workingGroupMintCapacityProposalTest(
     // Proposal creation
     const proposedMintingCapacity: BN = initialMintingCapacity.add(mintingCapacityIncrement);
     const proposalPromise = apiWrapper.expectProposalCreated();
-    await apiWrapper.proposeWorkingGroupMintCapacity(
+    await apiWrapper.proposeContentWorkingGroupMintCapacity(
       m1KeyPairs[0],
       'testing mint capacity' + uuid().substring(0, 8),
       'mint capacity to test proposal functionality' + uuid().substring(0, 8),
@@ -50,7 +50,7 @@ export function workingGroupMintCapacityProposalTest(
     const mintCapacityPromise = apiWrapper.expectProposalFinalized();
     await apiWrapper.batchApproveProposal(m2KeyPairs, proposalNumber);
     await mintCapacityPromise;
-    const newMintingCapacity: BN = await apiWrapper.getWorkingGroupMintCapacity();
+    const newMintingCapacity: BN = await apiWrapper.getContentWorkingGroupMintCapacity();
     assert(
       proposedMintingCapacity.eq(newMintingCapacity),
       `Content working group has unexpected minting capacity ${newMintingCapacity}, expected ${proposedMintingCapacity}`

+ 213 - 0
tests/network-tests/src/nicaea/tests/proposals/manageLeaderRole.ts

@@ -0,0 +1,213 @@
+import { KeyringPair } from '@polkadot/keyring/types';
+import { membershipTest } from '../impl/membershipCreation';
+import { councilTest } from '../impl/electingCouncil';
+import { initConfig } from '../../utils/config';
+import { Keyring, WsProvider } from '@polkadot/api';
+import BN from 'bn.js';
+import { setTestTimeout } from '../../utils/setTestTimeout';
+import tap from 'tap';
+import { registerJoystreamTypes } from '@nicaea/types';
+import { closeApi } from '../impl/closeApi';
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper';
+import {
+  createWorkingGroupLeaderOpening,
+  voteForProposal,
+  beginWorkingGroupLeaderApplicationReview,
+  fillLeaderOpeningProposal,
+  terminateLeaderRoleProposal,
+  setLeaderRewardProposal,
+  decreaseLeaderStakeProposal,
+  slashLeaderProposal,
+} from './impl/proposalsModule';
+import {
+  applyForOpening,
+  expectLeadOpeningAdded,
+  expectLeaderSet,
+  expectBeganApplicationReview,
+  expectLeaderRoleTerminated,
+  expectLeaderRewardAmountUpdated,
+  expectLeaderStakeDecreased,
+  expectLeaderSlashed,
+} from '../workingGroup/impl/workingGroupModule';
+
+tap.mocha.describe('Set lead proposal scenario', async () => {
+  initConfig();
+  registerJoystreamTypes();
+
+  const m1KeyPairs: KeyringPair[] = new Array();
+  const m2KeyPairs: KeyringPair[] = new Array();
+  const leadKeyPair: KeyringPair[] = new Array();
+
+  const keyring = new Keyring({ type: 'sr25519' });
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!;
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!;
+  const nodeUrl: string = process.env.NODE_URL!;
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
+  const K: number = +process.env.COUNCIL_ELECTION_K!;
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!);
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!);
+  const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!);
+  const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!);
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
+  const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!);
+  const alteredPayoutAmount: BN = new BN(process.env.ALTERED_PAYOUT_AMOUNT!);
+  const stakeDecrement: BN = new BN(process.env.STAKE_DECREMENT!);
+  const slashAmount: BN = new BN(process.env.SLASH_AMOUNT!);
+  const durationInBlocks: number = 70;
+
+  const provider = new WsProvider(nodeUrl);
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider);
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri);
+
+  setTestTimeout(apiWrapper, durationInBlocks);
+  membershipTest(apiWrapper, m1KeyPairs, keyring, N, paidTerms, sudoUri);
+  membershipTest(apiWrapper, m2KeyPairs, keyring, N, paidTerms, sudoUri);
+  membershipTest(apiWrapper, leadKeyPair, keyring, 1, paidTerms, sudoUri);
+  councilTest(apiWrapper, m1KeyPairs, m2KeyPairs, keyring, K, sudoUri, greaterStake, lesserStake);
+
+  let createOpeningProposalId: BN;
+  let openingId: BN;
+  tap.test(
+    'Propose create leader opening',
+    async () =>
+      (createOpeningProposalId = await createWorkingGroupLeaderOpening(
+        apiWrapper,
+        m1KeyPairs,
+        sudo,
+        applicationStake,
+        roleStake,
+        'Storage'
+      ))
+  );
+  tap.test('Approve add opening proposal', async () => {
+    voteForProposal(apiWrapper, m2KeyPairs, sudo, createOpeningProposalId);
+    openingId = await expectLeadOpeningAdded(apiWrapper);
+  });
+
+  tap.test(
+    'Apply for lead opening',
+    async () =>
+      await applyForOpening(
+        apiWrapper,
+        leadKeyPair,
+        sudo,
+        applicationStake,
+        roleStake,
+        new BN(openingId),
+        WorkingGroups.storageWorkingGroup,
+        false
+      )
+  );
+  let beginReviewProposalId: BN;
+  tap.test(
+    'Propose begin leader application review',
+    async () =>
+      (beginReviewProposalId = await beginWorkingGroupLeaderApplicationReview(
+        apiWrapper,
+        m1KeyPairs,
+        sudo,
+        new BN(openingId),
+        'Storage'
+      ))
+  );
+  tap.test('Approve begin review proposal', async () => {
+    voteForProposal(apiWrapper, m2KeyPairs, sudo, beginReviewProposalId);
+    expectBeganApplicationReview(apiWrapper);
+  });
+
+  let fillLeaderOpeningProposalId: BN;
+  tap.test(
+    'Propose fill leader opening',
+    async () =>
+      (fillLeaderOpeningProposalId = await fillLeaderOpeningProposal(
+        apiWrapper,
+        m1KeyPairs,
+        leadKeyPair[0].address,
+        sudo,
+        firstRewardInterval,
+        rewardInterval,
+        payoutAmount,
+        new BN(openingId),
+        WorkingGroups.storageWorkingGroup
+      ))
+  );
+  tap.test('Approve fill leader opening', async () => {
+    voteForProposal(apiWrapper, m2KeyPairs, sudo, fillLeaderOpeningProposalId);
+    await expectLeaderSet(apiWrapper, leadKeyPair[0].address, WorkingGroups.storageWorkingGroup);
+  });
+
+  let rewardProposalId: BN;
+  tap.test(
+    'Propose leader reward',
+    async () =>
+      (rewardProposalId = await setLeaderRewardProposal(
+        apiWrapper,
+        m1KeyPairs,
+        sudo,
+        alteredPayoutAmount,
+        WorkingGroups.storageWorkingGroup
+      ))
+  );
+  tap.test('Approve new leader reward', async () => {
+    voteForProposal(apiWrapper, m2KeyPairs, sudo, rewardProposalId);
+    await expectLeaderRewardAmountUpdated(apiWrapper, alteredPayoutAmount, WorkingGroups.storageWorkingGroup);
+  });
+
+  let decreaseStakeProposalId: BN;
+  let newStake: BN;
+  tap.test(
+    'Propose decrease stake',
+    async () =>
+      (decreaseStakeProposalId = await decreaseLeaderStakeProposal(
+        apiWrapper,
+        m1KeyPairs,
+        sudo,
+        stakeDecrement,
+        WorkingGroups.storageWorkingGroup
+      ))
+  );
+  tap.test('Approve decreased leader stake', async () => {
+    newStake = applicationStake.sub(stakeDecrement);
+    voteForProposal(apiWrapper, m2KeyPairs, sudo, decreaseStakeProposalId);
+    await expectLeaderStakeDecreased(apiWrapper, newStake, WorkingGroups.storageWorkingGroup);
+  });
+
+  let slashProposalId: BN;
+  tap.test(
+    'Propose leader slash',
+    async () =>
+      (slashProposalId = await slashLeaderProposal(
+        apiWrapper,
+        m1KeyPairs,
+        sudo,
+        slashAmount,
+        WorkingGroups.storageWorkingGroup
+      ))
+  );
+  tap.test('Approve leader slash', async () => {
+    newStake = newStake.sub(slashAmount);
+    voteForProposal(apiWrapper, m2KeyPairs, sudo, slashProposalId);
+    await expectLeaderSlashed(apiWrapper, newStake, WorkingGroups.storageWorkingGroup);
+  });
+
+  let terminateLeaderRoleProposalId: BN;
+  tap.test(
+    'Propose terminate leader role',
+    async () =>
+      (terminateLeaderRoleProposalId = await terminateLeaderRoleProposal(
+        apiWrapper,
+        m1KeyPairs,
+        leadKeyPair[0].address,
+        sudo,
+        false,
+        WorkingGroups.storageWorkingGroup
+      ))
+  );
+  tap.test('Approve leader role termination', async () => {
+    voteForProposal(apiWrapper, m2KeyPairs, sudo, terminateLeaderRoleProposalId);
+    await expectLeaderRoleTerminated(apiWrapper, WorkingGroups.storageWorkingGroup);
+  });
+
+  closeApi(apiWrapper);
+});

+ 28 - 6
tests/network-tests/src/nicaea/tests/proposals/workingGroupMintCapacityProposalTest.ts

@@ -1,7 +1,6 @@
 import { KeyringPair } from '@polkadot/keyring/types';
 import { membershipTest } from '../impl/membershipCreation';
 import { councilTest } from '../impl/electingCouncil';
-import { workingGroupMintCapacityProposalTest } from './impl/workingGroupMintCapacityProposal';
 import { initConfig } from '../../utils/config';
 import { Keyring, WsProvider } from '@polkadot/api';
 import BN from 'bn.js';
@@ -9,9 +8,11 @@ import { setTestTimeout } from '../../utils/setTestTimeout';
 import tap from 'tap';
 import { registerJoystreamTypes } from '@nicaea/types';
 import { closeApi } from '../impl/closeApi';
-import { ApiWrapper } from '../../utils/apiWrapper';
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper';
+import { voteForProposal, workingGroupMintCapacityProposal } from './impl/proposalsModule';
+import { expectMintCapacityChanged } from '../workingGroup/impl/workingGroupModule';
 
-tap.mocha.describe('Validator count proposal scenario', async () => {
+tap.mocha.describe('Set storage working group mint capacity scenario', async () => {
   initConfig();
   registerJoystreamTypes();
 
@@ -26,16 +27,37 @@ tap.mocha.describe('Validator count proposal scenario', async () => {
   const K: number = +process.env.COUNCIL_ELECTION_K!;
   const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!);
   const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!);
-  const mintingCapacityIncrement: BN = new BN(+process.env.MINTING_CAPACITY_INCREMENT!);
-  const durationInBlocks: number = 29;
+  const mintCapacityIncrement: BN = new BN(process.env.MINT_CAPACITY_INCREMENT!);
+  const durationInBlocks: number = 30;
 
   const provider = new WsProvider(nodeUrl);
   const apiWrapper: ApiWrapper = await ApiWrapper.create(provider);
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri);
 
   setTestTimeout(apiWrapper, durationInBlocks);
   membershipTest(apiWrapper, m1KeyPairs, keyring, N, paidTerms, sudoUri);
   membershipTest(apiWrapper, m2KeyPairs, keyring, N, paidTerms, sudoUri);
   councilTest(apiWrapper, m1KeyPairs, m2KeyPairs, keyring, K, sudoUri, greaterStake, lesserStake);
-  workingGroupMintCapacityProposalTest(apiWrapper, m1KeyPairs, m2KeyPairs, keyring, sudoUri, mintingCapacityIncrement);
+
+  let mintCapacityProposalId: BN;
+  const newMintCapacity: BN = (await apiWrapper.getWorkingGroupMintCapacity(WorkingGroups.storageWorkingGroup)).add(
+    mintCapacityIncrement
+  );
+  tap.test(
+    'Propose mint capacity',
+    async () =>
+      (mintCapacityProposalId = await workingGroupMintCapacityProposal(
+        apiWrapper,
+        m1KeyPairs,
+        sudo,
+        newMintCapacity,
+        WorkingGroups.storageWorkingGroup
+      ))
+  );
+  tap.test('Approve mint capacity', async () => {
+    voteForProposal(apiWrapper, m2KeyPairs, sudo, mintCapacityProposalId);
+    await expectMintCapacityChanged(apiWrapper, newMintCapacity);
+  });
+
   closeApi(apiWrapper);
 });

+ 2 - 2
tests/network-tests/src/nicaea/tests/workingGroup/atLeastValueBugTest.ts

@@ -31,8 +31,8 @@ tap.mocha.describe('Worker application happy case scenario', async () => {
   const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
   const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!);
   const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!);
-  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
-  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
   const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!);
   const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!);
   const durationInBlocks: number = 48;

+ 104 - 22
tests/network-tests/src/nicaea/tests/workingGroup/impl/workingGroupModule.ts

@@ -2,6 +2,7 @@ import BN from 'bn.js';
 import { assert } from 'chai';
 import { ApiWrapper, WorkingGroups } from '../../../utils/apiWrapper';
 import { KeyringPair } from '@polkadot/keyring/types';
+import { Balance } from '@polkadot/types/interfaces';
 import { Keyring } from '@polkadot/api';
 import { v4 as uuid } from 'uuid';
 import { RewardRelationship } from '@nicaea/types/recurring-rewards';
@@ -23,7 +24,7 @@ export async function addWorkerOpening(
   expectFailure: boolean
 ): Promise<BN> {
   // Worker opening construction
-  let opening = new WorkingGroupOpening();
+  const opening = new WorkingGroupOpening();
   const activateAtBlock: BN | undefined = activationDelay.eqn(0)
     ? undefined
     : (await apiWrapper.getBestBlock()).add(activationDelay);
@@ -41,10 +42,10 @@ export async function addWorkerOpening(
   opening.setSuccessfulApplicantApplicationStakeUnstakingPeriod(unstakingPeriod);
   opening.setFailedApplicantApplicationStakeUnstakingPeriod(unstakingPeriod);
   opening.setFailedApplicantRoleStakeUnstakingPeriod(unstakingPeriod);
-  opening.setTerminateCuratorApplicationStakeUnstakingPeriod(unstakingPeriod);
-  opening.setTerminateCuratorRoleStakeUnstakingPeriod(unstakingPeriod);
-  opening.setExitCuratorRoleApplicationStakeUnstakingPeriod(unstakingPeriod);
-  opening.setExitCuratorRoleStakeUnstakingPeriod(unstakingPeriod);
+  opening.setTerminateApplicationStakeUnstakingPeriod(unstakingPeriod);
+  opening.setTerminateRoleStakeUnstakingPeriod(unstakingPeriod);
+  opening.setExitRoleApplicationStakeUnstakingPeriod(unstakingPeriod);
+  opening.setExitRoleStakeUnstakingPeriod(unstakingPeriod);
   opening.setText(uuid().substring(0, 8));
   opening.setOpeningType('Worker');
 
@@ -75,7 +76,7 @@ export async function addLeaderOpening(
   const activateAtBlock: BN | undefined = activationDelay.eqn(0)
     ? undefined
     : (await apiWrapper.getBestBlock()).add(activationDelay);
-  let opening = new WorkingGroupOpening();
+  const opening = new WorkingGroupOpening();
   opening.setActivateAtBlock(activateAtBlock);
   opening.setMaxActiveApplicants(new BN(membersKeyPairs.length));
   opening.setMaxReviewPeriodLength(new BN(32));
@@ -90,10 +91,10 @@ export async function addLeaderOpening(
   opening.setSuccessfulApplicantApplicationStakeUnstakingPeriod(new BN(1));
   opening.setFailedApplicantApplicationStakeUnstakingPeriod(new BN(1));
   opening.setFailedApplicantRoleStakeUnstakingPeriod(new BN(1));
-  opening.setTerminateCuratorApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setTerminateCuratorRoleStakeUnstakingPeriod(new BN(1));
-  opening.setExitCuratorRoleApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setExitCuratorRoleStakeUnstakingPeriod(new BN(1));
+  opening.setTerminateApplicationStakeUnstakingPeriod(new BN(1));
+  opening.setTerminateRoleStakeUnstakingPeriod(new BN(1));
+  opening.setExitRoleApplicationStakeUnstakingPeriod(new BN(1));
+  opening.setExitRoleStakeUnstakingPeriod(new BN(1));
   opening.setText(uuid().substring(0, 8));
   opening.setOpeningType('leader');
 
@@ -180,7 +181,7 @@ export async function beginApplicationReview(
   await apiWrapper.transferBalance(sudo, lead.address, beginReviewFee);
 
   // Begin application review
-  const beginApplicantReviewPromise: Promise<void> = apiWrapper.expectApplicationReviewBegan();
+  const beginApplicantReviewPromise: Promise<BN> = apiWrapper.expectApplicationReviewBegan();
   await apiWrapper.beginApplicantReview(lead, openingId, module);
   await beginApplicantReviewPromise;
 }
@@ -228,6 +229,8 @@ export async function fillOpening(
     module
   );
   const applicationIdToWorkerIdMap: ApplicationIdToWorkerIdMap = await fillOpeningPromise;
+
+  // Assertions
   applicationIdToWorkerIdMap.forEach(async (workerId, applicationId) => {
     const worker: Worker = await apiWrapper.getWorkerById(workerId, module);
     const application: Application = await apiWrapper.getApplicationById(applicationId, module);
@@ -236,8 +239,6 @@ export async function fillOpening(
       `Role account ids does not match, worker account: ${worker.role_account_id}, application account ${application.role_account_id}`
     );
   });
-
-  // Assertions
   const openingWorkersAccounts: string[] = (await apiWrapper.getWorkers(module)).map(worker =>
     worker.role_account_id.toString()
   );
@@ -274,23 +275,17 @@ export async function fillLeaderOpening(
     payoutInterval,
     module
   );
+
+  // Assertions
   const applicationIdToWorkerIdMap: ApplicationIdToWorkerIdMap = await fillOpeningPromise;
   applicationIdToWorkerIdMap.forEach(async (workerId, applicationId) => {
     const worker: Worker = await apiWrapper.getWorkerById(workerId, module);
     const application: Application = await apiWrapper.getApplicationById(applicationId, module);
     assert(
       worker.role_account_id.toString() === application.role_account_id.toString(),
-      `Role account ids does not match, worker account: ${worker.role_account_id}, application account ${application.role_account_id}`
+      `Role account ids does not match, leader account: ${worker.role_account_id}, application account ${application.role_account_id}`
     );
   });
-
-  // Assertions
-  const openingWorkersAccounts: string[] = (await apiWrapper.getWorkers(module)).map(worker =>
-    worker.role_account_id.toString()
-  );
-  membersKeyPairs.forEach(keyPair =>
-    assert(openingWorkersAccounts.includes(keyPair.address), `Account ${keyPair.address} is not leader`)
-  );
 }
 
 export async function increaseStake(
@@ -509,3 +504,90 @@ export async function awaitPayout(apiWrapper: ApiWrapper, membersKeyPairs: Keyri
 export async function setMintCapacity(apiWrapper: ApiWrapper, sudo: KeyringPair, capacity: BN, module: WorkingGroups) {
   await apiWrapper.sudoSetWorkingGroupMintCapacity(sudo, capacity, module);
 }
+
+export async function expectLeadOpeningAdded(apiWrapper: ApiWrapper): Promise<BN> {
+  return apiWrapper.expectOpeningAdded();
+}
+
+export async function expectLeaderSet(
+  apiWrapper: ApiWrapper,
+  leaderAddress: string,
+  module: WorkingGroups
+): Promise<BN> {
+  const leadWorkerId: BN = await apiWrapper.expectLeaderSet();
+  const worker: Worker = await apiWrapper.getWorkerById(leadWorkerId, module);
+  const leaderApplicationId = (await apiWrapper.getApplicationsIdsByRoleAccount(leaderAddress, module))[0];
+  const application: Application = await apiWrapper.getApplicationById(leaderApplicationId, module);
+  assert(
+    worker.role_account_id.toString() === application.role_account_id.toString(),
+    `Role account ids does not match, leader account: ${worker.role_account_id}, application account ${application.role_account_id}`
+  );
+  return leadWorkerId;
+}
+
+export async function expectBeganApplicationReview(apiWrapper: ApiWrapper): Promise<BN> {
+  return apiWrapper.expectApplicationReviewBegan();
+}
+
+export async function expectLeaderRoleTerminated(apiWrapper: ApiWrapper, module: WorkingGroups): Promise<void> {
+  await apiWrapper.expectLeaderTerminated();
+  const leadWorkerId: BN | undefined = await apiWrapper.getLeadWorkerId(module);
+  assert(leadWorkerId === undefined, `Unexpected lead worker id: ${leadWorkerId}, expected none`);
+  return;
+}
+
+export async function expectLeaderRewardAmountUpdated(
+  apiWrapper: ApiWrapper,
+  expectedReward: BN,
+  module: WorkingGroups
+): Promise<void> {
+  await apiWrapper.expectWorkerRewardAmountUpdated();
+  const leadWorkerId: BN = (await apiWrapper.getLeadWorkerId(module))!;
+  const receivedReward: BN = (await apiWrapper.getRewardRelationship(leadWorkerId)).getField<Balance>(
+    'amount_per_payout'
+  );
+  assert(
+    receivedReward.eq(expectedReward),
+    `Unexpected reward amount for worker with id ${leadWorkerId}: ${receivedReward}, expected ${expectedReward}`
+  );
+  return;
+}
+
+export async function expectLeaderStakeDecreased(
+  apiWrapper: ApiWrapper,
+  expectedStake: BN,
+  module: WorkingGroups
+): Promise<void> {
+  await apiWrapper.expectWorkerStakeDecreased();
+  const leadWorkerId: BN = (await apiWrapper.getLeadWorkerId(module))!;
+  const receivedStake: BN = await apiWrapper.getWorkerStakeAmount(leadWorkerId, module);
+  assert(
+    receivedStake.eq(expectedStake),
+    `Unexpected stake amount for worker with id ${leadWorkerId}: ${receivedStake}, expected ${expectedStake}`
+  );
+  return;
+}
+
+export async function expectLeaderSlashed(
+  apiWrapper: ApiWrapper,
+  expectedStake: BN,
+  module: WorkingGroups
+): Promise<void> {
+  await apiWrapper.expectWorkerStakeSlashed();
+  const leadWorkerId: BN = (await apiWrapper.getLeadWorkerId(module))!;
+  const receivedStake: BN = await apiWrapper.getWorkerStakeAmount(leadWorkerId, module);
+  assert(
+    receivedStake.eq(expectedStake),
+    `Unexpected stake amount for worker with id after slash ${leadWorkerId}: ${receivedStake}, expected ${expectedStake}`
+  );
+  return;
+}
+
+export async function expectMintCapacityChanged(apiWrapper: ApiWrapper, expectedMintCapacity: BN): Promise<void> {
+  const receivedMintCapacity = await apiWrapper.expectMintCapacityChanged();
+  assert(
+    receivedMintCapacity.eq(expectedMintCapacity),
+    `Unexpected mint capacity: ${receivedMintCapacity}, expected ${expectedMintCapacity}`
+  );
+  return;
+}

+ 2 - 2
tests/network-tests/src/nicaea/tests/workingGroup/manageWorkerAsLeadTest.ts

@@ -36,8 +36,8 @@ tap.mocha.describe('Manage worker as worker scenario', async () => {
   const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
   const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!);
   const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!);
-  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
-  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
   const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!);
   const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!);
   const durationInBlocks: number = 60;

+ 2 - 2
tests/network-tests/src/nicaea/tests/workingGroup/manageWorkerAsWorkerTest.ts

@@ -36,8 +36,8 @@ tap.mocha.describe('Manage worker as worker scenario', async () => {
   const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
   const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!);
   const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!);
-  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
-  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
   const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!);
   const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!);
   const durationInBlocks: number = 38;

+ 2 - 2
tests/network-tests/src/nicaea/tests/workingGroup/workerApplicationHappyCaseTest.ts

@@ -34,8 +34,8 @@ tap.mocha.describe('Worker application happy case scenario', async () => {
   const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
   const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!);
   const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!);
-  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
-  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
   const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!);
   const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!);
   const durationInBlocks: number = 48;

+ 2 - 2
tests/network-tests/src/nicaea/tests/workingGroup/workerApplicationRejectionCaseTest.ts

@@ -33,8 +33,8 @@ tap.mocha.describe('Worker application happy case scenario', async () => {
   const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
   const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!);
   const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!);
-  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
-  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERWAL!);
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!);
   const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!);
   const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!);
   const durationInBlocks: number = 38;

+ 2 - 2
tests/network-tests/src/nicaea/tests/workingGroup/workerPayout.ts

@@ -35,8 +35,8 @@ tap.mocha.describe('Worker application happy case scenario', async () => {
   const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
   const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!);
   const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!);
-  const firstRewardInterval: BN = new BN(process.env.SHORT_FIRST_REWARD_INTERWAL!);
-  const rewardInterval: BN = new BN(process.env.SHORT_REWARD_INTERWAL!);
+  const firstRewardInterval: BN = new BN(process.env.SHORT_FIRST_REWARD_INTERVAL!);
+  const rewardInterval: BN = new BN(process.env.SHORT_REWARD_INTERVAL!);
   const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!);
   const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!);
   const mintCapacity: BN = new BN(process.env.STORAGE_WORKING_GROUP_MINTING_CAPACITY!);

+ 456 - 10
tests/network-tests/src/nicaea/utils/apiWrapper.ts

@@ -1,5 +1,5 @@
 import { ApiPromise, WsProvider } from '@polkadot/api';
-import { Option, Vec, Bytes, u32 } from '@polkadot/types';
+import { Option, Vec, Bytes, u32, u64 } from '@polkadot/types';
 import { Codec } from '@polkadot/types/types';
 import { KeyringPair } from '@polkadot/keyring/types';
 import { UserInfo, PaidMembershipTerms, MemberId } from '@nicaea/types/members';
@@ -18,6 +18,7 @@ import { Stake, StakedState } from '@nicaea/types/stake';
 import { RewardRelationship } from '@nicaea/types/recurring-rewards';
 import { Opening as HiringOpening, ApplicationId } from '@nicaea/types/hiring';
 import { WorkingGroupOpening } from '../dto/workingGroupOpening';
+import { FillOpeningParameters } from '../dto/fillOpeningParameters';
 
 export enum WorkingGroups {
   storageWorkingGroup = 'storageWorkingGroup',
@@ -41,6 +42,15 @@ export class ApiWrapper {
     this.api.disconnect();
   }
 
+  public getWorkingGroupString(workingGroup: WorkingGroups): string {
+    switch (workingGroup) {
+      case WorkingGroups.storageWorkingGroup:
+        return 'Storage';
+      default:
+        return 'Undefined';
+    }
+  }
+
   public async buyMembership(
     account: KeyringPair,
     paidTermsId: number,
@@ -134,7 +144,12 @@ export class ApiWrapper {
     );
   }
 
-  public estimateProposeWorkingGroupMintCapacityFee(title: string, description: string, stake: BN, balance: BN): BN {
+  public estimateProposeContentWorkingGroupMintCapacityFee(
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN
+  ): BN {
     return this.estimateTxFee(
       this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal(
         stake,
@@ -310,6 +325,156 @@ export class ApiWrapper {
     );
   }
 
+  public estimateProposeCreateWorkingGroupLeaderOpeningFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createAddWorkingGroupLeaderOpeningProposal(
+        0,
+        'some long title for the purpose of testing',
+        'some long description for the purpose of testing',
+        0,
+        {
+          activate_at: 'CurrentBlock',
+          commitment: {
+            application_rationing_policy: { max_active_applicants: '32' },
+            max_review_period_length: 32,
+            application_staking_policy: {
+              amount: 0,
+              amount_mode: 'AtLeast',
+              crowded_out_unstaking_period_length: 0,
+              review_period_expired_unstaking_period_length: 0,
+            },
+            role_staking_policy: {
+              amount: 0,
+              amount_mode: 'AtLeast',
+              crowded_out_unstaking_period_length: 0,
+              review_period_expired_unstaking_period_length: 0,
+            },
+            role_slashing_terms: {
+              Slashable: {
+                max_count: 0,
+                max_percent_pts_per_time: 0,
+              },
+            },
+            fill_opening_successful_applicant_application_stake_unstaking_period: 0,
+            fill_opening_failed_applicant_application_stake_unstaking_period: 0,
+            fill_opening_failed_applicant_role_stake_unstaking_period: 0,
+            terminate_curator_application_stake_unstaking_period: 0,
+            terminate_curator_role_stake_unstaking_period: 0,
+            exit_curator_role_application_stake_unstaking_period: 0,
+            exit_curator_role_stake_unstaking_period: 0,
+          },
+          human_readable_text: 'Opening readable text',
+          working_group: 'Storage',
+        }
+      )
+    );
+  }
+
+  public estimateProposeBeginWorkingGroupLeaderApplicationReviewFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createBeginReviewWorkingGroupLeaderApplicationsProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        'Storage'
+      )
+    );
+  }
+
+  public estimateProposeFillLeaderOpeningFee(): BN {
+    const fillOpeningParameters: FillOpeningParameters = new FillOpeningParameters();
+    fillOpeningParameters.setAmountPerPayout(new BN(1));
+    fillOpeningParameters.setNextPaymentAtBlock(new BN(99999));
+    fillOpeningParameters.setPayoutInterval(new BN(99999));
+    fillOpeningParameters.setOpeningId(new BN(0));
+    fillOpeningParameters.setSuccessfulApplicationId(new BN(0));
+    fillOpeningParameters.setWorkingGroup('Storage');
+
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createFillWorkingGroupLeaderOpeningProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        fillOpeningParameters.getFillOpeningParameters()
+      )
+    );
+  }
+
+  public estimateProposeTerminateLeaderRoleFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createTerminateWorkingGroupLeaderRoleProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        {
+          worker_id: 0,
+          rationale: 'Exceptionaly long and extraordinary descriptive rationale',
+          slash: true,
+          working_group: 'Storage',
+        }
+      )
+    );
+  }
+
+  public estimateProposeLeaderRewardFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetWorkingGroupLeaderRewardProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        0,
+        'Storage'
+      )
+    );
+  }
+
+  public estimateProposeDecreaseLeaderStakeFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createDecreaseWorkingGroupLeaderStakeProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        0,
+        'Storage'
+      )
+    );
+  }
+
+  public estimateProposeSlashLeaderStakeFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSlashWorkingGroupLeaderStakeProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        0,
+        'Storage'
+      )
+    );
+  }
+
+  public estimateProposeWorkingGroupMintCapacityFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetWorkingGroupMintCapacityProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        'Storage'
+      )
+    );
+  }
+
   private applyForCouncilElection(account: KeyringPair, amount: BN): Promise<void> {
     return this.sender.signAndSend(this.api.tx.councilElection.apply(amount), account, false);
   }
@@ -458,7 +623,7 @@ export class ApiWrapper {
     );
   }
 
-  public async proposeWorkingGroupMintCapacity(
+  public async proposeContentWorkingGroupMintCapacity(
     account: KeyringPair,
     title: string,
     description: string,
@@ -601,6 +766,29 @@ export class ApiWrapper {
     );
   }
 
+  public async proposeBeginWorkingGroupLeaderApplicationReview(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    openingId: BN,
+    workingGroup: string
+  ) {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createBeginReviewWorkingGroupLeaderApplicationsProposal(
+        memberId,
+        title,
+        description,
+        stake,
+        openingId,
+        workingGroup
+      ),
+      account,
+      false
+    );
+  }
+
   public approveProposal(account: KeyringPair, memberId: BN, proposal: BN): Promise<void> {
     return this.sender.signAndSend(this.api.tx.proposalsEngine.vote(memberId, proposal, 'Approve'), account, false);
   }
@@ -682,11 +870,23 @@ export class ApiWrapper {
     });
   }
 
-  public expectApplicationReviewBegan(): Promise<void> {
+  public expectLeaderSet(): Promise<BN> {
     return new Promise(async resolve => {
       await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
-          if (record.event.method && record.event.method.toString() === 'BeganApplicationReview') {
+          if (record.event.method && record.event.method.toString() === 'LeaderSet') {
+            resolve((record.event.data as unknown) as BN);
+          }
+        });
+      });
+    });
+  }
+
+  public expectLeaderTerminated(): Promise<void> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method && record.event.method.toString() === 'TerminatedLeader') {
             resolve();
           }
         });
@@ -694,6 +894,66 @@ export class ApiWrapper {
     });
   }
 
+  public expectWorkerRewardAmountUpdated(): Promise<void> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method && record.event.method.toString() === 'WorkerRewardAmountUpdated') {
+            resolve();
+          }
+        });
+      });
+    });
+  }
+
+  public expectWorkerStakeDecreased(): Promise<void> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method && record.event.method.toString() === 'StakeDecreased') {
+            resolve();
+          }
+        });
+      });
+    });
+  }
+
+  public expectWorkerStakeSlashed(): Promise<void> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method && record.event.method.toString() === 'StakeSlashed') {
+            resolve();
+          }
+        });
+      });
+    });
+  }
+
+  public expectApplicationReviewBegan(): Promise<BN> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method && record.event.method.toString() === 'BeganApplicationReview') {
+            resolve((record.event.data as unknown) as BN);
+          }
+        });
+      });
+    });
+  }
+
+  public expectMintCapacityChanged(): Promise<BN> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method && record.event.method.toString() === 'MintCapacityChanged') {
+            resolve((record.event.data[1] as unknown) as BN);
+          }
+        });
+      });
+    });
+  }
+
   public getTotalIssuance(): Promise<BN> {
     return this.api.query.balances.totalIssuance<Balance>();
   }
@@ -708,13 +968,20 @@ export class ApiWrapper {
     return this.api.query.proposalsEngine.proposalCount<u32>();
   }
 
-  public async getWorkingGroupMintCapacity(): Promise<BN> {
+  public async getContentWorkingGroupMintCapacity(): Promise<BN> {
     const mintId: MintId = await this.api.query.contentWorkingGroup.mint<MintId>();
     const mintCodec = await this.api.query.minting.mints<Codec[]>(mintId);
     const mint: Mint = (mintCodec[0] as unknown) as Mint;
     return mint.getField<Balance>('capacity');
   }
 
+  public async getWorkingGroupMintCapacity(module: WorkingGroups): Promise<BN> {
+    const mintId: MintId = await this.api.query[module].mint<MintId>();
+    const mintCodec = await this.api.query.minting.mints<Codec[]>(mintId);
+    const mint: Mint = (mintCodec[0] as unknown) as Mint;
+    return mint.getField<Balance>('capacity');
+  }
+
   public getValidatorCount(): Promise<BN> {
     return this.api.query.staking.validatorCount<u32>();
   }
@@ -747,17 +1014,192 @@ export class ApiWrapper {
     module: WorkingGroups,
     expectFailure: boolean
   ): Promise<void> {
-    await this.sender.signAndSend(this.createAddOpeningTransaction(opening, module), leader, expectFailure);
+    return this.sender.signAndSend(this.createAddOpeningTransaction(opening, module), leader, expectFailure);
   }
 
   public async sudoAddOpening(sudo: KeyringPair, opening: WorkingGroupOpening, module: WorkingGroups): Promise<void> {
-    await this.sender.signAndSend(
+    return this.sender.signAndSend(
       this.api.tx.sudo.sudo(this.createAddOpeningTransaction(opening, module)),
       sudo,
       false
     );
   }
 
+  public async proposeCreateWorkingGroupLeaderOpening(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    opening: WorkingGroupOpening,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0];
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createAddWorkingGroupLeaderOpeningProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        {
+          activate_at: opening.getActivateAt(),
+          commitment: opening.getCommitment(),
+          human_readable_text: opening.getText(),
+          working_group: workingGroup,
+        }
+      ),
+      account,
+      false
+    );
+  }
+
+  public async proposeFillLeaderOpening(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    fillOpeningParameters: FillOpeningParameters
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0];
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createFillWorkingGroupLeaderOpeningProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        fillOpeningParameters.getFillOpeningParameters()
+      ),
+      account,
+      false
+    );
+  }
+
+  public async proposeTerminateLeaderRole(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    leadWorkerId: BN,
+    rationale: string,
+    slash: boolean,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0];
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createTerminateWorkingGroupLeaderRoleProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        {
+          worker_id: leadWorkerId,
+          rationale,
+          slash,
+          working_group: workingGroup,
+        }
+      ),
+      account,
+      false
+    );
+  }
+
+  public async proposeLeaderReward(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    workerId: BN,
+    rewardAmount: BN,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0];
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetWorkingGroupLeaderRewardProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        workerId,
+        rewardAmount,
+        workingGroup
+      ),
+      account,
+      false
+    );
+  }
+
+  public async proposeDecreaseLeaderStake(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    workerId: BN,
+    rewardAmount: BN,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0];
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createDecreaseWorkingGroupLeaderStakeProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        workerId,
+        rewardAmount,
+        workingGroup
+      ),
+      account,
+      false
+    );
+  }
+
+  public async proposeSlashLeaderStake(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    workerId: BN,
+    rewardAmount: BN,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0];
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSlashWorkingGroupLeaderStakeProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        workerId,
+        rewardAmount,
+        workingGroup
+      ),
+      account,
+      false
+    );
+  }
+
+  public async proposeWorkingGroupMintCapacity(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    mintCapacity: BN,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0];
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetWorkingGroupMintCapacityProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        mintCapacity,
+        workingGroup
+      ),
+      account,
+      false
+    );
+  }
+
   private createAddOpeningTransaction(
     opening: WorkingGroupOpening,
     module: WorkingGroups
@@ -1090,7 +1532,7 @@ export class ApiWrapper {
   }
 
   public async getWorkerStakeAmount(workerId: BN, module: WorkingGroups): Promise<BN> {
-    let stakeId: BN = (await this.getWorkerById(workerId, module)).role_stake_profile.unwrap().stake_id;
+    const stakeId: BN = (await this.getWorkerById(workerId, module)).role_stake_profile.unwrap().stake_id;
     return (((await this.getStake(stakeId)).staking_status.value as unknown) as StakedState).staked_amount;
   }
 
@@ -1101,7 +1543,11 @@ export class ApiWrapper {
   }
 
   public async getWorkerRewardAccount(workerId: BN, module: WorkingGroups): Promise<string> {
-    let rewardRelationshipId: BN = (await this.getWorkerById(workerId, module)).reward_relationship.unwrap();
+    const rewardRelationshipId: BN = (await this.getWorkerById(workerId, module)).reward_relationship.unwrap();
     return (await this.getRewardRelationship(rewardRelationshipId)).getField('account').toString();
   }
+
+  public async getLeadWorkerId(module: WorkingGroups): Promise<BN | undefined> {
+    return (await this.api.query[module].currentLead<Option<WorkerId>>()).unwrapOr(undefined);
+  }
 }

+ 1 - 1
yarn.lock

@@ -1828,7 +1828,7 @@
     "@types/istanbul-reports" "^1.1.1"
     "@types/yargs" "^13.0.0"
 
-"@joystream/types@link:types":
+"@joystream/types@link:types", "@nicaea/types@link:types":
   version "0.12.0"
   dependencies:
     "@polkadot/keyring" "^1.7.0-beta.5"