Browse Source

working group tests merged

Gleb Urvanov 4 years ago
parent
commit
09714050f3
54 changed files with 2185 additions and 1482 deletions
  1. 39 0
      .github/workflows/joystream-cli.yml
  2. 6 2
      .github/workflows/pioneer.yml
  3. 1 1
      Cargo.lock
  4. 17 16
      cli/src/Api.ts
  5. 3 8
      cli/src/Types.ts
  6. 3 3
      cli/src/base/WorkingGroupsCommandBase.ts
  7. 1 1
      cli/src/commands/council/info.ts
  8. 2 2
      cli/src/commands/working-groups/overview.ts
  9. 35 25
      pioneer/packages/joy-roles/src/transport.substrate.ts
  10. 1 1
      runtime-modules/common/src/lib.rs
  11. 26 0
      runtime-modules/common/src/origin.rs
  12. 0 5
      runtime-modules/common/src/origin_validator.rs
  13. 7 1
      runtime-modules/hiring/src/hiring/staking_policy.rs
  14. 3 22
      runtime-modules/proposals/codex/src/lib.rs
  15. 1 1
      runtime-modules/proposals/codex/src/tests/mock.rs
  16. 1 1
      runtime-modules/proposals/discussion/src/lib.rs
  17. 1 1
      runtime-modules/proposals/engine/src/lib.rs
  18. 1 1
      runtime-modules/proposals/engine/src/tests/mock/mod.rs
  19. 3 3
      runtime-modules/recurring-reward/src/lib.rs
  20. 1 1
      runtime-modules/service-discovery/src/mock.rs
  21. 1 1
      runtime-modules/storage/src/data_directory.rs
  22. 18 6
      runtime-modules/storage/src/tests/data_object_type_registry.rs
  23. 2 2
      runtime-modules/storage/src/tests/mock.rs
  24. 16 6
      runtime-modules/working-group/src/errors.rs
  25. 293 319
      runtime-modules/working-group/src/lib.rs
  26. 304 185
      runtime-modules/working-group/src/tests/fixtures.rs
  27. 200 0
      runtime-modules/working-group/src/tests/hiring_workflow.rs
  28. 50 3
      runtime-modules/working-group/src/tests/mock.rs
  29. 262 322
      runtime-modules/working-group/src/tests/mod.rs
  30. 67 50
      runtime-modules/working-group/src/types.rs
  31. 1 2
      runtime/Cargo.toml
  32. 141 0
      runtime/src/integration/content_working_group.rs
  33. 2 0
      runtime/src/integration/mod.rs
  34. 2 2
      runtime/src/integration/proposals/council_origin_validator.rs
  35. 2 2
      runtime/src/integration/proposals/membership_origin_validator.rs
  36. 1 1
      runtime/src/integration/storage.rs
  37. 49 0
      runtime/src/integration/working_group.rs
  38. 12 139
      runtime/src/lib.rs
  39. 1 3
      scripts/run-test-chain.sh
  40. 2 2
      tests/network-tests/package.json
  41. 1 1
      tests/network-tests/src/constantinople/tests/proposals/impl/storageRoleParametersProposal.ts
  42. 129 70
      tests/network-tests/src/nicaea/tests/workingGroup/impl/workingGroupModule.ts
  43. 37 18
      tests/network-tests/src/nicaea/tests/workingGroup/manageWorkerAsLeadTest.ts
  44. 32 10
      tests/network-tests/src/nicaea/tests/workingGroup/manageWorkerAsWorkerTest.ts
  45. 39 14
      tests/network-tests/src/nicaea/tests/workingGroup/workerApplicationHappyCaseTest.ts
  46. 34 10
      tests/network-tests/src/nicaea/tests/workingGroup/workerApplicationRejectionCaseTest.ts
  47. 186 112
      tests/network-tests/src/nicaea/utils/apiWrapper.ts
  48. 12 1
      types/.gitignore
  49. 6 0
      types/.npmignore
  50. 8 8
      types/package.json
  51. 16 1
      types/src/content-working-group/index.ts
  52. 89 91
      types/src/working-group/index.ts
  53. 11 6
      types/tsconfig.json
  54. 7 0
      yarn.lock

+ 39 - 0
.github/workflows/joystream-cli.yml

@@ -0,0 +1,39 @@
+name: joystream-cli
+on: [pull_request, push]
+
+jobs:
+  cli_build_ubuntu:
+    name: Ubuntu Build
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: build
+      run: |
+        yarn install --frozen-lockfile
+        yarn madge --circular types/
+        yarn workspace joystream-cli build
+
+  cli_build_osx:
+    name: MacOS Build
+    runs-on: macos-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: build
+      run: |
+        yarn install --frozen-lockfile --network-timeout 120000
+        yarn madge --circular types/
+        yarn workspace joystream-cli build

+ 6 - 2
.github/workflows/pioneer-pr.yml → .github/workflows/pioneer.yml

@@ -17,6 +17,7 @@ jobs:
     - name: build
       run: |
         yarn install --frozen-lockfile
+        yarn madge --circular types/
         yarn workspace pioneer build
 
   pioneer_build_osx:
@@ -33,7 +34,8 @@ jobs:
         node-version: ${{ matrix.node-version }}
     - name: build
       run: |
-        yarn install --frozen-lockfile
+        yarn install --frozen-lockfile --network-timeout 120000
+        yarn madge --circular types/
         yarn workspace pioneer build
 
   pioneer_lint_ubuntu:
@@ -51,6 +53,7 @@ jobs:
     - name: lint
       run: |
         yarn install --frozen-lockfile
+        yarn madge --circular types/
         yarn workspace pioneer lint
 
   pioneer_lint_osx:
@@ -67,5 +70,6 @@ jobs:
         node-version: ${{ matrix.node-version }}
     - name: lint
       run: |
-        yarn install --frozen-lockfile
+        yarn install --frozen-lockfile --network-timeout 120000
+        yarn madge --circular types/
         yarn workspace pioneer lint

+ 1 - 1
Cargo.lock

@@ -1614,7 +1614,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node-runtime"
-version = "6.16.0"
+version = "6.17.0"
 dependencies = [
  "parity-scale-codec",
  "safe-mix",

+ 17 - 16
cli/src/Api.ts

@@ -12,16 +12,15 @@ import {
     AccountSummary,
     CouncilInfoObj, CouncilInfoTuple, createCouncilInfoObj,
     WorkingGroups,
-    GroupLeadWithProfile,
     GroupMember,
 } from './Types';
 import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types';
 import { CLIError } from '@oclif/errors';
 import ExitCodes from './ExitCodes';
-import { Worker, Lead as WorkerLead, WorkerId, WorkerRoleStakeProfile } from '@joystream/types/lib/working-group';
-import { MemberId, Profile } from '@joystream/types/lib/members';
-import { RewardRelationship, RewardRelationshipId } from '@joystream/types/lib/recurring-rewards';
-import { Stake, StakeId } from '@joystream/types/lib/stake';
+import { Worker, WorkerId, RoleStakeProfile } from '@joystream/types/working-group';
+import { MemberId, Profile } from '@joystream/types/members';
+import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards';
+import { Stake, StakeId } from '@joystream/types/stake';
 import { LinkageResult } from '@polkadot/types/codec/Linkage';
 
 export const DEFAULT_API_URI = 'wss://rome-rpc-endpoint.joystream.org:9944/';
@@ -166,21 +165,23 @@ export default class Api {
         return profile.unwrapOr(null);
     }
 
-    async groupLead (group: WorkingGroups): Promise <GroupLeadWithProfile | null> {
-        const optLead = (await this.workingGroupApiQuery(group).currentLead()) as Option<WorkerLead>;
+    async groupLead(group: WorkingGroups): Promise<GroupMember | null> {
+        const optLeadId = (await this.workingGroupApiQuery(group).currentLead()) as Option<WorkerId>;
 
-        if (!optLead.isSome) {
-          return null;
+        if (!optLeadId.isSome) {
+            return null;
         }
 
-        const lead = optLead.unwrap();
-        const profile = await this.memberProfileById(lead.member_id);
+        const leadWorkerId = optLeadId.unwrap();
+        const leadWorker = this.singleLinkageResult<Worker>(
+            await this.workingGroupApiQuery(group).workerById(leadWorkerId) as LinkageResult
+        );
 
-        if (!profile) {
-            throw new Error(`Group lead profile not found! (member id: ${lead.member_id.toNumber()})`);
+        if (!leadWorker.is_active) {
+            return null;
         }
 
-        return { lead, profile };
+        return await this.groupMember(leadWorkerId, leadWorker);
     }
 
     protected async stakeValue (stakeId: StakeId): Promise<Balance> {
@@ -188,7 +189,7 @@ export default class Api {
         return stake.value;
     }
 
-    protected async workerStake (stakeProfile: WorkerRoleStakeProfile): Promise<Balance> {
+    protected async workerStake (stakeProfile: RoleStakeProfile): Promise<Balance> {
         return this.stakeValue(stakeProfile.stake_id);
     }
 
@@ -203,7 +204,7 @@ export default class Api {
         id: WorkerId,
         worker: Worker
       ): Promise<GroupMember> {
-        const roleAccount = worker.role_account;
+        const roleAccount = worker.role_account_id;
         const memberId = worker.member_id;
 
         const profile = await this.memberProfileById(memberId);

+ 3 - 8
cli/src/Types.ts

@@ -1,11 +1,11 @@
 import BN from 'bn.js';
-import { ElectionStage, Seat } from '@joystream/types/lib/council';
+import { ElectionStage, Seat } from '@joystream/types/council';
 import { Option } from '@polkadot/types';
 import { BlockNumber, Balance, AccountId } from '@polkadot/types/interfaces';
 import { DerivedBalances } from '@polkadot/api-derive/types';
 import { KeyringPair } from '@polkadot/keyring/types';
-import { WorkerId, Lead } from '@joystream/types/lib/working-group';
-import { Profile, MemberId } from '@joystream/types/lib/members';
+import { WorkerId } from '@joystream/types/working-group';
+import { Profile, MemberId } from '@joystream/types/members';
 
 // KeyringPair type extended with mandatory "meta.name"
 // It's used for accounts/keys management within CLI.
@@ -75,11 +75,6 @@ export const AvailableGroups: readonly WorkingGroups[] = [
 ] as const;
 
 // Compound working group types
-export type GroupLeadWithProfile = {
-    lead: Lead;
-    profile: Profile;
-}
-
 export type GroupMember = {
     workerId: WorkerId;
     memberId: MemberId;

+ 3 - 3
cli/src/base/WorkingGroupsCommandBase.ts

@@ -1,7 +1,7 @@
 import ExitCodes from '../ExitCodes';
 import AccountsCommandBase from './AccountsCommandBase';
 import { flags } from '@oclif/command';
-import { WorkingGroups, AvailableGroups, NamedKeyringPair, GroupLeadWithProfile, GroupMember } from '../Types';
+import { WorkingGroups, AvailableGroups, NamedKeyringPair, GroupMember } from '../Types';
 import { CLIError } from '@oclif/errors';
 import inquirer from 'inquirer';
 
@@ -25,11 +25,11 @@ export default abstract class WorkingGroupsCommandBase extends AccountsCommandBa
     };
 
     // Use when lead access is required in given command
-    async getRequiredLead(): Promise<GroupLeadWithProfile> {
+    async getRequiredLead(): Promise<GroupMember> {
         let selectedAccount: NamedKeyringPair = await this.getRequiredSelectedAccount();
         let lead = await this.getApi().groupLead(this.group);
 
-        if (!lead || lead.lead.role_account_id.toString() !== selectedAccount.address) {
+        if (!lead || lead.roleAccount.toString() !== selectedAccount.address) {
             this.error('Lead access required for this command!', { exit: ExitCodes.AccessDenied });
         }
 

+ 1 - 1
cli/src/commands/council/info.ts

@@ -1,4 +1,4 @@
-import { ElectionStage } from '@joystream/types/lib/council';
+import { ElectionStage } from '@joystream/types/council';
 import { formatNumber, formatBalance } from '@polkadot/util';
 import { BlockNumber } from '@polkadot/types/interfaces';
 import { CouncilInfoObj, NameValueObj } from '../../Types';

+ 2 - 2
cli/src/commands/working-groups/overview.ts

@@ -16,9 +16,9 @@ export default class WorkingGroupsOverview extends WorkingGroupsCommandBase {
         displayHeader('Group lead');
         if (lead) {
             displayNameValueTable([
-                { name: 'Member id:', value: lead.lead.member_id.toString() },
+                { name: 'Member id:', value: lead.memberId.toString() },
                 { name: 'Member handle:', value: lead.profile.handle.toString() },
-                { name: 'Role account:', value: lead.lead.role_account_id.toString() },
+                { name: 'Role account:', value: lead.roleAccount.toString() },
             ]);
         }
         else {

+ 35 - 25
pioneer/packages/joy-roles/src/transport.substrate.ts

@@ -24,14 +24,13 @@ import {
 } from '@joystream/types/content-working-group';
 
 import {
-  WorkerApplication, WorkerApplicationId,
-  WorkerOpening, WorkerOpeningId,
+  Application as WGApplication,
+  Opening as WGOpening,
   Worker, WorkerId,
-  WorkerRoleStakeProfile,
-  Lead as LeadOf
+  RoleStakeProfile
 } from '@joystream/types/working-group';
 
-import { Application, Opening, OpeningId } from '@joystream/types/hiring';
+import { Application, Opening, OpeningId, ApplicationId } from '@joystream/types/hiring';
 import { Stake, StakeId } from '@joystream/types/stake';
 import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards';
 import { ActorInRole, Profile, MemberId, Role, RoleKeys, ActorId } from '@joystream/types/members';
@@ -72,14 +71,14 @@ type WGApiMethodType =
   | 'workerById';
 type WGApiMethodsMapping = { [key in WGApiMethodType]: string };
 
-type GroupApplication = CuratorApplication | WorkerApplication;
-type GroupApplicationId = CuratorApplicationId | WorkerApplicationId;
-type GroupOpening = CuratorOpening | WorkerOpening;
-type GroupOpeningId = CuratorOpeningId | WorkerOpeningId;
+type GroupApplication = CuratorApplication | WGApplication;
+type GroupApplicationId = CuratorApplicationId | ApplicationId;
+type GroupOpening = CuratorOpening | WGOpening;
+type GroupOpeningId = CuratorOpeningId | OpeningId;
 type GroupWorker = Worker | Curator;
 type GroupWorkerId = CuratorId | WorkerId;
-type GroupWorkerStakeProfile = WorkerRoleStakeProfile | CuratorRoleStakeProfile;
-type GroupLead = Lead | LeadOf;
+type GroupWorkerStakeProfile = RoleStakeProfile | CuratorRoleStakeProfile;
+type GroupLead = Lead | Worker;
 type GroupLeadWithMemberId = {
   lead: GroupLead;
   memberId: MemberId;
@@ -99,15 +98,15 @@ const workingGroupsApiMapping: WGApiMapping = {
   [WorkingGroups.StorageProviders]: {
     module: 'storageWorkingGroup',
     methods: {
-      nextOpeningId: 'nextWorkerOpeningId',
-      openingById: 'workerOpeningById',
-      nextApplicationId: 'nextWorkerApplicationId',
-      applicationById: 'workerApplicationById',
+      nextOpeningId: 'nextOpeningId',
+      openingById: 'openingById',
+      nextApplicationId: 'nextApplicationId',
+      applicationById: 'applicationById',
       nextWorkerId: 'nextWorkerId',
       workerById: 'workerById'
     },
-    openingType: WorkerOpening,
-    applicationType: WorkerApplication,
+    openingType: WGOpening,
+    applicationType: WGApplication,
     workerType: Worker
   },
   [WorkingGroups.ContentCurators]: {
@@ -210,7 +209,7 @@ export class Transport extends TransportBase implements ITransport {
     id: GroupWorkerId,
     worker: GroupWorker
   ): Promise<GroupMember> {
-    const roleAccount = worker.role_account;
+    const roleAccount = worker.role_account_id;
     const memberId = group === WorkingGroups.ContentCurators
       ? await this.memberIdFromCuratorId(id)
       : (worker as Worker).member_id;
@@ -255,7 +254,7 @@ export class Transport extends TransportBase implements ITransport {
     );
 
     for (let i = 0; i < groupOpenings.linked_values.length; i++) {
-      const opening = await this.opening(groupOpenings.linked_values[i].opening_id.toNumber());
+      const opening = await this.opening(groupOpenings.linked_values[i].hiring_opening_id.toNumber());
       if (opening.is_active) {
         return true;
       }
@@ -291,15 +290,26 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   protected async currentStorageLead (): Promise <GroupLeadWithMemberId | null> {
-    const optLead = (await this.cachedApi.query.storageWorkingGroup.currentLead()) as Option<LeadOf>;
+    const optLeadId = (await this.cachedApi.query.storageWorkingGroup.currentLead()) as Option<WorkerId>;
 
-    if (!optLead.isSome) {
+    if (!optLeadId.isSome) {
+      return null;
+    }
+
+    const leadWorkerId = optLeadId.unwrap();
+    const leadWorkerLink = new SingleLinkedMapEntry(
+      Worker,
+      await this.cachedApi.query.storageWorkingGroup.workerById(leadWorkerId)
+    );
+    const leadWorker = leadWorkerLink.value;
+
+    if (!leadWorker.is_active) {
       return null;
     }
 
     return {
-      lead: optLead.unwrap(),
-      memberId: optLead.unwrap().member_id
+      lead: leadWorker,
+      memberId: leadWorker.member_id
     };
   }
 
@@ -415,7 +425,7 @@ export class Transport extends TransportBase implements ITransport {
         await this.cachedApiMethodByGroup(group, 'applicationById')(i)
       );
 
-      if (cApplication.value.worker_opening_id.toNumber() !== groupOpeningId) {
+      if (cApplication.value.opening_id.toNumber() !== groupOpeningId) {
         continue;
       }
 
@@ -454,7 +464,7 @@ export class Transport extends TransportBase implements ITransport {
     );
 
     const opening = await this.opening(
-      groupOpening.value.opening_id.toNumber()
+      groupOpening.value.hiring_opening_id.toNumber()
     );
 
     const applications = await this.groupOpeningApplications(group, id);

+ 1 - 1
runtime-modules/common/src/lib.rs

@@ -3,7 +3,7 @@
 
 pub mod constraints;
 pub mod currency;
-pub mod origin_validator;
+pub mod origin;
 
 use codec::{Decode, Encode};
 #[cfg(feature = "std")]

+ 26 - 0
runtime-modules/common/src/origin.rs

@@ -0,0 +1,26 @@
+use system::RawOrigin;
+
+/// Abstract validator for the origin(account_id) and actor_id (eg.: thread author id).
+pub trait ActorOriginValidator<Origin, ActorId, AccountId> {
+    /// Check for valid combination of origin and actor_id.
+    fn ensure_actor_origin(origin: Origin, actor_id: ActorId) -> Result<AccountId, &'static str>;
+}
+
+// Multiplies the T::Origin.
+// In our current substrate version system::Origin doesn't support clone(),
+// but it will be supported in latest up-to-date substrate version.
+// TODO: delete when T::Origin will support the clone()
+pub fn double_origin<T: system::Trait>(origin: T::Origin) -> (T::Origin, T::Origin) {
+    let coerced_origin = origin.into().ok().unwrap_or(RawOrigin::None);
+
+    let (cloned_origin1, cloned_origin2) = match coerced_origin {
+        RawOrigin::None => (RawOrigin::None, RawOrigin::None),
+        RawOrigin::Root => (RawOrigin::Root, RawOrigin::Root),
+        RawOrigin::Signed(account_id) => (
+            RawOrigin::Signed(account_id.clone()),
+            RawOrigin::Signed(account_id),
+        ),
+    };
+
+    (cloned_origin1.into(), cloned_origin2.into())
+}

+ 0 - 5
runtime-modules/common/src/origin_validator.rs

@@ -1,5 +0,0 @@
-/// Abstract validator for the origin(account_id) and actor_id (eg.: thread author id).
-pub trait ActorOriginValidator<Origin, ActorId, AccountId> {
-    /// Check for valid combination of origin and actor_id.
-    fn ensure_actor_origin(origin: Origin, actor_id: ActorId) -> Result<AccountId, &'static str>;
-}

+ 7 - 1
runtime-modules/hiring/src/hiring/staking_policy.rs

@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
 
 /// Policy for staking
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Debug, Eq, PartialEq, Clone)]
+#[derive(Encode, Decode, Debug, Eq, PartialEq, Clone, Default)]
 pub struct StakingPolicy<Balance, BlockNumber> {
     /// Staking amount
     pub amount: Balance,
@@ -77,3 +77,9 @@ pub enum StakingAmountLimitMode {
     /// Stake should be equal to provided value
     Exact,
 }
+
+impl Default for StakingAmountLimitMode {
+    fn default() -> Self {
+        StakingAmountLimitMode::Exact
+    }
+}

+ 3 - 22
runtime-modules/proposals/codex/src/lib.rs

@@ -54,7 +54,7 @@ mod proposal_types;
 #[cfg(test)]
 mod tests;
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use governance::election_params::ElectionParameters;
 use proposal_engine::ProposalParameters;
 use rstd::clone::Clone;
@@ -65,7 +65,7 @@ use sr_primitives::traits::Zero;
 use srml_support::dispatch::DispatchResult;
 use srml_support::traits::{Currency, Get};
 use srml_support::{decl_error, decl_module, decl_storage, ensure, print};
-use system::{ensure_root, RawOrigin};
+use system::ensure_root;
 
 pub use crate::proposal_types::ProposalsConfigParameters;
 pub use proposal_types::{ProposalDetails, ProposalDetailsOf, ProposalEncoder};
@@ -577,7 +577,7 @@ decl_module! {
             origin,
             wasm: Vec<u8>,
         ) {
-            let (cloned_origin1, cloned_origin2) =  Self::double_origin(origin);
+            let (cloned_origin1, cloned_origin2) = common::origin::double_origin::<T>(origin);
             ensure_root(cloned_origin1)?;
 
             print("Runtime upgrade proposal execution started.");
@@ -590,25 +590,6 @@ decl_module! {
 }
 
 impl<T: Trait> Module<T> {
-    // Multiplies the T::Origin.
-    // In our current substrate version system::Origin doesn't support clone(),
-    // but it will be supported in latest up-to-date substrate version.
-    // TODO: delete when T::Origin will support the clone()
-    fn double_origin(origin: T::Origin) -> (T::Origin, T::Origin) {
-        let coerced_origin = origin.into().ok().unwrap_or(RawOrigin::None);
-
-        let (cloned_origin1, cloned_origin2) = match coerced_origin {
-            RawOrigin::None => (RawOrigin::None, RawOrigin::None),
-            RawOrigin::Root => (RawOrigin::Root, RawOrigin::Root),
-            RawOrigin::Signed(account_id) => (
-                RawOrigin::Signed(account_id.clone()),
-                RawOrigin::Signed(account_id),
-            ),
-        };
-
-        (cloned_origin1.into(), cloned_origin2.into())
-    }
-
     // Generic template proposal builder
     fn create_proposal(
         origin: T::Origin,

+ 1 - 1
runtime-modules/proposals/codex/src/tests/mock.rs

@@ -123,7 +123,7 @@ impl governance::council::Trait for Test {
     type CouncilTermEnded = ();
 }
 
-impl common::origin_validator::ActorOriginValidator<Origin, u64, u64> for () {
+impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
     fn ensure_actor_origin(origin: Origin, _: u64) -> Result<u64, &'static str> {
         let account_id = system::ensure_signed(origin)?;
 

+ 1 - 1
runtime-modules/proposals/discussion/src/lib.rs

@@ -56,7 +56,7 @@ use srml_support::{decl_error, decl_event, decl_module, decl_storage, ensure, Pa
 use srml_support::traits::Get;
 use types::{DiscussionPost, DiscussionThread, ThreadCounter};
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use srml_support::dispatch::DispatchResult;
 
 type MemberId<T> = <T as membership::members::Trait>::MemberId;

+ 1 - 1
runtime-modules/proposals/engine/src/lib.rs

@@ -135,7 +135,7 @@ use srml_support::{
 use system::{ensure_root, RawOrigin};
 
 use crate::types::ApprovedProposalData;
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use srml_support::dispatch::Dispatchable;
 
 type MemberId<T> = <T as membership::members::Trait>::MemberId;

+ 1 - 1
runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -124,7 +124,7 @@ impl Default for proposals::Call<Test> {
     }
 }
 
-impl common::origin_validator::ActorOriginValidator<Origin, u64, u64> for () {
+impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
     fn ensure_actor_origin(origin: Origin, _account_id: u64) -> Result<u64, &'static str> {
         let signed_account_id = system::ensure_signed(origin)?;
 

+ 3 - 3
runtime-modules/recurring-reward/src/lib.rs

@@ -94,10 +94,10 @@ pub struct RewardRelationship<AccountId, Balance, BlockNumber, MintId, Recipient
     mint_id: MintId,
 
     /// Destination account for reward
-    account: AccountId,
+    pub account: AccountId,
 
     /// The payout amount at the next payout
-    amount_per_payout: Balance,
+    pub amount_per_payout: Balance,
 
     /// When set, identifies block when next payout should be processed,
     /// otherwise there is no pending payout
@@ -146,7 +146,7 @@ decl_storage! {
 
         RecipientsCreated get(recipients_created): T::RecipientId;
 
-        RewardRelationships get(reward_relationships): linked_map T::RewardRelationshipId => RewardRelationship<T::AccountId, BalanceOf<T>, T::BlockNumber, T::MintId, T::RecipientId>;
+        pub RewardRelationships get(reward_relationships): linked_map T::RewardRelationshipId => RewardRelationship<T::AccountId, BalanceOf<T>, T::BlockNumber, T::MintId, T::RecipientId>;
 
         RewardRelationshipsCreated get(reward_relationships_created): T::RewardRelationshipId;
     }

+ 1 - 1
runtime-modules/service-discovery/src/mock.rs

@@ -158,7 +158,7 @@ pub(crate) fn hire_storage_provider() -> (u64, u64) {
 
     let storage_provider = working_group::Worker {
         member_id: 1,
-        role_account: role_account_id,
+        role_account_id,
         reward_relationship: None,
         role_stake_profile: None,
     };

+ 1 - 1
runtime-modules/storage/src/data_directory.rs

@@ -27,7 +27,7 @@ use sr_primitives::traits::{MaybeSerialize, Member};
 use srml_support::{decl_error, decl_event, decl_module, decl_storage, ensure, Parameter};
 use system::{self, ensure_root};
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 pub(crate) use common::BlockAndTime;
 
 use crate::data_object_type_registry;

+ 18 - 6
runtime-modules/storage/src/tests/data_object_type_registry.rs

@@ -1,21 +1,33 @@
 #![cfg(test)]
 
 use super::mock::*;
-use crate::StorageWorkingGroup;
+use srml_support::{StorageLinkedMap, StorageValue};
 use system::{self, EventRecord, Phase, RawOrigin};
 
 const DEFAULT_LEADER_ACCOUNT_ID: u64 = 1;
 const DEFAULT_LEADER_MEMBER_ID: u64 = 1;
+const DEFAULT_LEADER_WORKER_ID: u32 = 1;
 
 struct SetLeadFixture;
 impl SetLeadFixture {
     fn set_default_lead() {
-        let set_lead_result = <StorageWorkingGroup<Test>>::set_lead(
-            RawOrigin::Root.into(),
-            DEFAULT_LEADER_MEMBER_ID,
-            DEFAULT_LEADER_ACCOUNT_ID,
+        let worker = working_group::Worker {
+            member_id: DEFAULT_LEADER_MEMBER_ID,
+            role_account_id: DEFAULT_LEADER_ACCOUNT_ID,
+            reward_relationship: None,
+            role_stake_profile: None,
+        };
+
+        // Create the worker.
+        <working_group::WorkerById<Test, StorageWorkingGroupInstance>>::insert(
+            DEFAULT_LEADER_WORKER_ID,
+            worker,
+        );
+
+        // Update current lead.
+        <working_group::CurrentLead<Test, StorageWorkingGroupInstance>>::put(
+            DEFAULT_LEADER_WORKER_ID,
         );
-        assert!(set_lead_result.is_ok());
     }
 }
 

+ 2 - 2
runtime-modules/storage/src/tests/mock.rs

@@ -169,7 +169,7 @@ impl crate::data_directory::StorageProviderHelper<Test> for () {
     }
 }
 
-impl common::origin_validator::ActorOriginValidator<Origin, u64, u64> for () {
+impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
     fn ensure_actor_origin(origin: Origin, _account_id: u64) -> Result<u64, &'static str> {
         let signed_account_id = system::ensure_signed(origin)?;
 
@@ -304,7 +304,7 @@ pub(crate) fn hire_storage_provider() -> (u64, u32) {
 
     let storage_provider = working_group::Worker {
         member_id: 1,
-        role_account: role_account_id,
+        role_account_id,
         reward_relationship: None,
         role_stake_profile: None,
     };

+ 16 - 6
runtime-modules/working-group/src/errors.rs

@@ -14,6 +14,12 @@ decl_error! {
         /// Current lead is not set.
         CurrentLeadNotSet,
 
+        /// There is leader already, cannot hire another one.
+        CannotHireLeaderWhenLeaderExists,
+
+        /// Cannot fill opening with multiple applications.
+        CannotHireMultipleLeaders,
+
         /// Not a lead account.
         IsNotLeadAccount,
 
@@ -23,8 +29,8 @@ decl_error! {
         /// Opening text too long.
         OpeningTextTooLong,
 
-        /// Worker opening does not exist.
-        WorkerOpeningDoesNotExist,
+        /// Opening does not exist.
+        OpeningDoesNotExist,
 
         /// Insufficient balance to apply.
         InsufficientBalanceToApply,
@@ -63,10 +69,10 @@ decl_error! {
         SuccessfulWorkerApplicationDoesNotExist,
 
         /// Reward policy has invalid next payment block number.
-        FillWorkerOpeningInvalidNextPaymentBlock,
+        FillOpeningInvalidNextPaymentBlock,
 
         /// Working group mint does not exist.
-        FillWorkerOpeningMintDoesNotExist,
+        FillOpeningMintDoesNotExist,
 
         ///Relationship must exist.
         RelationshipMustExist,
@@ -235,6 +241,9 @@ decl_error! {
 
         /// Require root origin in extrinsics.
         RequireRootOrigin,
+
+        /// Require signed origin in extrinsics.
+        RequireSignedOrigin,
     }
 }
 
@@ -243,6 +252,7 @@ impl From<system::Error> for Error {
         match error {
             system::Error::Other(msg) => Error::Other(msg),
             system::Error::RequireRootOrigin => Error::RequireRootOrigin,
+            system::Error::RequireSignedOrigin => Error::RequireSignedOrigin,
             _ => Error::Other(error.into()),
         }
     }
@@ -324,10 +334,10 @@ impl<T: hiring::Trait> rstd::convert::From<WrappedError<hiring::FillOpeningError
             ) => match stake_purpose {
                 hiring::StakePurpose::Application => match outcome_in_filled_opening {
                     hiring::ApplicationOutcomeInFilledOpening::Success => {
-                        Error::FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort
+                        Error::FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort
                     }
                     hiring::ApplicationOutcomeInFilledOpening::Failure => {
-                        Error::FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort
+                        Error::FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort
                     }
                 },
                 hiring::StakePurpose::Role => match outcome_in_filled_opening {

File diff suppressed because it is too large
+ 293 - 319
runtime-modules/working-group/src/lib.rs


+ 304 - 185
runtime-modules/working-group/src/tests/fixtures.rs

@@ -1,9 +1,10 @@
 use super::mock::{
     Balances, Membership, System, Test, TestEvent, TestWorkingGroup, TestWorkingGroupInstance,
 };
+use crate::tests::fill_worker_position;
 use crate::types::{
-    OpeningPolicyCommitment, RewardPolicy, Worker, WorkerApplication, WorkerOpening,
-    WorkerRoleStakeProfile,
+    Application, Opening, OpeningPolicyCommitment, OpeningType, RewardPolicy, RoleStakeProfile,
+    Worker,
 };
 use crate::Error;
 use crate::RawEvent;
@@ -22,7 +23,7 @@ pub struct IncreaseWorkerStakeFixture {
 impl IncreaseWorkerStakeFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
         let account_id = 1;
-        IncreaseWorkerStakeFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             worker_id,
             balance: 10,
@@ -30,18 +31,18 @@ impl IncreaseWorkerStakeFixture {
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        IncreaseWorkerStakeFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_balance(self, balance: u64) -> Self {
-        IncreaseWorkerStakeFixture { balance, ..self }
+        Self { balance, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
         let stake_id = 0;
         let old_stake = <stake::Module<Test>>::stakes(stake_id);
         let old_balance = Balances::free_balance(&self.account_id);
-        let actual_result = TestWorkingGroup::increase_worker_stake(
+        let actual_result = TestWorkingGroup::increase_stake(
             self.origin.clone().into(),
             self.worker_id,
             self.balance,
@@ -71,11 +72,12 @@ pub struct TerminateWorkerRoleFixture {
     origin: RawOrigin<u64>,
     text: Vec<u8>,
     constraint: InputValidationLengthConstraint,
+    slash_stake: bool,
 }
 
 impl TerminateWorkerRoleFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
-        TerminateWorkerRoleFixture {
+        Self {
             worker_id,
             origin: RawOrigin::Signed(1),
             text: b"rationale_text".to_vec(),
@@ -83,23 +85,32 @@ impl TerminateWorkerRoleFixture {
                 min: 1,
                 max_min_diff: 20,
             },
+            slash_stake: false,
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        TerminateWorkerRoleFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_text(self, text: Vec<u8>) -> Self {
-        TerminateWorkerRoleFixture { text, ..self }
+        Self { text, ..self }
+    }
+
+    pub fn with_slashing(self) -> Self {
+        Self {
+            slash_stake: true,
+            ..self
+        }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
         <crate::WorkerExitRationaleText<TestWorkingGroupInstance>>::put(self.constraint.clone());
 
-        let actual_result = TestWorkingGroup::terminate_worker_role(
+        let actual_result = TestWorkingGroup::terminate_role(
             self.origin.clone().into(),
             self.worker_id,
             self.text.clone(),
+            self.slash_stake,
         );
         assert_eq!(actual_result, expected_result);
 
@@ -120,18 +131,18 @@ pub(crate) struct LeaveWorkerRoleFixture {
 
 impl LeaveWorkerRoleFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
-        LeaveWorkerRoleFixture {
+        Self {
             worker_id,
             origin: RawOrigin::Signed(1),
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        LeaveWorkerRoleFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
         let rationale_text = b"rationale_text".to_vec();
-        let actual_result = TestWorkingGroup::leave_worker_role(
+        let actual_result = TestWorkingGroup::leave_role(
             self.origin.clone().into(),
             self.worker_id,
             rationale_text.clone(),
@@ -144,33 +155,80 @@ impl LeaveWorkerRoleFixture {
     }
 }
 
+pub struct UpdateWorkerRewardAmountFixture {
+    worker_id: u64,
+    amount: u64,
+    origin: RawOrigin<u64>,
+}
+
+impl UpdateWorkerRewardAmountFixture {
+    pub fn default_for_worker_id(worker_id: u64) -> Self {
+        let lead_account_id = get_current_lead_account_id();
+
+        Self {
+            worker_id,
+            amount: 120,
+            origin: RawOrigin::Signed(lead_account_id),
+        }
+    }
+    pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
+        let actual_result = TestWorkingGroup::update_reward_amount(
+            self.origin.clone().into(),
+            self.worker_id,
+            self.amount,
+        );
+
+        assert_eq!(actual_result.clone(), expected_result);
+
+        if actual_result.is_ok() {
+            let worker = TestWorkingGroup::worker_by_id(self.worker_id);
+            let relationship_id = worker.reward_relationship.unwrap();
+
+            let relationship = recurringrewards::RewardRelationships::<Test>::get(relationship_id);
+
+            assert_eq!(relationship.amount_per_payout, self.amount);
+        }
+    }
+}
 pub struct UpdateWorkerRewardAccountFixture {
     worker_id: u64,
-    new_role_account_id: u64,
+    new_reward_account_id: u64,
     origin: RawOrigin<u64>,
 }
 
 impl UpdateWorkerRewardAccountFixture {
-    pub fn default_with_ids(worker_id: u64, new_role_account_id: u64) -> Self {
-        UpdateWorkerRewardAccountFixture {
+    pub fn default_with_ids(worker_id: u64, new_reward_account_id: u64) -> Self {
+        Self {
             worker_id,
-            new_role_account_id,
+            new_reward_account_id,
             origin: RawOrigin::Signed(1),
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        UpdateWorkerRewardAccountFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
-        assert_eq!(
-            TestWorkingGroup::update_worker_reward_account(
-                self.origin.clone().into(),
-                self.worker_id,
-                self.new_role_account_id
-            ),
-            expected_result
+        let actual_result = TestWorkingGroup::update_reward_account(
+            self.origin.clone().into(),
+            self.worker_id,
+            self.new_reward_account_id,
         );
+
+        assert_eq!(actual_result.clone(), expected_result);
+
+        if actual_result.is_ok() {
+            let worker = TestWorkingGroup::worker_by_id(self.worker_id);
+            let relationship_id = worker.reward_relationship.unwrap();
+
+            let relationship = recurringrewards::RewardRelationships::<Test>::get(relationship_id);
+
+            assert_eq!(relationship.account, self.new_reward_account_id);
+        }
     }
 }
 
@@ -182,18 +240,18 @@ pub struct UpdateWorkerRoleAccountFixture {
 
 impl UpdateWorkerRoleAccountFixture {
     pub fn default_with_ids(worker_id: u64, new_role_account_id: u64) -> Self {
-        UpdateWorkerRoleAccountFixture {
+        Self {
             worker_id,
             new_role_account_id,
             origin: RawOrigin::Signed(1),
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        UpdateWorkerRoleAccountFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
-        let actual_result = TestWorkingGroup::update_worker_role_account(
+        let actual_result = TestWorkingGroup::update_role_account(
             self.origin.clone().into(),
             self.worker_id,
             self.new_role_account_id,
@@ -203,22 +261,11 @@ impl UpdateWorkerRoleAccountFixture {
         if actual_result.is_ok() {
             let worker = TestWorkingGroup::worker_by_id(self.worker_id);
 
-            assert_eq!(worker.role_account, self.new_role_account_id);
+            assert_eq!(worker.role_account_id, self.new_role_account_id);
         }
     }
 }
 
-pub struct UnsetLeadFixture;
-impl UnsetLeadFixture {
-    pub fn unset_lead() {
-        assert_eq!(TestWorkingGroup::unset_lead(RawOrigin::Root.into()), Ok(()));
-    }
-
-    pub fn call_and_assert(origin: RawOrigin<u64>, expected_result: Result<(), Error>) {
-        assert_eq!(TestWorkingGroup::unset_lead(origin.into()), expected_result);
-    }
-}
-
 pub fn set_mint_id(mint_id: u64) {
     <crate::Mint<Test, TestWorkingGroupInstance>>::put(mint_id);
 }
@@ -230,8 +277,8 @@ pub fn create_mint() -> u64 {
 pub struct FillWorkerOpeningFixture {
     origin: RawOrigin<u64>,
     opening_id: u64,
-    successful_worker_application_ids: BTreeSet<u64>,
-    role_account: u64,
+    successful_application_ids: BTreeSet<u64>,
+    role_account_id: u64,
     reward_policy: Option<RewardPolicy<u64, u64>>,
 }
 
@@ -239,41 +286,48 @@ impl FillWorkerOpeningFixture {
     pub fn default_for_ids(opening_id: u64, application_ids: Vec<u64>) -> Self {
         let application_ids: BTreeSet<u64> = application_ids.iter().map(|x| *x).collect();
 
-        FillWorkerOpeningFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             opening_id,
-            successful_worker_application_ids: application_ids,
-            role_account: 1,
+            successful_application_ids: application_ids,
+            role_account_id: 1,
             reward_policy: None,
         }
     }
 
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        FillWorkerOpeningFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_reward_policy(self, reward_policy: RewardPolicy<u64, u64>) -> Self {
-        FillWorkerOpeningFixture {
+        Self {
             reward_policy: Some(reward_policy),
             ..self
         }
     }
 
-    pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
+    pub fn call(&self) -> Result<u64, Error> {
         let saved_worker_next_id = TestWorkingGroup::next_worker_id();
-        let actual_result = TestWorkingGroup::fill_worker_opening(
+        TestWorkingGroup::fill_opening(
             self.origin.clone().into(),
             self.opening_id,
-            self.successful_worker_application_ids.clone(),
+            self.successful_application_ids.clone(),
             self.reward_policy.clone(),
-        );
+        )?;
+
+        Ok(saved_worker_next_id)
+    }
+
+    pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
+        let saved_worker_next_id = TestWorkingGroup::next_worker_id();
+        let actual_result = self.call().map(|_| ());
         assert_eq!(actual_result.clone(), expected_result);
 
         if actual_result.is_ok() {
             assert_eq!(TestWorkingGroup::next_worker_id(), saved_worker_next_id + 1);
             let worker_id = saved_worker_next_id;
 
-            let opening = TestWorkingGroup::worker_opening_by_id(self.opening_id);
+            let opening = TestWorkingGroup::opening_by_id(self.opening_id);
 
             let role_stake_profile = if opening
                 .policy_commitment
@@ -282,14 +336,12 @@ impl FillWorkerOpeningFixture {
                 || opening.policy_commitment.role_staking_policy.is_some()
             {
                 let stake_id = 0;
-                Some(WorkerRoleStakeProfile::new(
+                Some(RoleStakeProfile::new(
                     &stake_id,
                     &opening
                         .policy_commitment
-                        .terminate_worker_role_stake_unstaking_period,
-                    &opening
-                        .policy_commitment
-                        .exit_worker_role_stake_unstaking_period,
+                        .terminate_role_stake_unstaking_period,
+                    &opening.policy_commitment.exit_role_stake_unstaking_period,
                 ))
             } else {
                 None
@@ -298,7 +350,7 @@ impl FillWorkerOpeningFixture {
 
             let expected_worker = Worker {
                 member_id: 1,
-                role_account: self.role_account,
+                role_account_id: self.role_account_id,
                 reward_relationship,
                 role_stake_profile,
             };
@@ -319,19 +371,17 @@ pub struct BeginReviewWorkerApplicationsFixture {
 
 impl BeginReviewWorkerApplicationsFixture {
     pub fn default_for_opening_id(opening_id: u64) -> Self {
-        BeginReviewWorkerApplicationsFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             opening_id,
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        BeginReviewWorkerApplicationsFixture { origin, ..self }
+        Self { origin, ..self }
     }
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
-        let actual_result = TestWorkingGroup::begin_worker_applicant_review(
-            self.origin.clone().into(),
-            self.opening_id,
-        );
+        let actual_result =
+            TestWorkingGroup::begin_applicant_review(self.origin.clone().into(), self.opening_id);
         assert_eq!(actual_result, expected_result);
     }
 }
@@ -343,22 +393,22 @@ pub struct TerminateApplicationFixture {
 
 impl TerminateApplicationFixture {
     pub fn with_signer(self, account_id: u64) -> Self {
-        TerminateApplicationFixture {
+        Self {
             origin: RawOrigin::Signed(account_id),
             ..self
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        TerminateApplicationFixture { origin, ..self }
+        Self { origin, ..self }
     }
     pub fn default_for_application_id(application_id: u64) -> Self {
-        TerminateApplicationFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             worker_application_id: application_id,
         }
     }
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
-        let actual_result = TestWorkingGroup::terminate_worker_application(
+        let actual_result = TestWorkingGroup::terminate_application(
             self.origin.clone().into(),
             self.worker_application_id,
         );
@@ -372,22 +422,22 @@ pub struct WithdrawApplicationFixture {
 
 impl WithdrawApplicationFixture {
     pub fn with_signer(self, account_id: u64) -> Self {
-        WithdrawApplicationFixture {
+        Self {
             origin: RawOrigin::Signed(account_id),
             ..self
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        WithdrawApplicationFixture { origin, ..self }
+        Self { origin, ..self }
     }
     pub fn default_for_application_id(application_id: u64) -> Self {
-        WithdrawApplicationFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             worker_application_id: application_id,
         }
     }
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
-        let actual_result = TestWorkingGroup::withdraw_worker_application(
+        let actual_result = TestWorkingGroup::withdraw_application(
             self.origin.clone().into(),
             self.worker_application_id,
         );
@@ -400,6 +450,10 @@ pub fn increase_total_balance_issuance_using_account_id(account_id: u64, balance
         <Balances as srml_support::traits::Currency<u64>>::deposit_creating(&account_id, balance);
 }
 
+pub fn get_balance(account_id: u64) -> u64 {
+    <super::mock::Balances as srml_support::traits::Currency<u64>>::total_balance(&account_id)
+}
+
 pub fn setup_members(count: u8) {
     let authority_account_id = 1;
     Membership::set_screening_authority(RawOrigin::Root.into(), authority_account_id).unwrap();
@@ -424,7 +478,7 @@ pub struct ApplyOnWorkerOpeningFixture {
     origin: RawOrigin<u64>,
     member_id: u64,
     worker_opening_id: u64,
-    role_account: u64,
+    role_account_id: u64,
     opt_role_stake_balance: Option<u64>,
     opt_application_stake_balance: Option<u64>,
     human_readable_text: Vec<u8>,
@@ -432,73 +486,86 @@ pub struct ApplyOnWorkerOpeningFixture {
 
 impl ApplyOnWorkerOpeningFixture {
     pub fn with_text(self, text: Vec<u8>) -> Self {
-        ApplyOnWorkerOpeningFixture {
+        Self {
             human_readable_text: text,
             ..self
         }
     }
 
-    pub fn with_role_stake(self, stake: u64) -> Self {
-        ApplyOnWorkerOpeningFixture {
-            opt_role_stake_balance: Some(stake),
+    pub fn with_origin(self, origin: RawOrigin<u64>, member_id: u64) -> Self {
+        Self {
+            origin,
+            member_id,
+            ..self
+        }
+    }
+
+    pub fn with_role_stake(self, stake: Option<u64>) -> Self {
+        Self {
+            opt_role_stake_balance: stake,
             ..self
         }
     }
 
     pub fn with_application_stake(self, stake: u64) -> Self {
-        ApplyOnWorkerOpeningFixture {
+        Self {
             opt_application_stake_balance: Some(stake),
             ..self
         }
     }
 
     pub fn default_for_opening_id(opening_id: u64) -> Self {
-        ApplyOnWorkerOpeningFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             member_id: 1,
             worker_opening_id: opening_id,
-            role_account: 1,
+            role_account_id: 1,
             opt_role_stake_balance: None,
             opt_application_stake_balance: None,
             human_readable_text: b"human_text".to_vec(),
         }
     }
 
-    pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
-        let saved_application_next_id = TestWorkingGroup::next_worker_application_id();
-        let actual_result = TestWorkingGroup::apply_on_worker_opening(
+    pub fn call(&self) -> Result<u64, Error> {
+        let saved_application_next_id = TestWorkingGroup::next_application_id();
+        TestWorkingGroup::apply_on_opening(
             self.origin.clone().into(),
             self.member_id,
             self.worker_opening_id,
-            self.role_account,
+            self.role_account_id,
             self.opt_role_stake_balance,
             self.opt_application_stake_balance,
             self.human_readable_text.clone(),
-        );
+        )?;
+
+        Ok(saved_application_next_id)
+    }
+    pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
+        let saved_application_next_id = TestWorkingGroup::next_application_id();
+
+        let actual_result = self.call().map(|_| ());
         assert_eq!(actual_result.clone(), expected_result);
 
         if actual_result.is_ok() {
             assert_eq!(
-                TestWorkingGroup::next_worker_application_id(),
+                TestWorkingGroup::next_application_id(),
                 saved_application_next_id + 1
             );
             let application_id = saved_application_next_id;
 
-            let actual_application = TestWorkingGroup::worker_application_by_id(application_id);
+            let actual_application = TestWorkingGroup::application_by_id(application_id);
 
-            let expected_application = WorkerApplication {
-                role_account: self.role_account,
-                worker_opening_id: self.worker_opening_id,
+            let expected_application = Application {
+                role_account_id: self.role_account_id,
+                opening_id: self.worker_opening_id,
                 member_id: self.member_id,
-                application_id,
+                hiring_application_id: application_id,
             };
 
             assert_eq!(actual_application, expected_application);
 
-            let current_opening = TestWorkingGroup::worker_opening_by_id(self.worker_opening_id);
-            assert!(current_opening
-                .worker_applications
-                .contains(&application_id));
+            let current_opening = TestWorkingGroup::opening_by_id(self.worker_opening_id);
+            assert!(current_opening.applications.contains(&application_id));
         }
 
         saved_application_next_id
@@ -512,57 +579,112 @@ pub struct AcceptWorkerApplicationsFixture {
 
 impl AcceptWorkerApplicationsFixture {
     pub fn default_for_opening_id(opening_id: u64) -> Self {
-        AcceptWorkerApplicationsFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             opening_id,
         }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
-        let actual_result = TestWorkingGroup::accept_worker_applications(
-            self.origin.clone().into(),
-            self.opening_id,
-        );
+        let actual_result =
+            TestWorkingGroup::accept_applications(self.origin.clone().into(), self.opening_id);
         assert_eq!(actual_result, expected_result);
     }
 }
 
-pub struct SetLeadFixture;
+pub struct SetLeadFixture {
+    pub member_id: u64,
+    pub role_account_id: u64,
+    pub worker_id: u64,
+}
+impl Default for SetLeadFixture {
+    fn default() -> Self {
+        SetLeadFixture {
+            member_id: 1,
+            role_account_id: 1,
+            worker_id: 1,
+        }
+    }
+}
+
 impl SetLeadFixture {
-    pub fn set_lead(lead_account_id: u64) {
-        assert_eq!(
-            TestWorkingGroup::set_lead(RawOrigin::Root.into(), 1, lead_account_id),
-            Ok(())
-        );
+    pub fn unset_lead() {
+        TestWorkingGroup::unset_lead();
     }
 
-    pub fn call_and_assert(
-        origin: RawOrigin<u64>,
-        member_id: u64,
-        account_id: u64,
-        expected_result: Result<(), Error>,
-    ) {
-        assert_eq!(
-            TestWorkingGroup::set_lead(origin.into(), member_id, account_id),
-            expected_result
-        );
+    pub fn set_lead(self) {
+        TestWorkingGroup::set_lead(self.worker_id);
+    }
+    pub fn set_lead_with_ids(member_id: u64, role_account_id: u64, worker_id: u64) {
+        Self {
+            member_id,
+            role_account_id,
+            worker_id,
+        }
+        .set_lead();
     }
 }
 
+pub struct HireLeadFixture {
+    setup_environment: bool,
+    stake: Option<u64>,
+    reward_policy: Option<RewardPolicy<u64, u64>>,
+}
+
+impl Default for HireLeadFixture {
+    fn default() -> Self {
+        Self {
+            setup_environment: true,
+            stake: None,
+            reward_policy: None,
+        }
+    }
+}
+impl HireLeadFixture {
+    pub fn with_stake(self, stake: u64) -> Self {
+        Self {
+            stake: Some(stake),
+            ..self
+        }
+    }
+    pub fn with_reward_policy(self, reward_policy: RewardPolicy<u64, u64>) -> Self {
+        Self {
+            reward_policy: Some(reward_policy),
+            ..self
+        }
+    }
+
+    pub fn hire_lead(self) -> u64 {
+        fill_worker_position(
+            self.reward_policy,
+            self.stake,
+            self.setup_environment,
+            OpeningType::Leader,
+            Some(b"leader".to_vec()),
+        )
+    }
+}
+
+pub fn get_worker_by_id(worker_id: u64) -> Worker<u64, u64, u64, u64, u64> {
+    TestWorkingGroup::worker_by_id(worker_id)
+}
+
 pub struct AddWorkerOpeningFixture {
     origin: RawOrigin<u64>,
     activate_at: hiring::ActivateOpeningAt<u64>,
     commitment: OpeningPolicyCommitment<u64, u64>,
     human_readable_text: Vec<u8>,
+    opening_type: OpeningType,
 }
 
 impl Default for AddWorkerOpeningFixture {
     fn default() -> Self {
-        AddWorkerOpeningFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             activate_at: hiring::ActivateOpeningAt::CurrentBlock,
             commitment: <OpeningPolicyCommitment<u64, u64>>::default(),
             human_readable_text: b"human_text".to_vec(),
+            opening_type: OpeningType::Worker,
         }
     }
 }
@@ -572,35 +694,32 @@ impl AddWorkerOpeningFixture {
         self,
         policy_commitment: OpeningPolicyCommitment<u64, u64>,
     ) -> Self {
-        AddWorkerOpeningFixture {
+        Self {
             commitment: policy_commitment,
             ..self
         }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
-        let saved_opening_next_id = TestWorkingGroup::next_worker_opening_id();
-        let actual_result = TestWorkingGroup::add_worker_opening(
-            self.origin.clone().into(),
-            self.activate_at.clone(),
-            self.commitment.clone(),
-            self.human_readable_text.clone(),
-        );
+        let saved_opening_next_id = TestWorkingGroup::next_opening_id();
+        let actual_result = self.call().map(|_| ());
+
         assert_eq!(actual_result.clone(), expected_result);
 
         if actual_result.is_ok() {
             assert_eq!(
-                TestWorkingGroup::next_worker_opening_id(),
+                TestWorkingGroup::next_opening_id(),
                 saved_opening_next_id + 1
             );
             let opening_id = saved_opening_next_id;
 
-            let actual_opening = TestWorkingGroup::worker_opening_by_id(opening_id);
+            let actual_opening = TestWorkingGroup::opening_by_id(opening_id);
 
-            let expected_opening = WorkerOpening::<u64, u64, u64, u64> {
-                opening_id,
-                worker_applications: BTreeSet::new(),
+            let expected_opening = Opening::<u64, u64, u64, u64> {
+                hiring_opening_id: opening_id,
+                applications: BTreeSet::new(),
                 policy_commitment: self.commitment.clone(),
+                opening_type: self.opening_type,
             };
 
             assert_eq!(actual_opening, expected_opening);
@@ -609,15 +728,39 @@ impl AddWorkerOpeningFixture {
         saved_opening_next_id
     }
 
+    pub fn call(&self) -> Result<u64, Error> {
+        let saved_opening_next_id = TestWorkingGroup::next_opening_id();
+        TestWorkingGroup::add_opening(
+            self.origin.clone().into(),
+            self.activate_at.clone(),
+            self.commitment.clone(),
+            self.human_readable_text.clone(),
+            self.opening_type,
+        )?;
+
+        Ok(saved_opening_next_id)
+    }
+
     pub fn with_text(self, text: Vec<u8>) -> Self {
-        AddWorkerOpeningFixture {
+        Self {
             human_readable_text: text,
             ..self
         }
     }
 
+    pub fn with_opening_type(self, opening_type: OpeningType) -> Self {
+        Self {
+            opening_type,
+            ..self
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+        Self { origin, ..self }
+    }
+
     pub fn with_activate_at(self, activate_at: hiring::ActivateOpeningAt<u64>) -> Self {
-        AddWorkerOpeningFixture {
+        Self {
             activate_at,
             ..self
         }
@@ -626,43 +769,6 @@ impl AddWorkerOpeningFixture {
 
 pub struct EventFixture;
 impl EventFixture {
-    pub fn assert_crate_events(
-        expected_raw_events: Vec<
-            RawEvent<
-                u64,
-                u64,
-                u64,
-                u64,
-                u64,
-                u64,
-                std::collections::BTreeMap<u64, u64>,
-                Vec<u8>,
-                u64,
-                u64,
-                TestWorkingGroupInstance,
-            >,
-        >,
-    ) {
-        let converted_events = expected_raw_events
-            .iter()
-            .map(|ev| TestEvent::working_group_TestWorkingGroupInstance(ev.clone()))
-            .collect::<Vec<TestEvent>>();
-
-        Self::assert_global_events(converted_events)
-    }
-    pub fn assert_global_events(expected_raw_events: Vec<TestEvent>) {
-        let expected_events = expected_raw_events
-            .iter()
-            .map(|ev| EventRecord {
-                phase: Phase::ApplyExtrinsic(0),
-                event: ev.clone(),
-                topics: vec![],
-            })
-            .collect::<Vec<EventRecord<_, _>>>();
-
-        assert_eq!(System::events(), expected_events);
-    }
-
     pub fn assert_last_crate_event(
         expected_raw_event: RawEvent<
             u64,
@@ -670,7 +776,6 @@ impl EventFixture {
             u64,
             u64,
             u64,
-            u64,
             std::collections::BTreeMap<u64, u64>,
             Vec<u8>,
             u64,
@@ -704,26 +809,29 @@ pub struct DecreaseWorkerStakeFixture {
 impl DecreaseWorkerStakeFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
         let account_id = 1;
-        DecreaseWorkerStakeFixture {
-            origin: RawOrigin::Signed(account_id),
+
+        let lead_account_id = get_current_lead_account_id();
+
+        Self {
+            origin: RawOrigin::Signed(lead_account_id),
             worker_id,
             balance: 10,
             account_id,
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        DecreaseWorkerStakeFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_balance(self, balance: u64) -> Self {
-        DecreaseWorkerStakeFixture { balance, ..self }
+        Self { balance, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
         let stake_id = 0;
         let old_balance = Balances::free_balance(&self.account_id);
         let old_stake = <stake::Module<Test>>::stakes(stake_id);
-        let actual_result = TestWorkingGroup::decrease_worker_stake(
+        let actual_result = TestWorkingGroup::decrease_stake(
             self.origin.clone().into(),
             self.worker_id,
             self.balance,
@@ -748,7 +856,7 @@ impl DecreaseWorkerStakeFixture {
     }
 }
 
-fn get_stake_balance(stake: stake::Stake<u64, u64, u64>) -> u64 {
+pub(crate) fn get_stake_balance(stake: stake::Stake<u64, u64, u64>) -> u64 {
     if let stake::StakingStatus::Staked(stake) = stake.staking_status {
         return stake.staked_amount;
     }
@@ -756,6 +864,17 @@ fn get_stake_balance(stake: stake::Stake<u64, u64, u64>) -> u64 {
     panic!("Not staked.");
 }
 
+fn get_current_lead_account_id() -> u64 {
+    let leader_worker_id = TestWorkingGroup::current_lead();
+
+    if let Some(leader_worker_id) = leader_worker_id {
+        let leader = TestWorkingGroup::worker_by_id(leader_worker_id);
+        leader.role_account_id
+    } else {
+        0 // return invalid lead_account_id for testing
+    }
+}
+
 pub struct SlashWorkerStakeFixture {
     origin: RawOrigin<u64>,
     worker_id: u64,
@@ -766,30 +885,30 @@ pub struct SlashWorkerStakeFixture {
 impl SlashWorkerStakeFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
         let account_id = 1;
-        SlashWorkerStakeFixture {
-            origin: RawOrigin::Signed(account_id),
+
+        let lead_account_id = get_current_lead_account_id();
+
+        Self {
+            origin: RawOrigin::Signed(lead_account_id),
             worker_id,
             balance: 10,
             account_id,
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        SlashWorkerStakeFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_balance(self, balance: u64) -> Self {
-        SlashWorkerStakeFixture { balance, ..self }
+        Self { balance, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
         let stake_id = 0;
         let old_balance = Balances::free_balance(&self.account_id);
         let old_stake = <stake::Module<Test>>::stakes(stake_id);
-        let actual_result = TestWorkingGroup::slash_worker_stake(
-            self.origin.clone().into(),
-            self.worker_id,
-            self.balance,
-        );
+        let actual_result =
+            TestWorkingGroup::slash_stake(self.origin.clone().into(), self.worker_id, self.balance);
 
         assert_eq!(actual_result, expected_result);
 

+ 200 - 0
runtime-modules/working-group/src/tests/hiring_workflow.rs

@@ -0,0 +1,200 @@
+use crate::tests::fixtures::{
+    create_mint, increase_total_balance_issuance_using_account_id, set_mint_id, setup_members,
+    AddWorkerOpeningFixture, ApplyOnWorkerOpeningFixture, BeginReviewWorkerApplicationsFixture,
+    FillWorkerOpeningFixture, SetLeadFixture,
+};
+use crate::tests::mock::TestWorkingGroup;
+use crate::Error;
+use crate::{OpeningPolicyCommitment, OpeningType, RewardPolicy};
+use system::RawOrigin;
+
+#[derive(Clone)]
+struct HiringWorkflowApplication {
+    stake: Option<u64>,
+    worker_handle: Vec<u8>,
+    origin: RawOrigin<u64>,
+    member_id: u64,
+}
+
+pub struct HiringWorkflow {
+    opening_type: OpeningType,
+    expected_result: Result<(), Error>,
+    role_stake: Option<u64>,
+    applications: Vec<HiringWorkflowApplication>,
+    setup_environment: bool,
+    reward_policy: Option<RewardPolicy<u64, u64>>,
+}
+
+impl Default for HiringWorkflow {
+    fn default() -> Self {
+        Self {
+            opening_type: OpeningType::Worker,
+            expected_result: Ok(()),
+            role_stake: None,
+            applications: Vec::new(),
+            setup_environment: true,
+            reward_policy: None,
+        }
+    }
+}
+
+impl HiringWorkflow {
+    pub fn expect(self, result: Result<(), Error>) -> Self {
+        Self {
+            expected_result: result,
+            ..self
+        }
+    }
+
+    pub fn disable_setup_environment(self) -> Self {
+        Self {
+            setup_environment: false,
+            ..self
+        }
+    }
+
+    pub fn with_setup_environment(self, setup_environment: bool) -> Self {
+        Self {
+            setup_environment,
+            ..self
+        }
+    }
+
+    pub fn with_opening_type(self, opening_type: OpeningType) -> Self {
+        Self {
+            opening_type,
+            ..self
+        }
+    }
+
+    pub fn with_role_stake(self, role_stake: Option<u64>) -> Self {
+        Self { role_stake, ..self }
+    }
+
+    pub fn with_reward_policy(self, reward_policy: Option<RewardPolicy<u64, u64>>) -> Self {
+        Self {
+            reward_policy,
+            ..self
+        }
+    }
+
+    pub fn add_default_application(self) -> Self {
+        let worker_handle = b"default worker handle".to_vec();
+
+        self.add_application(worker_handle)
+    }
+
+    pub fn add_application(self, worker_handle: Vec<u8>) -> Self {
+        self.add_application_with_origin(worker_handle, RawOrigin::Signed(1), 1)
+    }
+
+    pub fn add_application_with_origin(
+        self,
+        worker_handle: Vec<u8>,
+        origin: RawOrigin<u64>,
+        member_id: u64,
+    ) -> Self {
+        let mut applications = self.applications;
+        applications.push(HiringWorkflowApplication {
+            worker_handle,
+            stake: self.role_stake.clone(),
+            origin,
+            member_id,
+        });
+
+        Self {
+            applications,
+            ..self
+        }
+    }
+
+    fn setup_environment(&self) {
+        if matches!(self.opening_type, OpeningType::Worker) {
+            SetLeadFixture::default().set_lead();
+        }
+        increase_total_balance_issuance_using_account_id(1, 10000);
+        setup_members(3);
+        set_mint_id(create_mint());
+    }
+
+    pub fn execute(&self) -> Option<u64> {
+        if self.setup_environment {
+            self.setup_environment()
+        }
+
+        let result = self.fill_worker_position();
+
+        let check_result = result.clone().map(|_| ());
+
+        assert_eq!(check_result, self.expected_result);
+
+        result.ok()
+    }
+
+    fn fill_worker_position(&self) -> Result<u64, Error> {
+        let origin = match self.opening_type {
+            OpeningType::Leader => RawOrigin::Root,
+            OpeningType::Worker => {
+                let leader_worker_id = TestWorkingGroup::current_lead().unwrap();
+                let leader = TestWorkingGroup::worker_by_id(leader_worker_id);
+                let lead_account_id = leader.role_account_id;
+
+                RawOrigin::Signed(lead_account_id)
+            }
+        };
+
+        // create the opening
+        let mut add_worker_opening_fixture = AddWorkerOpeningFixture::default()
+            .with_opening_type(self.opening_type)
+            .with_origin(origin.clone());
+
+        if let Some(stake) = self.role_stake.clone() {
+            add_worker_opening_fixture =
+                add_worker_opening_fixture.with_policy_commitment(OpeningPolicyCommitment {
+                    role_staking_policy: Some(hiring::StakingPolicy {
+                        amount: stake,
+                        amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                        crowded_out_unstaking_period_length: None,
+                        review_period_expired_unstaking_period_length: None,
+                    }),
+                    ..OpeningPolicyCommitment::default()
+                });
+        }
+
+        let opening_id = add_worker_opening_fixture.call()?;
+
+        // Fill applications.
+        let mut application_ids = Vec::new();
+        for application in self.applications.clone() {
+            let apply_on_worker_opening_fixture =
+                ApplyOnWorkerOpeningFixture::default_for_opening_id(opening_id)
+                    .with_text(application.worker_handle)
+                    .with_origin(application.origin, application.member_id)
+                    .with_role_stake(self.role_stake);
+
+            let application_id = apply_on_worker_opening_fixture.call()?;
+            application_ids.push(application_id);
+        }
+
+        // begin application review
+
+        let begin_review_worker_applications_fixture =
+            BeginReviewWorkerApplicationsFixture::default_for_opening_id(opening_id)
+                .with_origin(origin.clone());
+        begin_review_worker_applications_fixture.call_and_assert(Ok(()));
+
+        // fill opening
+        let mut fill_worker_opening_fixture =
+            FillWorkerOpeningFixture::default_for_ids(opening_id, application_ids)
+                .with_origin(origin.clone());
+
+        if let Some(reward_policy) = self.reward_policy.clone() {
+            fill_worker_opening_fixture =
+                fill_worker_opening_fixture.with_reward_policy(reward_policy);
+        }
+
+        let worker_id = fill_worker_opening_fixture.call()?;
+
+        Ok(worker_id)
+    }
+}

+ 50 - 3
runtime-modules/working-group/src/tests/mock.rs

@@ -1,4 +1,4 @@
-use crate::{Module, Trait};
+use crate::{BalanceOf, Module, NegativeImbalance, Trait};
 use common::constraints::InputValidationLengthConstraint;
 use primitives::H256;
 use sr_primitives::{
@@ -6,7 +6,10 @@ use sr_primitives::{
     traits::{BlakeTwo256, IdentityLookup},
     Perbill,
 };
-use srml_support::{impl_outer_event, impl_outer_origin, parameter_types};
+use srml_support::{
+    impl_outer_event, impl_outer_origin, parameter_types, StorageLinkedMap, StorageMap,
+};
+use std::marker::PhantomData;
 
 impl_outer_origin! {
         pub enum Origin for Test {}
@@ -79,7 +82,7 @@ impl minting::Trait for Test {
 impl stake::Trait for Test {
     type Currency = Balances;
     type StakePoolId = StakePoolId;
-    type StakingEventsHandler = ();
+    type StakingEventsHandler = StakingEventsHandler<Test>;
     type StakeId = u64;
     type SlashId = u64;
 }
@@ -163,3 +166,47 @@ pub fn build_test_externalities() -> runtime_io::TestExternalities {
 
     t.into()
 }
+
+pub struct StakingEventsHandler<T> {
+    pub marker: PhantomData<T>,
+}
+
+impl<T: stake::Trait + crate::Trait<TestWorkingGroupInstance>> stake::StakingEventsHandler<T>
+    for StakingEventsHandler<T>
+{
+    /// Unstake remaining sum back to the source_account_id
+    fn unstaked(
+        stake_id: &<T as stake::Trait>::StakeId,
+        _unstaked_amount: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        // Stake not related to a staked role managed by the hiring module.
+        if !hiring::ApplicationIdByStakingId::<T>::exists(*stake_id) {
+            return remaining_imbalance;
+        }
+
+        let hiring_application_id = hiring::ApplicationIdByStakingId::<T>::get(*stake_id);
+
+        if crate::MemberIdByHiringApplicationId::<T, TestWorkingGroupInstance>::exists(
+            hiring_application_id,
+        ) {
+            return <crate::Module<T, TestWorkingGroupInstance>>::refund_working_group_stake(
+                *stake_id,
+                remaining_imbalance,
+            );
+        }
+
+        remaining_imbalance
+    }
+
+    /// Empty handler for slashing
+    fn slashed(
+        _: &<T as stake::Trait>::StakeId,
+        _: Option<<T as stake::Trait>::SlashId>,
+        _: BalanceOf<T>,
+        _: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        remaining_imbalance
+    }
+}

File diff suppressed because it is too large
+ 262 - 322
runtime-modules/working-group/src/tests/mod.rs


+ 67 - 50
runtime-modules/working-group/src/types.rs

@@ -6,7 +6,7 @@ use rstd::collections::btree_set::BTreeSet;
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 
-/// Terms for slashings applied to a given role.
+/// Terms for slashes applied to a given role.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
 pub struct SlashableTerms {
@@ -28,7 +28,7 @@ pub enum SlashingTerms {
     Slashable(SlashableTerms),
 }
 
-/// Must be default constructible because it indirectly is a value in a storage map.
+/// Must be default constructable because it indirectly is a value in a storage map.
 /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
 impl Default for SlashingTerms {
     fn default() -> Self {
@@ -67,83 +67,94 @@ pub struct OpeningPolicyCommitment<BlockNumber, Balance> {
     pub fill_opening_failed_applicant_role_stake_unstaking_period: Option<BlockNumber>,
 
     /// When terminating a worker: unstaking period for application stake.
-    pub terminate_worker_application_stake_unstaking_period: Option<BlockNumber>,
+    pub terminate_application_stake_unstaking_period: Option<BlockNumber>,
 
-    /// When terminating a worker: unstaking period for role stake.
-    pub terminate_worker_role_stake_unstaking_period: Option<BlockNumber>,
+    /// When terminating a worke/leadr: unstaking period for role stake.
+    pub terminate_role_stake_unstaking_period: Option<BlockNumber>,
 
-    /// When a worker exists: unstaking period for application stake.
-    pub exit_worker_role_application_stake_unstaking_period: Option<BlockNumber>,
+    /// When a worker/lead exists: unstaking period for application stake.
+    pub exit_role_application_stake_unstaking_period: Option<BlockNumber>,
 
-    /// When a worker exists: unstaking period for role stake.
-    pub exit_worker_role_stake_unstaking_period: Option<BlockNumber>,
+    /// When a worker/lead exists: unstaking period for role stake.
+    pub exit_role_stake_unstaking_period: Option<BlockNumber>,
 }
 
-/// An opening for a worker role.
+/// An opening for a worker or lead role.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-pub struct WorkerOpening<OpeningId, BlockNumber, Balance, WorkerApplicationId: core::cmp::Ord> {
-    /// Identifer for underlying opening in the hiring module.
-    pub opening_id: OpeningId,
+pub struct Opening<OpeningId, BlockNumber, Balance, WorkerApplicationId: core::cmp::Ord> {
+    /// Identifier for underlying opening in the hiring module.
+    pub hiring_opening_id: OpeningId,
 
     /// Set of identifiers for all worker applications ever added.
-    pub worker_applications: BTreeSet<WorkerApplicationId>,
+    pub applications: BTreeSet<WorkerApplicationId>,
 
     /// Commitment to policies in opening.
     pub policy_commitment: OpeningPolicyCommitment<BlockNumber, Balance>,
+
+    /// Defines opening type: Leader or worker.
+    pub opening_type: OpeningType,
 }
 
-/// Working group lead: worker lead.
+/// Defines type of the opening: regular working group fellow or group leader.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-pub struct Lead<MemberId, AccountId> {
-    /// Member id of the leader.
-    pub member_id: MemberId,
+#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq, Copy)]
+pub enum OpeningType {
+    /// Group leader.
+    Leader,
 
-    /// Account used to authenticate in this role.
-    pub role_account_id: AccountId,
+    /// Regular worker.
+    Worker,
 }
 
-/// An application for the worker role.
+/// Must be default constructible because it indirectly is a value in a storage map.
+/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
+impl Default for OpeningType {
+    fn default() -> Self {
+        Self::Worker
+    }
+}
+
+/// An application for the worker/lead role.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-pub struct WorkerApplication<AccountId, WorkerOpeningId, MemberId, ApplicationId> {
+pub struct Application<AccountId, OpeningId, MemberId, ApplicationId> {
     /// Account used to authenticate in this role.
-    pub role_account: AccountId,
+    pub role_account_id: AccountId,
 
     /// Opening on which this application applies.
-    pub worker_opening_id: WorkerOpeningId,
+    pub opening_id: OpeningId,
 
     /// Member applying.
     pub member_id: MemberId,
 
     /// Underlying application in hiring module.
-    pub application_id: ApplicationId,
+    pub hiring_application_id: ApplicationId,
 }
 
-impl<AccountId: Clone, WorkerOpeningId: Clone, MemberId: Clone, ApplicationId: Clone>
-    WorkerApplication<AccountId, WorkerOpeningId, MemberId, ApplicationId>
+impl<AccountId: Clone, OpeningId: Clone, MemberId: Clone, ApplicationId: Clone>
+    Application<AccountId, OpeningId, MemberId, ApplicationId>
 {
     /// Creates a new worker application using parameters.
     pub fn new(
-        role_account: &AccountId,
-        worker_opening_id: &WorkerOpeningId,
+        role_account_id: &AccountId,
+        opening_id: &OpeningId,
         member_id: &MemberId,
         application_id: &ApplicationId,
     ) -> Self {
-        WorkerApplication {
-            role_account: role_account.clone(),
-            worker_opening_id: worker_opening_id.clone(),
+        Application {
+            role_account_id: role_account_id.clone(),
+            opening_id: opening_id.clone(),
             member_id: member_id.clone(),
-            application_id: application_id.clone(),
+            hiring_application_id: application_id.clone(),
         }
     }
 }
 
-/// Role stake information for a worker.
+/// Role stake information for a worker/lead.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-pub struct WorkerRoleStakeProfile<StakeId, BlockNumber> {
+pub struct RoleStakeProfile<StakeId, BlockNumber> {
     /// Whether participant is staked, and if so, the identifier for this staking in the staking module.
     pub stake_id: StakeId,
 
@@ -154,8 +165,8 @@ pub struct WorkerRoleStakeProfile<StakeId, BlockNumber> {
     pub exit_unstaking_period: Option<BlockNumber>,
 }
 
-impl<StakeId: Clone, BlockNumber: Clone> WorkerRoleStakeProfile<StakeId, BlockNumber> {
-    /// Creates a new worker role stake profile using stake parameters.
+impl<StakeId: Clone, BlockNumber: Clone> RoleStakeProfile<StakeId, BlockNumber> {
+    /// Creates a new worker/lead role stake profile using stake parameters.
     pub fn new(
         stake_id: &StakeId,
         termination_unstaking_period: &Option<BlockNumber>,
@@ -169,19 +180,22 @@ impl<StakeId: Clone, BlockNumber: Clone> WorkerRoleStakeProfile<StakeId, BlockNu
     }
 }
 
-/// Working group participant: worker.
+/// Working group participant: worker/lead.
 /// This role can be staked, have reward and be inducted through the hiring module.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 pub struct Worker<AccountId, RewardRelationshipId, StakeId, BlockNumber, MemberId> {
-    /// Member id related to the worker
+    /// Member id related to the worker/lead.
     pub member_id: MemberId,
+
     /// Account used to authenticate in this role.
-    pub role_account: AccountId,
+    pub role_account_id: AccountId,
+
     /// Whether the role has recurring reward, and if so an identifier for this.
     pub reward_relationship: Option<RewardRelationshipId>,
-    /// When set, describes role stake of worker.
-    pub role_stake_profile: Option<WorkerRoleStakeProfile<StakeId, BlockNumber>>,
+
+    /// When set, describes role stake of the worker/lead.
+    pub role_stake_profile: Option<RoleStakeProfile<StakeId, BlockNumber>>,
 }
 
 impl<
@@ -195,28 +209,31 @@ impl<
     /// Creates a new _Worker_ using parameters.
     pub fn new(
         member_id: &MemberId,
-        role_account: &AccountId,
+        role_account_id: &AccountId,
         reward_relationship: &Option<RewardRelationshipId>,
-        role_stake_profile: &Option<WorkerRoleStakeProfile<StakeId, BlockNumber>>,
+        role_stake_profile: &Option<RoleStakeProfile<StakeId, BlockNumber>>,
     ) -> Self {
         Worker {
             member_id: member_id.clone(),
-            role_account: role_account.clone(),
+            role_account_id: role_account_id.clone(),
             reward_relationship: reward_relationship.clone(),
             role_stake_profile: role_stake_profile.clone(),
         }
     }
 }
 
-/// Origin of exit initiation on behalf of a curator.'
+/// Origin of exit initiation.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Debug, Clone, PartialEq)]
-pub enum WorkerExitInitiationOrigin {
-    /// Lead is origin.
+pub enum ExitInitiationOrigin {
+    /// Lead fires the worker.
     Lead,
 
-    /// The curator exiting is the origin.
+    /// Worker leaves the position.
     Worker,
+
+    /// Council fires the leader.
+    Sudo,
 }
 
 /// The recurring reward if any to be assigned to an actor when filling in the position.

+ 1 - 2
runtime/Cargo.toml

@@ -1,11 +1,10 @@
-
 [package]
 authors = ['Joystream contributors']
 edition = '2018'
 name = 'joystream-node-runtime'
 # Follow convention: https://github.com/Joystream/substrate-runtime-joystream/issues/1
 # {Authoring}.{Spec}.{Impl} of the RuntimeVersion
-version = '6.16.0'
+version = '6.17.0'
 
 [features]
 default = ['std']

+ 141 - 0
runtime/src/integration/content_working_group.rs

@@ -0,0 +1,141 @@
+use crate::{AccountId, Credential, Runtime};
+
+use srml_support::traits::{Currency, Imbalance};
+use srml_support::{parameter_types, StorageLinkedMap, StorageMap};
+
+parameter_types! {
+    pub const CurrentLeadCredential: Credential = 0;
+    pub const AnyActiveCuratorCredential: Credential = 1;
+    pub const AnyActiveChannelOwnerCredential: Credential = 2;
+    pub const PrincipalIdMappingStartsAtCredential: Credential = 1000;
+}
+
+pub struct ContentWorkingGroupCredentials {}
+impl versioned_store_permissions::CredentialChecker<Runtime> for ContentWorkingGroupCredentials {
+    fn account_has_credential(
+        account: &AccountId,
+        credential: <Runtime as versioned_store_permissions::Trait>::Credential,
+    ) -> bool {
+        match credential {
+            // Credentials from 0..999 represents groups or more complex requirements
+            // Current Lead if set
+            credential if credential == CurrentLeadCredential::get() => {
+                match <content_working_group::Module<Runtime>>::ensure_lead_is_set() {
+                    Ok((_, lead)) => lead.role_account == *account,
+                    _ => false,
+                }
+            }
+            // Any Active Curator
+            credential if credential == AnyActiveCuratorCredential::get() => {
+                // Look for a Curator with a matching role account
+                for (_principal_id, principal) in
+                    <content_working_group::PrincipalById<Runtime>>::enumerate()
+                {
+                    if let content_working_group::Principal::Curator(curator_id) = principal {
+                        let curator =
+                            <content_working_group::CuratorById<Runtime>>::get(curator_id);
+                        if curator.role_account == *account
+                            && curator.stage == content_working_group::CuratorRoleStage::Active
+                        {
+                            return true;
+                        }
+                    }
+                }
+
+                false
+            }
+            // Any Active Channel Owner
+            credential if credential == AnyActiveChannelOwnerCredential::get() => {
+                // Look for a ChannelOwner with a matching role account
+                for (_principal_id, principal) in
+                    <content_working_group::PrincipalById<Runtime>>::enumerate()
+                {
+                    if let content_working_group::Principal::ChannelOwner(channel_id) = principal {
+                        let channel =
+                            <content_working_group::ChannelById<Runtime>>::get(channel_id);
+                        if channel.role_account == *account {
+                            return true; // should we also take publishing_status/curation_status into account ?
+                        }
+                    }
+                }
+
+                false
+            }
+            // mapping to working group principal id
+            n if n >= PrincipalIdMappingStartsAtCredential::get() => {
+                <content_working_group::Module<Runtime>>::account_has_credential(
+                    account,
+                    n - PrincipalIdMappingStartsAtCredential::get(),
+                )
+            }
+            _ => false,
+        }
+    }
+}
+
+pub struct ContentWorkingGroupStakingEventHandler {}
+impl stake::StakingEventsHandler<Runtime> for ContentWorkingGroupStakingEventHandler {
+    fn unstaked(
+        stake_id: &<Runtime as stake::Trait>::StakeId,
+        _unstaked_amount: stake::BalanceOf<Runtime>,
+        remaining_imbalance: stake::NegativeImbalance<Runtime>,
+    ) -> stake::NegativeImbalance<Runtime> {
+        if !hiring::ApplicationIdByStakingId::<Runtime>::exists(stake_id) {
+            // Stake not related to a staked role managed by the hiring module
+            return remaining_imbalance;
+        }
+
+        let application_id = hiring::ApplicationIdByStakingId::<Runtime>::get(stake_id);
+
+        if !content_working_group::CuratorApplicationById::<Runtime>::exists(application_id) {
+            // Stake not for a Curator
+            return remaining_imbalance;
+        }
+
+        // Notify the Hiring module - is there a potential re-entrancy bug if
+        // instant unstaking is occuring?
+        hiring::Module::<Runtime>::unstaked(*stake_id);
+
+        // Only notify working group module if non instantaneous unstaking occured
+        if content_working_group::UnstakerByStakeId::<Runtime>::exists(stake_id) {
+            content_working_group::Module::<Runtime>::unstaked(*stake_id);
+        }
+
+        // Determine member id of the curator
+        let curator_application =
+            content_working_group::CuratorApplicationById::<Runtime>::get(application_id);
+        let member_id = curator_application.member_id;
+
+        // get member's profile
+        let member_profile = membership::members::MemberProfile::<Runtime>::get(member_id).unwrap();
+
+        // deposit funds to member's root_account
+        // The application doesn't recorded the original source_account from which staked funds were
+        // provided, so we don't really have another option at the moment.
+        <Runtime as stake::Trait>::Currency::resolve_creating(
+            &member_profile.root_account,
+            remaining_imbalance,
+        );
+
+        stake::NegativeImbalance::<Runtime>::zero()
+    }
+
+    // Handler for slashing event
+    fn slashed(
+        _id: &<Runtime as stake::Trait>::StakeId,
+        _slash_id: Option<<Runtime as stake::Trait>::SlashId>,
+        _slashed_amount: stake::BalanceOf<Runtime>,
+        _remaining_stake: stake::BalanceOf<Runtime>,
+        remaining_imbalance: stake::NegativeImbalance<Runtime>,
+    ) -> stake::NegativeImbalance<Runtime> {
+        // Check if the stake is associated with a hired curator or applicant
+        // if their stake goes below minimum required for the role,
+        // they should get deactivated.
+        // Since we don't currently implement any slash initiation in working group,
+        // there is nothing to do for now.
+
+        // Not interested in transfering the slashed amount anywhere for now,
+        // so return it to next handler.
+        remaining_imbalance
+    }
+}

+ 2 - 0
runtime/src/integration/mod.rs

@@ -1,2 +1,4 @@
+pub mod content_working_group;
 pub mod proposals;
 pub mod storage;
+pub mod working_group;

+ 2 - 2
runtime/src/integration/proposals/council_origin_validator.rs

@@ -2,7 +2,7 @@
 
 use rstd::marker::PhantomData;
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use proposals_engine::VotersParameters;
 
 use super::{MemberId, MembershipOriginValidator};
@@ -44,7 +44,7 @@ impl<T: governance::council::Trait> VotersParameters for CouncilManager<T> {
 mod tests {
     use super::CouncilManager;
     use crate::Runtime;
-    use common::origin_validator::ActorOriginValidator;
+    use common::origin::ActorOriginValidator;
     use membership::members::UserInfo;
     use proposals_engine::VotersParameters;
     use sr_primitives::AccountId32;

+ 2 - 2
runtime/src/integration/proposals/membership_origin_validator.rs

@@ -2,7 +2,7 @@
 
 use rstd::marker::PhantomData;
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use system::ensure_signed;
 
 /// Member of the Joystream organization
@@ -46,7 +46,7 @@ impl<T: crate::members::Trait>
 mod tests {
     use super::MembershipOriginValidator;
     use crate::Runtime;
-    use common::origin_validator::ActorOriginValidator;
+    use common::origin::ActorOriginValidator;
     use membership::members::UserInfo;
     use sr_primitives::AccountId32;
     use system::RawOrigin;

+ 1 - 1
runtime/src/integration/storage.rs

@@ -8,7 +8,7 @@ pub struct StorageProviderHelper;
 
 impl storage::data_directory::StorageProviderHelper<Runtime> for StorageProviderHelper {
     fn get_random_storage_provider() -> Result<ActorId, &'static str> {
-        let ids = crate::StorageWorkingGroup::get_all_worker_ids();
+        let ids = crate::StorageWorkingGroup::get_regular_worker_ids();
 
         let live_ids: Vec<ActorId> = ids
             .into_iter()

+ 49 - 0
runtime/src/integration/working_group.rs

@@ -0,0 +1,49 @@
+use rstd::marker::PhantomData;
+use srml_support::{StorageLinkedMap, StorageMap};
+
+use crate::StorageWorkingGroupInstance;
+use stake::{BalanceOf, NegativeImbalance};
+
+pub struct StakingEventsHandler<T> {
+    pub marker: PhantomData<T>,
+}
+
+impl<T: stake::Trait + working_group::Trait<StorageWorkingGroupInstance>>
+    stake::StakingEventsHandler<T> for StakingEventsHandler<T>
+{
+    /// Unstake remaining sum back to the source_account_id
+    fn unstaked(
+        stake_id: &<T as stake::Trait>::StakeId,
+        _unstaked_amount: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        // Stake not related to a staked role managed by the hiring module.
+        if !hiring::ApplicationIdByStakingId::<T>::exists(*stake_id) {
+            return remaining_imbalance;
+        }
+
+        let hiring_application_id = hiring::ApplicationIdByStakingId::<T>::get(*stake_id);
+
+        if working_group::MemberIdByHiringApplicationId::<T, StorageWorkingGroupInstance>::exists(
+            hiring_application_id,
+        ) {
+            return <working_group::Module<T, StorageWorkingGroupInstance>>::refund_working_group_stake(
+				*stake_id,
+				remaining_imbalance,
+			);
+        }
+
+        remaining_imbalance
+    }
+
+    /// Empty handler for the slashing.
+    fn slashed(
+        _: &<T as stake::Trait>::StakeId,
+        _: Option<<T as stake::Trait>::SlashId>,
+        _: BalanceOf<T>,
+        _: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        remaining_imbalance
+    }
+}

+ 12 - 139
runtime/src/lib.rs

@@ -161,7 +161,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
     spec_name: create_runtime_str!("joystream-node"),
     impl_name: create_runtime_str!("joystream-node"),
     authoring_version: 6,
-    spec_version: 16,
+    spec_version: 17,
     impl_version: 0,
     apis: RUNTIME_API_VERSIONS,
 };
@@ -319,7 +319,7 @@ impl transaction_payment::Trait for Runtime {
     type TransactionBaseFee = TransactionBaseFee;
     type TransactionByteFee = TransactionByteFee;
     type WeightToFee = ();
-    type FeeMultiplierUpdate = (); // FeeMultiplierUpdateHandler;
+    type FeeMultiplierUpdate = ();
 }
 
 impl sudo::Trait for Runtime {
@@ -447,7 +447,10 @@ impl versioned_store::Trait for Runtime {
 
 impl versioned_store_permissions::Trait for Runtime {
     type Credential = Credential;
-    type CredentialChecker = (ContentWorkingGroupCredentials, SudoKeyHasAllCredentials);
+    type CredentialChecker = (
+        integration::content_working_group::ContentWorkingGroupCredentials,
+        SudoKeyHasAllCredentials,
+    );
     type CreateClassPermissionsChecker = ContentLeadOrSudoKeyCanCreateClasses;
 }
 
@@ -462,72 +465,6 @@ impl versioned_store_permissions::CredentialChecker<Runtime> for SudoKeyHasAllCr
     }
 }
 
-parameter_types! {
-    pub const CurrentLeadCredential: Credential = 0;
-    pub const AnyActiveCuratorCredential: Credential = 1;
-    pub const AnyActiveChannelOwnerCredential: Credential = 2;
-    pub const PrincipalIdMappingStartsAtCredential: Credential = 1000;
-}
-
-pub struct ContentWorkingGroupCredentials {}
-impl versioned_store_permissions::CredentialChecker<Runtime> for ContentWorkingGroupCredentials {
-    fn account_has_credential(
-        account: &AccountId,
-        credential: <Runtime as versioned_store_permissions::Trait>::Credential,
-    ) -> bool {
-        match credential {
-            // Credentials from 0..999 represents groups or more complex requirements
-            // Current Lead if set
-            credential if credential == CurrentLeadCredential::get() => {
-                match <content_wg::Module<Runtime>>::ensure_lead_is_set() {
-                    Ok((_, lead)) => lead.role_account == *account,
-                    _ => false,
-                }
-            }
-            // Any Active Curator
-            credential if credential == AnyActiveCuratorCredential::get() => {
-                // Look for a Curator with a matching role account
-                for (_principal_id, principal) in <content_wg::PrincipalById<Runtime>>::enumerate()
-                {
-                    if let content_wg::Principal::Curator(curator_id) = principal {
-                        let curator = <content_wg::CuratorById<Runtime>>::get(curator_id);
-                        if curator.role_account == *account
-                            && curator.stage == content_wg::CuratorRoleStage::Active
-                        {
-                            return true;
-                        }
-                    }
-                }
-
-                false
-            }
-            // Any Active Channel Owner
-            credential if credential == AnyActiveChannelOwnerCredential::get() => {
-                // Look for a ChannelOwner with a matching role account
-                for (_principal_id, principal) in <content_wg::PrincipalById<Runtime>>::enumerate()
-                {
-                    if let content_wg::Principal::ChannelOwner(channel_id) = principal {
-                        let channel = <content_wg::ChannelById<Runtime>>::get(channel_id);
-                        if channel.role_account == *account {
-                            return true; // should we also take publishing_status/curation_status into account ?
-                        }
-                    }
-                }
-
-                false
-            }
-            // mapping to workging group principal id
-            n if n >= PrincipalIdMappingStartsAtCredential::get() => {
-                <content_wg::Module<Runtime>>::account_has_credential(
-                    account,
-                    n - PrincipalIdMappingStartsAtCredential::get(),
-                )
-            }
-            _ => false,
-        }
-    }
-}
-
 // Allow sudo key holder permission to create classes
 pub struct SudoKeyCanCreateClasses {}
 impl versioned_store_permissions::CreateClassPermissionsChecker<Runtime>
@@ -591,80 +528,16 @@ impl stake::Trait for Runtime {
     type Currency = <Self as common::currency::GovernanceCurrency>::Currency;
     type StakePoolId = StakePoolId;
     type StakingEventsHandler = (
-        ContentWorkingGroupStakingEventHandler,
-        crate::integration::proposals::StakingEventsHandler<Self>,
+        crate::integration::content_working_group::ContentWorkingGroupStakingEventHandler,
+        (
+            crate::integration::proposals::StakingEventsHandler<Self>,
+            crate::integration::working_group::StakingEventsHandler<Self>,
+        ),
     );
     type StakeId = u64;
     type SlashId = u64;
 }
 
-pub struct ContentWorkingGroupStakingEventHandler {}
-impl stake::StakingEventsHandler<Runtime> for ContentWorkingGroupStakingEventHandler {
-    fn unstaked(
-        stake_id: &<Runtime as stake::Trait>::StakeId,
-        _unstaked_amount: stake::BalanceOf<Runtime>,
-        remaining_imbalance: stake::NegativeImbalance<Runtime>,
-    ) -> stake::NegativeImbalance<Runtime> {
-        if !hiring::ApplicationIdByStakingId::<Runtime>::exists(stake_id) {
-            // Stake not related to a staked role managed by the hiring module
-            return remaining_imbalance;
-        }
-
-        let application_id = hiring::ApplicationIdByStakingId::<Runtime>::get(stake_id);
-
-        if !content_wg::CuratorApplicationById::<Runtime>::exists(application_id) {
-            // Stake not for a Curator
-            return remaining_imbalance;
-        }
-
-        // Notify the Hiring module - is there a potential re-entrancy bug if
-        // instant unstaking is occuring?
-        hiring::Module::<Runtime>::unstaked(*stake_id);
-
-        // Only notify working group module if non instantaneous unstaking occured
-        if content_wg::UnstakerByStakeId::<Runtime>::exists(stake_id) {
-            content_wg::Module::<Runtime>::unstaked(*stake_id);
-        }
-
-        // Determine member id of the curator
-        let curator_application =
-            content_wg::CuratorApplicationById::<Runtime>::get(application_id);
-        let member_id = curator_application.member_id;
-
-        // get member's profile
-        let member_profile = membership::members::MemberProfile::<Runtime>::get(member_id).unwrap();
-
-        // deposit funds to member's root_account
-        // The application doesn't recorded the original source_account from which staked funds were
-        // provided, so we don't really have another option at the moment.
-        <Runtime as stake::Trait>::Currency::resolve_creating(
-            &member_profile.root_account,
-            remaining_imbalance,
-        );
-
-        stake::NegativeImbalance::<Runtime>::zero()
-    }
-
-    // Handler for slashing event
-    fn slashed(
-        _id: &<Runtime as stake::Trait>::StakeId,
-        _slash_id: Option<<Runtime as stake::Trait>::SlashId>,
-        _slashed_amount: stake::BalanceOf<Runtime>,
-        _remaining_stake: stake::BalanceOf<Runtime>,
-        remaining_imbalance: stake::NegativeImbalance<Runtime>,
-    ) -> stake::NegativeImbalance<Runtime> {
-        // Check if the stake is associated with a hired curator or applicant
-        // if their stake goes below minimum required for the role,
-        // they should get deactivated.
-        // Since we don't currently implement any slash initiation in working group,
-        // there is nothing to do for now.
-
-        // Not interested in transfering the slashed amount anywhere for now,
-        // so return it to next handler.
-        remaining_imbalance
-    }
-}
-
 impl content_wg::Trait for Runtime {
     type Event = Event;
 }
@@ -720,7 +593,7 @@ impl members::Trait for Runtime {
  *
  * ForumUserRegistry could have been implemented directly on
  * the membership module, and likewise ForumUser on Profile,
- * however this approach is more loosley coupled.
+ * however this approach is more loosely coupled.
  *
  * Further exploration required to decide what the long
  * run convention should be.

+ 1 - 3
scripts/run-test-chain.sh

@@ -7,8 +7,6 @@ perl -i -pe's/"setElectionParametersProposalGracePeriod":.*/"setElectionParamete
 perl -i -pe's/"textProposalGracePeriod":.*/"textProposalGracePeriod": 0,/' .tmp/chainspec.json
 perl -i -pe's/"setContentWorkingGroupMintCapacityProposalGracePeriod":.*/"setContentWorkingGroupMintCapacityProposalGracePeriod": 0,/' .tmp/chainspec.json
 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/"spendingProposalGracePeriod":.*/"spendingProposalGracePeriod": 0/' .tmp/chainspec.json
 yes | cargo run --release -p joystream-node -- purge-chain --dev
 cargo run --release -p joystream-node -- --chain=.tmp/chainspec.json --alice --validator

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

@@ -4,10 +4,10 @@
   "license": "GPL-3.0-only",
   "scripts": {
     "build": "tsc --build tsconfig.json",
-    "test": "tap --files ts-node/register src/nicaea/tests/proposals/*Test.ts --files ts-node/register src/nicaea/tests/bureaucracy/*Test.ts -T",
+    "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/bureaucracy/manageWorkerAsLeadTest.ts -T",
+    "debug": "tap --files src/nicaea/tests/workingGroup/workerApplicationHappyCaseTest.ts -T",
     "lint": "tslint --project tsconfig.json",
     "prettier": "prettier --write ./src"
   },

+ 1 - 1
tests/network-tests/src/constantinople/tests/proposals/impl/storageRoleParametersProposal.ts

@@ -4,7 +4,7 @@ import { ApiWrapper } from '../../../utils/apiWrapper';
 import { v4 as uuid } from 'uuid';
 import BN from 'bn.js';
 import { assert } from 'chai';
-import { RoleParameters } from '@constantinople/types/lib/roles';
+import { RoleParameters } from '@constantinople/types/roles';
 import tap from 'tap';
 
 export function storageRoleParametersProposalTest(

+ 129 - 70
tests/network-tests/src/nicaea/tests/bureaucracy/impl/workingGroupModule.ts → tests/network-tests/src/nicaea/tests/workingGroup/impl/workingGroupModule.ts

@@ -2,7 +2,6 @@ import BN from 'bn.js';
 import { assert } from 'chai';
 import { ApiWrapper } from '../../../utils/apiWrapper';
 import { KeyringPair } from '@polkadot/keyring/types';
-import { WorkerOpening } from '@nicaea/types/lib/working-group';
 import { Keyring } from '@polkadot/api';
 import { v4 as uuid } from 'uuid';
 
@@ -23,18 +22,18 @@ export async function addWorkerOpening(
   roleStake: BN,
   activationDelay: BN
 ): Promise<BN> {
-  let workerOpeningId: BN;
+  let openingId: BN;
 
   // Fee estimation and transfer
-  const addWorkerOpeningFee: BN = apiWrapper.estimateAddWorkerOpeningFee();
-  await apiWrapper.transferBalance(sudo, lead.address, addWorkerOpeningFee);
+  const addOpeningFee: BN = apiWrapper.estimateAddOpeningFee();
+  await apiWrapper.transferBalance(sudo, lead.address, addOpeningFee);
 
   // Worker opening creation
-  workerOpeningId = await apiWrapper.getNextWorkerOpeningId();
+  openingId = await apiWrapper.getNextOpeningId();
   const activateAtBlock: BN | undefined = activationDelay.eqn(0)
     ? undefined
     : (await apiWrapper.getBestBlock()).add(activationDelay);
-  await apiWrapper.addWorkerOpening(
+  await apiWrapper.addOpening(
     activateAtBlock,
     lead,
     new BN(membersKeyPairs.length),
@@ -54,33 +53,71 @@ export async function addWorkerOpening(
     new BN(1),
     new BN(1),
     new BN(1),
-    uuid().substring(0, 8)
+    uuid().substring(0, 8),
+    'Worker'
   );
 
-  return workerOpeningId;
+  return openingId;
 }
 
-export async function acceptWorkerApplications(
+export async function addLeaderOpening(
   apiWrapper: ApiWrapper,
-  lead: KeyringPair,
+  membersKeyPairs: KeyringPair[],
   sudo: KeyringPair,
-  workerOpeningId: BN
-) {
+  applicationStake: BN,
+  roleStake: BN,
+  activationDelay: BN
+): Promise<BN> {
+  let openingId: BN;
+
+  // Leader opening creation
+  openingId = await apiWrapper.getNextOpeningId();
+  const activateAtBlock: BN | undefined = activationDelay.eqn(0)
+    ? undefined
+    : (await apiWrapper.getBestBlock()).add(activationDelay);
+  await apiWrapper.sudoAddOpening(
+    activateAtBlock,
+    sudo,
+    new BN(membersKeyPairs.length),
+    new BN(32),
+    new BN(applicationStake),
+    new BN(0),
+    new BN(0),
+    new BN(roleStake),
+    new BN(0),
+    new BN(0),
+    new BN(1),
+    new BN(100),
+    new BN(1),
+    new BN(1),
+    new BN(1),
+    new BN(1),
+    new BN(1),
+    new BN(1),
+    new BN(1),
+    uuid().substring(0, 8),
+    'Leader'
+  );
+
+  return openingId;
+}
+
+export async function acceptApplications(apiWrapper: ApiWrapper, lead: KeyringPair, sudo: KeyringPair, openingId: BN) {
   // Fee estimation and transfer
-  const acceptWorkerApplicationsFee = apiWrapper.estimateAcceptWorkerApplicationsFee();
-  await apiWrapper.transferBalance(sudo, lead.address, acceptWorkerApplicationsFee);
+  const acceptApplicationsFee = apiWrapper.estimateAcceptApplicationsFee();
+  await apiWrapper.transferBalance(sudo, lead.address, acceptApplicationsFee);
 
   // Begin accepting applications
-  await apiWrapper.acceptWorkerApplications(lead, workerOpeningId);
+  await apiWrapper.acceptApplications(lead, openingId);
 }
 
-export async function applyForWorkerOpening(
+export async function applyForOpening(
   apiWrapper: ApiWrapper,
   membersKeyPairs: KeyringPair[],
   sudo: KeyringPair,
   applicationStake: BN,
   roleStake: BN,
-  workerOpeningId: BN,
+  openingId: BN,
   expectFailure: boolean
 ): Promise<BN> {
   let nextApplicationId: BN;
@@ -91,9 +128,9 @@ export async function applyForWorkerOpening(
 
   // Applying for created worker opening
   nextApplicationId = await apiWrapper.getNextApplicationId();
-  await apiWrapper.batchApplyOnWorkerOpening(
+  await apiWrapper.batchApplyOnOpening(
     membersKeyPairs,
-    workerOpeningId,
+    openingId,
     roleStake,
     applicationStake,
     uuid().substring(0, 8),
@@ -103,21 +140,17 @@ export async function applyForWorkerOpening(
   return nextApplicationId;
 }
 
-export async function withdrawWorkerApplicaiton(
-  apiWrapper: ApiWrapper,
-  membersKeyPairs: KeyringPair[],
-  sudo: KeyringPair
-) {
+export async function withdrawApplicaiton(apiWrapper: ApiWrapper, membersKeyPairs: KeyringPair[], sudo: KeyringPair) {
   // Fee estimation and transfer
-  const withdrawApplicaitonFee: BN = apiWrapper.estimateWithdrawWorkerApplicationFee();
+  const withdrawApplicaitonFee: BN = apiWrapper.estimateWithdrawApplicationFee();
   await apiWrapper.transferBalanceToAccounts(sudo, membersKeyPairs, withdrawApplicaitonFee);
 
   // Application withdrawal
-  await apiWrapper.batchWithdrawWorkerApplication(membersKeyPairs);
+  await apiWrapper.batchWithdrawApplication(membersKeyPairs);
 
   // Assertions
   membersKeyPairs.forEach(async keyPair => {
-    const activeApplications: BN[] = await apiWrapper.getActiveWorkerApplicationsIdsByRoleAccount(keyPair.address);
+    const activeApplications: BN[] = await apiWrapper.getActiveApplicationsIdsByRoleAccount(keyPair.address);
     assert(activeApplications.length === 0, `Unexpected active application found for ${keyPair.address}`);
   });
 }
@@ -126,62 +159,83 @@ export async function beginApplicationReview(
   apiWrapper: ApiWrapper,
   lead: KeyringPair,
   sudo: KeyringPair,
-  workerOpeningId: BN
+  openingId: BN
 ) {
   // Fee estimation and transfer
-  const beginReviewFee: BN = apiWrapper.estimateBeginWorkerApplicantReviewFee();
+  const beginReviewFee: BN = apiWrapper.estimateBeginApplicantReviewFee();
   await apiWrapper.transferBalance(sudo, lead.address, beginReviewFee);
 
   // Begin application review
-  await apiWrapper.beginWorkerApplicationReview(lead, workerOpeningId);
+  await apiWrapper.beginApplicantReview(lead, openingId);
 }
 
-export async function fillWorkerOpening(
+export async function beginLeaderApplicationReview(apiWrapper: ApiWrapper, sudo: KeyringPair, openingId: BN) {
+  // Begin application review
+  await apiWrapper.sudoBeginApplicantReview(sudo, openingId);
+}
+
+export async function fillOpening(
   apiWrapper: ApiWrapper,
   membersKeyPairs: KeyringPair[],
   lead: KeyringPair,
   sudo: KeyringPair,
-  workerOpeningId: BN
+  openingId: BN
 ) {
   // Fee estimation and transfer
-  const beginReviewFee: BN = apiWrapper.estimateBeginWorkerApplicantReviewFee();
+  const beginReviewFee: BN = apiWrapper.estimateBeginApplicantReviewFee();
   await apiWrapper.transferBalance(sudo, lead.address, beginReviewFee);
   const applicationIds: BN[] = (
     await Promise.all(
-      membersKeyPairs.map(async keypair => apiWrapper.getActiveWorkerApplicationsIdsByRoleAccount(keypair.address))
+      membersKeyPairs.map(async keypair => apiWrapper.getActiveApplicationsIdsByRoleAccount(keypair.address))
     )
   ).flat();
 
   // Fill worker opening
-  await apiWrapper.fillWorkerOpening(
-    lead,
-    workerOpeningId,
-    applicationIds,
-    new BN(1),
-    new BN(99999999),
-    new BN(99999999)
-  );
+  await apiWrapper.fillOpening(lead, openingId, applicationIds, new BN(1), new BN(99999999), new BN(99999999));
 
   // Assertions
-  const workerOpening: WorkerOpening = await apiWrapper.getWorkerOpening(workerOpeningId);
   const openingWorkersAccounts: string[] = (await apiWrapper.getWorkers()).map(worker =>
-    worker.role_account.toString()
+    worker.role_account_id.toString()
   );
   membersKeyPairs.forEach(keyPair =>
     assert(openingWorkersAccounts.includes(keyPair.address), `Account ${keyPair.address} is not worker`)
   );
 }
 
-export async function increaseWorkerStake(apiWrapper: ApiWrapper, membersKeyPairs: KeyringPair[], sudo: KeyringPair) {
+export async function fillLeaderOpening(
+  apiWrapper: ApiWrapper,
+  membersKeyPairs: KeyringPair[],
+  sudo: KeyringPair,
+  openingId: BN
+) {
+  const applicationIds: BN[] = (
+    await Promise.all(
+      membersKeyPairs.map(async keypair => apiWrapper.getActiveApplicationsIdsByRoleAccount(keypair.address))
+    )
+  ).flat();
+
+  // Fill worker opening
+  await apiWrapper.sudoFillOpening(sudo, openingId, applicationIds, new BN(1), new BN(99999999), new BN(99999999));
+
+  // Assertions
+  const openingWorkersAccounts: string[] = (await apiWrapper.getWorkers()).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(apiWrapper: ApiWrapper, membersKeyPairs: KeyringPair[], sudo: KeyringPair) {
   // Fee estimation and transfer
-  const increaseStakeFee: BN = apiWrapper.estimateIncreaseWorkerStakeFee();
+  const increaseStakeFee: BN = apiWrapper.estimateIncreaseStakeFee();
   const stakeIncrement: BN = new BN(1);
   await apiWrapper.transferBalance(sudo, membersKeyPairs[0].address, increaseStakeFee.add(stakeIncrement));
   const workerId: BN = await apiWrapper.getWorkerIdByRoleAccount(membersKeyPairs[0].address);
 
   // Increase worker stake
   const increasedWorkerStake: BN = (await apiWrapper.getWorkerStakeAmount(workerId)).add(stakeIncrement);
-  await apiWrapper.increaseWorkerStake(membersKeyPairs[0], workerId, stakeIncrement);
+  await apiWrapper.increaseStake(membersKeyPairs[0], workerId, stakeIncrement);
   const newWorkerStake: BN = await apiWrapper.getWorkerStakeAmount(workerId);
   assert(
     increasedWorkerStake.eq(newWorkerStake),
@@ -224,7 +278,7 @@ export async function updateRoleAccount(
   // Update role account
   const createdAccount: KeyringPair = keyring.addFromUri(uuid().substring(0, 8));
   await apiWrapper.updateRoleAccount(membersKeyPairs[0], workerId, createdAccount.address);
-  const newRoleAccount: string = (await apiWrapper.getWorker(workerId)).role_account.toString();
+  const newRoleAccount: string = (await apiWrapper.getWorker(workerId)).role_account_id.toString();
   assert(
     newRoleAccount === createdAccount.address,
     `Unexpected role account ${newRoleAccount}, expected ${createdAccount.address}`
@@ -233,21 +287,21 @@ export async function updateRoleAccount(
   membersKeyPairs[0] = createdAccount;
 }
 
-export async function terminateWorkerApplications(
+export async function terminateApplications(
   apiWrapper: ApiWrapper,
   membersKeyPairs: KeyringPair[],
   lead: KeyringPair,
   sudo: KeyringPair
 ) {
   // Fee estimation and transfer
-  const terminateWorkerApplicationFee = apiWrapper.estimateTerminateWorkerApplicationFee();
-  await apiWrapper.transferBalance(sudo, lead.address, terminateWorkerApplicationFee.muln(membersKeyPairs.length));
+  const terminateApplicationFee = apiWrapper.estimateTerminateApplicationFee();
+  await apiWrapper.transferBalance(sudo, lead.address, terminateApplicationFee.muln(membersKeyPairs.length));
 
   // Terminate worker applications
-  await apiWrapper.batchTerminateWorkerApplication(lead, membersKeyPairs);
+  await apiWrapper.batchTerminateApplication(lead, membersKeyPairs);
 }
 
-export async function decreaseWorkerStake(
+export async function decreaseStake(
   apiWrapper: ApiWrapper,
   membersKeyPairs: KeyringPair[],
   lead: KeyringPair,
@@ -255,14 +309,14 @@ export async function decreaseWorkerStake(
   expectFailure: boolean
 ) {
   // Fee estimation and transfer
-  const decreaseWorkerStakeFee = apiWrapper.estimateDecreaseWorkerStakeFee();
-  await apiWrapper.transferBalance(sudo, lead.address, decreaseWorkerStakeFee);
+  const decreaseStakeFee = apiWrapper.estimateDecreaseStakeFee();
+  await apiWrapper.transferBalance(sudo, lead.address, decreaseStakeFee);
   const workerStakeDecrement = new BN(1);
   const workerId: BN = await apiWrapper.getWorkerIdByRoleAccount(membersKeyPairs[0].address);
 
   // Worker stake decrement
   const decreasedWorkerStake: BN = (await apiWrapper.getWorkerStakeAmount(workerId)).sub(workerStakeDecrement);
-  await apiWrapper.decreaseWorkerStake(lead, workerId, workerStakeDecrement, expectFailure);
+  await apiWrapper.decreaseStake(lead, workerId, workerStakeDecrement, expectFailure);
   const newWorkerStake: BN = await apiWrapper.getWorkerStakeAmount(workerId);
 
   // Assertions
@@ -274,7 +328,7 @@ export async function decreaseWorkerStake(
   }
 }
 
-export async function slashWorker(
+export async function slash(
   apiWrapper: ApiWrapper,
   membersKeyPairs: KeyringPair[],
   lead: KeyringPair,
@@ -282,24 +336,21 @@ export async function slashWorker(
   expectFailure: boolean
 ) {
   // Fee estimation and transfer
-  const slashWorkerStakeFee = apiWrapper.estimateSlashWorkerStakeFee();
-  await apiWrapper.transferBalance(sudo, lead.address, slashWorkerStakeFee);
+  const slashStakeFee = apiWrapper.estimateSlashStakeFee();
+  await apiWrapper.transferBalance(sudo, lead.address, slashStakeFee);
   const slashAmount = new BN(1);
   const workerId: BN = await apiWrapper.getWorkerIdByRoleAccount(membersKeyPairs[0].address);
 
   // Slash worker
-  const slashedWorkerStake: BN = (await apiWrapper.getWorkerStakeAmount(workerId)).sub(slashAmount);
-  await apiWrapper.slashWorkerStake(lead, workerId, slashAmount, expectFailure);
-  const newWorkerStake: BN = await apiWrapper.getWorkerStakeAmount(workerId);
+  const slashedStake: BN = (await apiWrapper.getWorkerStakeAmount(workerId)).sub(slashAmount);
+  await apiWrapper.slashStake(lead, workerId, slashAmount, expectFailure);
+  const newStake: BN = await apiWrapper.getWorkerStakeAmount(workerId);
 
   // Assertions
-  assert(
-    slashedWorkerStake.eq(newWorkerStake),
-    `Unexpected worker stake ${newWorkerStake}, expected ${slashedWorkerStake}`
-  );
+  assert(slashedStake.eq(newStake), `Unexpected worker stake ${newStake}, expected ${slashedStake}`);
 }
 
-export async function terminateWorkerRole(
+export async function terminateRole(
   apiWrapper: ApiWrapper,
   membersKeyPairs: KeyringPair[],
   lead: KeyringPair,
@@ -307,15 +358,23 @@ export async function terminateWorkerRole(
   expectFailure: boolean
 ) {
   // Fee estimation and transfer
-  const terminateWorkerRoleFee = apiWrapper.estimateTerminateWorkerRoleFee();
-  await apiWrapper.transferBalance(sudo, lead.address, terminateWorkerRoleFee);
+  const terminateRoleFee = apiWrapper.estimateTerminateRoleFee();
+  await apiWrapper.transferBalance(sudo, lead.address, terminateRoleFee);
   const workerId: BN = await apiWrapper.getWorkerIdByRoleAccount(membersKeyPairs[0].address);
 
   // Slash worker
-  await apiWrapper.terminateWorkerRole(lead, workerId, uuid().substring(0, 8), expectFailure);
+  await apiWrapper.terminateRole(lead, workerId, uuid().substring(0, 8), expectFailure);
 
   // Assertions
   apiWrapper.getWorkerIdByRoleAccount(membersKeyPairs[0].address);
   const newWorkerId = await apiWrapper.getWorkerIdByRoleAccount(membersKeyPairs[0].address);
   assert(newWorkerId === undefined, `Worker with account ${membersKeyPairs[0].address} is not terminated`);
 }
+
+export async function leaveRole(apiWrapper: ApiWrapper, membersKeyPairs: KeyringPair[], sudo: KeyringPair) {
+  // Fee estimation and transfer
+  const leaveRoleFee = apiWrapper.estimateLeaveRoleFee();
+  await apiWrapper.transferBalanceToAccounts(sudo, membersKeyPairs, leaveRoleFee);
+
+  await apiWrapper.batchLeaveRole(membersKeyPairs, uuid().substring(0, 8), false);
+}

+ 37 - 18
tests/network-tests/src/nicaea/tests/bureaucracy/manageWorkerAsLeadTest.ts → tests/network-tests/src/nicaea/tests/workingGroup/manageWorkerAsLeadTest.ts

@@ -9,14 +9,18 @@ import { setTestTimeout } from '../../utils/setTestTimeout';
 import { membershipTest } from '../impl/membershipCreation';
 import {
   addWorkerOpening,
-  applyForWorkerOpening,
+  applyForOpening,
   beginApplicationReview,
-  fillWorkerOpening,
+  fillOpening,
   setLead,
   unsetLead,
-  decreaseWorkerStake,
-  slashWorker,
-  terminateWorkerRole,
+  decreaseStake,
+  slash,
+  terminateRole,
+  addLeaderOpening,
+  beginLeaderApplicationReview,
+  fillLeaderOpening,
+  leaveRole,
 } from './impl/workingGroupModule';
 import BN from 'bn.js';
 
@@ -45,7 +49,26 @@ tap.mocha.describe('Manage worker as worker scenario', async () => {
   membershipTest(apiWrapper, nKeyPairs, keyring, N, paidTerms, sudoUri);
   membershipTest(apiWrapper, leadKeyPair, keyring, N, paidTerms, sudoUri);
 
-  tap.test('Set lead', async () => setLead(apiWrapper, leadKeyPair[0], sudo));
+  let leadOpenignId: BN;
+  tap.test(
+    'Add lead opening',
+    async () =>
+      (leadOpenignId = await addLeaderOpening(
+        apiWrapper,
+        nKeyPairs,
+        sudo,
+        applicationStake,
+        roleStake,
+        openingActivationDelay
+      ))
+  );
+  tap.test(
+    'Apply for lead opening',
+    async () => await applyForOpening(apiWrapper, leadKeyPair, sudo, applicationStake, roleStake, leadOpenignId, false)
+  );
+  tap.test('Begin lead application review', async () => beginLeaderApplicationReview(apiWrapper, sudo, leadOpenignId));
+  tap.test('Fill lead opening', async () => fillLeaderOpening(apiWrapper, leadKeyPair, sudo, leadOpenignId));
+
   let openignId: BN;
   tap.test(
     'Add worker opening',
@@ -62,25 +85,21 @@ tap.mocha.describe('Manage worker as worker scenario', async () => {
   );
   tap.test(
     'Apply for worker opening',
-    async () => await applyForWorkerOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, false)
+    async () => await applyForOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, false)
   );
   tap.test('Begin application review', async () => beginApplicationReview(apiWrapper, leadKeyPair[0], sudo, openignId));
-  tap.test('Fill worker opening', async () =>
-    fillWorkerOpening(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, openignId)
-  );
+  tap.test('Fill worker opening', async () => fillOpening(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, openignId));
 
   tap.test('Unset lead', async () => unsetLead(apiWrapper, sudo));
   tap.test('Decrease worker stake, expect failure', async () =>
-    decreaseWorkerStake(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, true)
+    decreaseStake(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, true)
   );
   tap.test('Set lead', async () => setLead(apiWrapper, leadKeyPair[0], sudo));
-  tap.test('Decrease worker stake', async () =>
-    decreaseWorkerStake(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, false)
-  );
-  tap.test('Slash worker', async () => slashWorker(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, false));
-  tap.test('Terminate worker role', async () =>
-    terminateWorkerRole(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, false)
-  );
+  tap.test('Decrease worker stake', async () => decreaseStake(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, false));
+  tap.test('Slash worker', async () => slash(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, false));
+  tap.test('Terminate worker role', async () => terminateRole(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, false));
+
+  tap.test('Leaving lead role', async () => leaveRole(apiWrapper, leadKeyPair, sudo));
 
   closeApi(apiWrapper);
 });

+ 32 - 10
tests/network-tests/src/nicaea/tests/bureaucracy/manageWorkerAsWorkerTest.ts → tests/network-tests/src/nicaea/tests/workingGroup/manageWorkerAsWorkerTest.ts

@@ -9,13 +9,16 @@ import { setTestTimeout } from '../../utils/setTestTimeout';
 import { membershipTest } from '../impl/membershipCreation';
 import {
   addWorkerOpening,
-  applyForWorkerOpening,
+  applyForOpening,
   beginApplicationReview,
-  fillWorkerOpening,
-  setLead,
-  increaseWorkerStake,
+  fillOpening,
+  increaseStake,
   updateRewardAccount,
   updateRoleAccount,
+  addLeaderOpening,
+  beginLeaderApplicationReview,
+  fillLeaderOpening,
+  leaveRole,
 } from './impl/workingGroupModule';
 import BN from 'bn.js';
 
@@ -44,7 +47,26 @@ tap.mocha.describe('Manage worker as worker scenario', async () => {
   membershipTest(apiWrapper, nKeyPairs, keyring, N, paidTerms, sudoUri);
   membershipTest(apiWrapper, leadKeyPair, keyring, N, paidTerms, sudoUri);
 
-  tap.test('Set lead', async () => setLead(apiWrapper, leadKeyPair[0], sudo));
+  let leadOpenignId: BN;
+  tap.test(
+    'Add lead opening',
+    async () =>
+      (leadOpenignId = await addLeaderOpening(
+        apiWrapper,
+        nKeyPairs,
+        sudo,
+        applicationStake,
+        roleStake,
+        openingActivationDelay
+      ))
+  );
+  tap.test(
+    'Apply for lead opening',
+    async () => await applyForOpening(apiWrapper, leadKeyPair, sudo, applicationStake, roleStake, leadOpenignId, false)
+  );
+  tap.test('Begin lead application review', async () => beginLeaderApplicationReview(apiWrapper, sudo, leadOpenignId));
+  tap.test('Fill lead opening', async () => fillLeaderOpening(apiWrapper, leadKeyPair, sudo, leadOpenignId));
+
   let openignId: BN;
   tap.test(
     'Add worker opening',
@@ -61,16 +83,16 @@ tap.mocha.describe('Manage worker as worker scenario', async () => {
   );
   tap.test(
     'Apply for worker opening',
-    async () => await applyForWorkerOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, false)
+    async () => await applyForOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, false)
   );
   tap.test('Begin application review', async () => beginApplicationReview(apiWrapper, leadKeyPair[0], sudo, openignId));
-  tap.test('Fill worker opening', async () =>
-    fillWorkerOpening(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, openignId)
-  );
+  tap.test('Fill worker opening', async () => fillOpening(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, openignId));
 
-  tap.test('Increase worker stake', async () => increaseWorkerStake(apiWrapper, nKeyPairs, sudo));
+  tap.test('Increase worker stake', async () => increaseStake(apiWrapper, nKeyPairs, sudo));
   tap.test('Update reward account', async () => updateRewardAccount(apiWrapper, nKeyPairs, keyring, sudo));
   tap.test('Update role account', async () => updateRoleAccount(apiWrapper, nKeyPairs, keyring, sudo));
 
+  tap.test('Leaving lead role', async () => leaveRole(apiWrapper, leadKeyPair, sudo));
+
   closeApi(apiWrapper);
 });

+ 39 - 14
tests/network-tests/src/nicaea/tests/bureaucracy/workerApplicationHappyCaseTest.ts → tests/network-tests/src/nicaea/tests/workingGroup/workerApplicationHappyCaseTest.ts

@@ -9,11 +9,14 @@ import { setTestTimeout } from '../../utils/setTestTimeout';
 import { membershipTest } from '../impl/membershipCreation';
 import {
   addWorkerOpening,
-  applyForWorkerOpening,
+  applyForOpening,
   beginApplicationReview,
-  fillWorkerOpening,
-  setLead,
-  withdrawWorkerApplicaiton,
+  fillOpening,
+  withdrawApplicaiton,
+  addLeaderOpening,
+  beginLeaderApplicationReview,
+  fillLeaderOpening,
+  leaveRole,
 } from './impl/workingGroupModule';
 import BN from 'bn.js';
 
@@ -31,7 +34,7 @@ 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 durationInBlocks: number = 28;
+  const durationInBlocks: number = 48;
   const openingActivationDelay: BN = new BN(0);
 
   const provider = new WsProvider(nodeUrl);
@@ -40,14 +43,33 @@ tap.mocha.describe('Worker application happy case scenario', async () => {
 
   setTestTimeout(apiWrapper, durationInBlocks);
   membershipTest(apiWrapper, nKeyPairs, keyring, N, paidTerms, sudoUri);
-  membershipTest(apiWrapper, leadKeyPair, keyring, N, paidTerms, sudoUri);
+  membershipTest(apiWrapper, leadKeyPair, keyring, 1, paidTerms, sudoUri);
 
-  tap.test('Set lead', async () => setLead(apiWrapper, leadKeyPair[0], sudo));
-  let openignId: BN;
+  let leadOpenignId: BN;
+  tap.test(
+    'Add lead opening',
+    async () =>
+      (leadOpenignId = await addLeaderOpening(
+        apiWrapper,
+        nKeyPairs,
+        sudo,
+        applicationStake,
+        roleStake,
+        openingActivationDelay
+      ))
+  );
+  tap.test(
+    'Apply for lead opening',
+    async () => await applyForOpening(apiWrapper, leadKeyPair, sudo, applicationStake, roleStake, leadOpenignId, false)
+  );
+  tap.test('Begin lead application review', async () => beginLeaderApplicationReview(apiWrapper, sudo, leadOpenignId));
+  tap.test('Fill lead opening', async () => fillLeaderOpening(apiWrapper, leadKeyPair, sudo, leadOpenignId));
+
+  let workerOpenignId: BN;
   tap.test(
     'Add worker opening',
     async () =>
-      (openignId = await addWorkerOpening(
+      (workerOpenignId = await addWorkerOpening(
         apiWrapper,
         nKeyPairs,
         leadKeyPair[0],
@@ -58,16 +80,19 @@ tap.mocha.describe('Worker application happy case scenario', async () => {
       ))
   );
   tap.test('Apply for worker opening', async () =>
-    applyForWorkerOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, false)
+    applyForOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, workerOpenignId, false)
   );
-  tap.test('Withdraw worker application', async () => withdrawWorkerApplicaiton(apiWrapper, nKeyPairs, sudo));
+  tap.test('Withdraw worker application', async () => withdrawApplicaiton(apiWrapper, nKeyPairs, sudo));
   tap.test('Apply for worker opening', async () =>
-    applyForWorkerOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, false)
+    applyForOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, workerOpenignId, false)
+  );
+  tap.test('Begin application review', async () =>
+    beginApplicationReview(apiWrapper, leadKeyPair[0], sudo, workerOpenignId)
   );
-  tap.test('Begin application review', async () => beginApplicationReview(apiWrapper, leadKeyPair[0], sudo, openignId));
   tap.test('Fill worker opening', async () =>
-    fillWorkerOpening(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, openignId)
+    fillOpening(apiWrapper, nKeyPairs, leadKeyPair[0], sudo, workerOpenignId)
   );
+  tap.test('Leaving lead role', async () => leaveRole(apiWrapper, leadKeyPair, sudo));
 
   closeApi(apiWrapper);
 });

+ 34 - 10
tests/network-tests/src/nicaea/tests/bureaucracy/workerApplicationRejectionCaseTest.ts → tests/network-tests/src/nicaea/tests/workingGroup/workerApplicationRejectionCaseTest.ts

@@ -9,11 +9,14 @@ import { setTestTimeout } from '../../utils/setTestTimeout';
 import { membershipTest, createKeyPairs } from '../impl/membershipCreation';
 import {
   addWorkerOpening,
-  applyForWorkerOpening,
-  setLead,
-  acceptWorkerApplications,
-  terminateWorkerApplications,
+  applyForOpening,
+  acceptApplications,
+  terminateApplications,
   unsetLead,
+  addLeaderOpening,
+  beginLeaderApplicationReview,
+  fillLeaderOpening,
+  leaveRole,
 } from './impl/workingGroupModule';
 import BN from 'bn.js';
 
@@ -43,7 +46,26 @@ tap.mocha.describe('Worker application happy case scenario', async () => {
   membershipTest(apiWrapper, nKeyPairs, keyring, N, paidTerms, sudoUri);
   membershipTest(apiWrapper, leadKeyPair, keyring, N, paidTerms, sudoUri);
 
-  tap.test('Set lead', async () => setLead(apiWrapper, leadKeyPair[0], sudo));
+  let leadOpenignId: BN;
+  tap.test(
+    'Add lead opening',
+    async () =>
+      (leadOpenignId = await addLeaderOpening(
+        apiWrapper,
+        nKeyPairs,
+        sudo,
+        applicationStake,
+        roleStake,
+        openingActivationDelay
+      ))
+  );
+  tap.test(
+    'Apply for lead opening',
+    async () => await applyForOpening(apiWrapper, leadKeyPair, sudo, applicationStake, roleStake, leadOpenignId, false)
+  );
+  tap.test('Begin lead application review', async () => beginLeaderApplicationReview(apiWrapper, sudo, leadOpenignId));
+  tap.test('Fill lead opening', async () => fillLeaderOpening(apiWrapper, leadKeyPair, sudo, leadOpenignId));
+
   let openignId: BN;
   tap.test(
     'Add worker opening',
@@ -59,21 +81,23 @@ tap.mocha.describe('Worker application happy case scenario', async () => {
       ))
   );
   tap.test('Apply for worker opening, expect failure', async () =>
-    applyForWorkerOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, true)
+    applyForOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, true)
   );
   tap.test('Begin accepting worker applications', async () =>
-    acceptWorkerApplications(apiWrapper, leadKeyPair[0], sudo, openignId)
+    acceptApplications(apiWrapper, leadKeyPair[0], sudo, openignId)
   );
   tap.test('Apply for worker opening as non-member, expect failure', async () =>
-    applyForWorkerOpening(apiWrapper, nonMemberKeyPairs, sudo, applicationStake, roleStake, openignId, true)
+    applyForOpening(apiWrapper, nonMemberKeyPairs, sudo, applicationStake, roleStake, openignId, true)
   );
   tap.test('Apply for worker opening as member', async () =>
-    applyForWorkerOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, false)
+    applyForOpening(apiWrapper, nKeyPairs, sudo, applicationStake, roleStake, openignId, false)
   );
   tap.test('Terminate worker applicaitons', async () =>
-    terminateWorkerApplications(apiWrapper, nKeyPairs, leadKeyPair[0], sudo)
+    terminateApplications(apiWrapper, nKeyPairs, leadKeyPair[0], sudo)
   );
   tap.test('Unset lead', async () => unsetLead(apiWrapper, sudo));
 
+  tap.test('Leaving lead role', async () => leaveRole(apiWrapper, leadKeyPair, sudo));
+
   closeApi(apiWrapper);
 });

+ 186 - 112
tests/network-tests/src/nicaea/utils/apiWrapper.ts

@@ -2,26 +2,21 @@ import { ApiPromise, WsProvider } from '@polkadot/api';
 import { Option, Vec, Bytes, u32 } from '@polkadot/types';
 import { Codec } from '@polkadot/types/types';
 import { KeyringPair } from '@polkadot/keyring/types';
-import { UserInfo, PaidMembershipTerms, MemberId } from '@nicaea/types/lib/members';
-import { Mint, MintId } from '@nicaea/types/lib/mint';
-import { Lead, LeadId } from '@nicaea/types/lib/content-working-group';
-import {
-  WorkerOpening,
-  Worker,
-  WorkerId,
-  WorkerApplication,
-  WorkerApplicationId,
-} from '@nicaea/types/lib/working-group';
-import { RoleParameters } from '@nicaea/types/lib/roles';
+import { UserInfo, PaidMembershipTerms, MemberId } from '@nicaea/types/members';
+import { Mint, MintId } from '@nicaea/types/mint';
+import { Lead, LeadId } from '@nicaea/types/content-working-group';
+import { Application, WorkerId, Worker } from '@nicaea/types/working-group';
+import { Application as HiringApplication } from '@nicaea/types/hiring';
+import { RoleParameters } from '@nicaea/types/roles';
 import { Seat } from '@nicaea/types/lib/council';
 import { Balance, EventRecord, AccountId, BlockNumber, BalanceOf } from '@polkadot/types/interfaces';
 import BN from 'bn.js';
 import { SubmittableExtrinsic } from '@polkadot/api/types';
 import { Sender } from './sender';
 import { Utils } from './utils';
-import { Stake, StakedState } from '@nicaea/types/lib/stake';
-import { RewardRelationship } from '@nicaea/types/lib/recurring-rewards';
-import { Application } from '@nicaea/types/lib/hiring';
+import { Stake, StakedState } from '@nicaea/types/stake';
+import { RewardRelationship } from '@nicaea/types/recurring-rewards';
+import { Opening, ApplicationId } from '@nicaea/types/hiring';
 
 export class ApiWrapper {
   private readonly api: ApiPromise;
@@ -226,9 +221,9 @@ export class ApiWrapper {
     return this.estimateTxFee(this.api.tx.proposalsEngine.vote(0, 0, 'Approve'));
   }
 
-  public estimateAddWorkerOpeningFee(): BN {
+  public estimateAddOpeningFee(): BN {
     return this.estimateTxFee(
-      this.api.tx.storageWorkingGroup.addWorkerOpening(
+      this.api.tx.storageWorkingGroup.addOpening(
         'CurrentBlock',
         {
           application_rationing_policy: { max_active_applicants: '32' },
@@ -259,18 +254,19 @@ export class ApiWrapper {
           exit_curator_role_application_stake_unstaking_period: 0,
           exit_curator_role_stake_unstaking_period: 0,
         },
-        'Opening readable text'
+        'Opening readable text',
+        'Worker'
       )
     );
   }
 
-  public estimateAcceptWorkerApplicationsFee(): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.acceptWorkerApplications(0));
+  public estimateAcceptApplicationsFee(): BN {
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.acceptApplications(0));
   }
 
   public estimateApplyOnOpeningFee(account: KeyringPair): BN {
     return this.estimateTxFee(
-      this.api.tx.storageWorkingGroup.applyOnWorkerOpening(
+      this.api.tx.storageWorkingGroup.applyOnOpening(
         0,
         0,
         account.address,
@@ -281,13 +277,13 @@ export class ApiWrapper {
     );
   }
 
-  public estimateBeginWorkerApplicantReviewFee(): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.beginWorkerApplicantReview(0));
+  public estimateBeginApplicantReviewFee(): BN {
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.beginApplicantReview(0));
   }
 
-  public estimateFillWorkerOpeningFee(): BN {
+  public estimateFillOpeningFee(): BN {
     return this.estimateTxFee(
-      this.api.tx.storageWorkingGroup.fillWorkerOpening(0, [0], {
+      this.api.tx.storageWorkingGroup.fillOpening(0, [0], {
         amount_per_payout: 0,
         next_payment_at_block: 0,
         payout_interval: 0,
@@ -295,41 +291,41 @@ export class ApiWrapper {
     );
   }
 
-  public estimateIncreaseWorkerStakeFee(): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.increaseWorkerStake(0, 0));
+  public estimateIncreaseStakeFee(): BN {
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.increaseStake(0, 0));
   }
 
-  public estimateDecreaseWorkerStakeFee(): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.decreaseWorkerStake(0, 0));
+  public estimateDecreaseStakeFee(): BN {
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.decreaseStake(0, 0));
   }
 
   public estimateUpdateRoleAccountFee(address: string): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.updateWorkerRoleAccount(0, address));
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.updateRoleAccount(0, address));
   }
 
   public estimateUpdateRewardAccountFee(address: string): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.updateWorkerRewardAccount(0, address));
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.updateRewardAccount(0, address));
   }
 
-  public estimateLeaveWorkerRoleFee(text: string): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.leaveWorkerRole(0, text));
+  public estimateLeaveRoleFee(): BN {
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.leaveRole(0, 'Long justification text'));
   }
 
-  public estimateWithdrawWorkerApplicationFee(): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.withdrawWorkerApplication(0));
+  public estimateWithdrawApplicationFee(): BN {
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.withdrawApplication(0));
   }
 
-  public estimateTerminateWorkerApplicationFee(): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.terminateWorkerApplication(0));
+  public estimateTerminateApplicationFee(): BN {
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.terminateApplication(0));
   }
 
-  public estimateSlashWorkerStakeFee(): BN {
-    return this.estimateTxFee(this.api.tx.storageWorkingGroup.slashWorkerStake(0, 0));
+  public estimateSlashStakeFee(): BN {
+    return this.estimateTxFee(this.api.tx.storageWorkingGroup.slashStake(0, 0));
   }
 
-  public estimateTerminateWorkerRoleFee(): BN {
+  public estimateTerminateRoleFee(): BN {
     return this.estimateTxFee(
-      this.api.tx.storageWorkingGroup.terminateWorkerRole(
+      this.api.tx.storageWorkingGroup.terminateRole(
         0,
         'Long justification text explaining why the worker role will be terminated'
       )
@@ -739,7 +735,7 @@ export class ApiWrapper {
     return this.sender.signAndSend(this.api.tx.sudo.sudo(this.api.tx.storageWorkingGroup.unsetLead()), sudo, false);
   }
 
-  public async addWorkerOpening(
+  public async addOpening(
     activateAtBlock: BN | undefined,
     account: KeyringPair,
     maxActiveApplicants: BN,
@@ -759,7 +755,8 @@ export class ApiWrapper {
     terminateCuratorRoleStakeUnstakingPeriod: BN,
     exitCuratorRoleApplicationStakeUnstakingPeriod: BN,
     exitCuratorRoleStakeUnstakingPeriod: BN,
-    text: string
+    text: string,
+    openingType: string
   ): Promise<void> {
     const activateAt = activateAtBlock == undefined ? 'CurrentBlock' : { ExactBlock: activateAtBlock };
     const commitment = {
@@ -792,31 +789,91 @@ export class ApiWrapper {
       exit_curator_role_stake_unstaking_period: exitCuratorRoleStakeUnstakingPeriod,
     };
     await this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.addWorkerOpening(activateAt, commitment, text),
+      this.api.tx.storageWorkingGroup.addOpening(activateAt, commitment, text, openingType),
       account,
       false
     );
   }
 
-  public async acceptWorkerApplications(account: KeyringPair, workerOpeningId: BN): Promise<void> {
-    return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.acceptWorkerApplications(workerOpeningId),
-      account,
+  public async sudoAddOpening(
+    activateAtBlock: BN | undefined,
+    sudo: KeyringPair,
+    maxActiveApplicants: BN,
+    maxReviewPeriodLength: BN,
+    applicationStakingPolicyAmount: BN,
+    applicationCrowdedOutUnstakingPeriodLength: BN,
+    applicationExpiredUnstakingPeriodLength: BN,
+    roleStakingPolicyAmount: BN,
+    roleCrowdedOutUnstakingPeriodLength: BN,
+    roleExpiredUnstakingPeriodLength: BN,
+    slashableMaxCount: BN,
+    slashableMaxPercentPtsPerTime: BN,
+    successfulApplicantApplicationStakeUnstakingPeriod: BN,
+    failedApplicantApplicationStakeUnstakingPeriod: BN,
+    failedApplicantRoleStakeUnstakingPeriod: BN,
+    terminateCuratorApplicationStakeUnstakingPeriod: BN,
+    terminateCuratorRoleStakeUnstakingPeriod: BN,
+    exitCuratorRoleApplicationStakeUnstakingPeriod: BN,
+    exitCuratorRoleStakeUnstakingPeriod: BN,
+    text: string,
+    openingType: string
+  ): Promise<void> {
+    const activateAt = activateAtBlock == undefined ? 'CurrentBlock' : { ExactBlock: activateAtBlock };
+    const commitment = {
+      application_rationing_policy: { max_active_applicants: maxActiveApplicants },
+      max_review_period_length: maxReviewPeriodLength,
+      application_staking_policy: {
+        amount: applicationStakingPolicyAmount,
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: applicationCrowdedOutUnstakingPeriodLength,
+        review_period_expired_unstaking_period_length: applicationExpiredUnstakingPeriodLength,
+      },
+      role_staking_policy: {
+        amount: roleStakingPolicyAmount,
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: roleCrowdedOutUnstakingPeriodLength,
+        review_period_expired_unstaking_period_length: roleExpiredUnstakingPeriodLength,
+      },
+      role_slashing_terms: {
+        Slashable: {
+          max_count: slashableMaxCount,
+          max_percent_pts_per_time: slashableMaxPercentPtsPerTime,
+        },
+      },
+      fill_opening_successful_applicant_application_stake_unstaking_period: successfulApplicantApplicationStakeUnstakingPeriod,
+      fill_opening_failed_applicant_application_stake_unstaking_period: failedApplicantApplicationStakeUnstakingPeriod,
+      fill_opening_failed_applicant_role_stake_unstaking_period: failedApplicantRoleStakeUnstakingPeriod,
+      terminate_curator_application_stake_unstaking_period: terminateCuratorApplicationStakeUnstakingPeriod,
+      terminate_curator_role_stake_unstaking_period: terminateCuratorRoleStakeUnstakingPeriod,
+      exit_curator_role_application_stake_unstaking_period: exitCuratorRoleApplicationStakeUnstakingPeriod,
+      exit_curator_role_stake_unstaking_period: exitCuratorRoleStakeUnstakingPeriod,
+    };
+    await this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.api.tx.storageWorkingGroup.addOpening(activateAt, commitment, text, openingType)),
+      sudo,
       false
     );
   }
 
-  public async beginWorkerApplicationReview(account: KeyringPair, workerOpeningId: BN): Promise<void> {
+  public async acceptApplications(account: KeyringPair, openingId: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.storageWorkingGroup.acceptApplications(openingId), account, false);
+  }
+
+  public async beginApplicantReview(account: KeyringPair, openingId: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.storageWorkingGroup.beginApplicantReview(openingId), account, false);
+  }
+
+  public async sudoBeginApplicantReview(sudo: KeyringPair, openingId: BN): Promise<void> {
     return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.beginWorkerApplicantReview(workerOpeningId),
-      account,
+      this.api.tx.sudo.sudo(this.api.tx.storageWorkingGroup.beginApplicantReview(openingId)),
+      sudo,
       false
     );
   }
 
-  public async applyOnWorkerOpening(
+  public async applyOnOpening(
     account: KeyringPair,
-    workerOpeningId: BN,
+    openingId: BN,
     roleStake: BN,
     applicantStake: BN,
     text: string,
@@ -824,9 +881,9 @@ export class ApiWrapper {
   ): Promise<void> {
     const memberId: BN = (await this.getMemberIds(account.address))[0];
     return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.applyOnWorkerOpening(
+      this.api.tx.storageWorkingGroup.applyOnOpening(
         memberId,
-        workerOpeningId,
+        openingId,
         account.address,
         roleStake,
         applicantStake,
@@ -837,9 +894,9 @@ export class ApiWrapper {
     );
   }
 
-  public async batchApplyOnWorkerOpening(
+  public async batchApplyOnOpening(
     accounts: KeyringPair[],
-    workerOpeningId: BN,
+    openingId: BN,
     roleStake: BN,
     applicantStake: BN,
     text: string,
@@ -847,21 +904,21 @@ export class ApiWrapper {
   ): Promise<void[]> {
     return Promise.all(
       accounts.map(async keyPair => {
-        await this.applyOnWorkerOpening(keyPair, workerOpeningId, roleStake, applicantStake, text, expectFailure);
+        await this.applyOnOpening(keyPair, openingId, roleStake, applicantStake, text, expectFailure);
       })
     );
   }
 
-  public async fillWorkerOpening(
+  public async fillOpening(
     account: KeyringPair,
-    workerOpeningId: BN,
+    openingId: BN,
     applicationId: BN[],
     amountPerPayout: BN,
     nextPaymentBlock: BN,
     payoutInterval: BN
   ): Promise<void> {
     return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.fillWorkerOpening(workerOpeningId, applicationId, {
+      this.api.tx.storageWorkingGroup.fillOpening(openingId, applicationId, {
         amount_per_payout: amountPerPayout,
         next_payment_at_block: nextPaymentBlock,
         payout_interval: payoutInterval,
@@ -871,38 +928,46 @@ export class ApiWrapper {
     );
   }
 
-  public async increaseWorkerStake(account: KeyringPair, workerId: BN, stake: BN): Promise<void> {
+  public async sudoFillOpening(
+    sudo: KeyringPair,
+    openingId: BN,
+    applicationId: BN[],
+    amountPerPayout: BN,
+    nextPaymentBlock: BN,
+    payoutInterval: BN
+  ): Promise<void> {
     return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.increaseWorkerStake(workerId, stake),
-      account,
+      this.api.tx.sudo.sudo(
+        this.api.tx.storageWorkingGroup.fillOpening(openingId, applicationId, {
+          amount_per_payout: amountPerPayout,
+          next_payment_at_block: nextPaymentBlock,
+          payout_interval: payoutInterval,
+        })
+      ),
+      sudo,
       false
     );
   }
 
-  public async decreaseWorkerStake(
-    account: KeyringPair,
-    workerId: BN,
-    stake: BN,
-    expectFailure: boolean
-  ): Promise<void> {
-    return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.decreaseWorkerStake(workerId, stake),
-      account,
-      expectFailure
-    );
+  public async increaseStake(account: KeyringPair, workerId: BN, stake: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.storageWorkingGroup.increaseStake(workerId, stake), account, false);
   }
 
-  public async slashWorkerStake(account: KeyringPair, workerId: BN, stake: BN, expectFailure: boolean): Promise<void> {
+  public async decreaseStake(account: KeyringPair, workerId: BN, stake: BN, expectFailure: boolean): Promise<void> {
     return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.slashWorkerStake(workerId, stake),
+      this.api.tx.storageWorkingGroup.decreaseStake(workerId, stake),
       account,
       expectFailure
     );
   }
 
+  public async slashStake(account: KeyringPair, workerId: BN, stake: BN, expectFailure: boolean): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.storageWorkingGroup.slashStake(workerId, stake), account, expectFailure);
+  }
+
   public async updateRoleAccount(account: KeyringPair, workerId: BN, newRoleAccount: string): Promise<void> {
     return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.updateWorkerRoleAccount(workerId, newRoleAccount),
+      this.api.tx.storageWorkingGroup.updateRoleAccount(workerId, newRoleAccount),
       account,
       false
     );
@@ -910,55 +975,64 @@ export class ApiWrapper {
 
   public async updateRewardAccount(account: KeyringPair, workerId: BN, newRewardAccount: string): Promise<void> {
     return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.updateWorkerRewardAccount(workerId, newRewardAccount),
+      this.api.tx.storageWorkingGroup.updateRewardAccount(workerId, newRewardAccount),
       account,
       false
     );
   }
 
-  public async withdrawWorkerApplication(account: KeyringPair, workerId: BN): Promise<void> {
-    return this.sender.signAndSend(this.api.tx.storageWorkingGroup.withdrawWorkerApplication(workerId), account, false);
+  public async withdrawApplication(account: KeyringPair, workerId: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.storageWorkingGroup.withdrawApplication(workerId), account, false);
   }
 
-  public async batchWithdrawWorkerApplication(accounts: KeyringPair[]): Promise<void[]> {
+  public async batchWithdrawApplication(accounts: KeyringPair[]): Promise<void[]> {
     return Promise.all(
       accounts.map(async keyPair => {
-        const applicationIds: BN[] = await this.getWorkerApplicationsIdsByRoleAccount(keyPair.address);
-        await this.withdrawWorkerApplication(keyPair, applicationIds[0]);
+        const applicationIds: BN[] = await this.getApplicationsIdsByRoleAccount(keyPair.address);
+        await this.withdrawApplication(keyPair, applicationIds[0]);
       })
     );
   }
 
-  public async terminateWorkerApplication(account: KeyringPair, applicationId: BN): Promise<void> {
-    return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.terminateWorkerApplication(applicationId),
-      account,
-      false
-    );
+  public async terminateApplication(account: KeyringPair, applicationId: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.storageWorkingGroup.terminateApplication(applicationId), account, false);
   }
 
-  public async batchTerminateWorkerApplication(account: KeyringPair, roleAccounts: KeyringPair[]): Promise<void[]> {
+  public async batchTerminateApplication(account: KeyringPair, roleAccounts: KeyringPair[]): Promise<void[]> {
     return Promise.all(
       roleAccounts.map(async keyPair => {
-        const applicationIds: BN[] = await this.getActiveWorkerApplicationsIdsByRoleAccount(keyPair.address);
-        await this.terminateWorkerApplication(account, applicationIds[0]);
+        const applicationIds: BN[] = await this.getActiveApplicationsIdsByRoleAccount(keyPair.address);
+        await this.terminateApplication(account, applicationIds[0]);
       })
     );
   }
 
-  public async terminateWorkerRole(
+  public async terminateRole(
     account: KeyringPair,
     applicationId: BN,
     text: string,
     expectFailure: boolean
   ): Promise<void> {
     return this.sender.signAndSend(
-      this.api.tx.storageWorkingGroup.terminateWorkerRole(applicationId, text),
+      this.api.tx.storageWorkingGroup.terminateRole(applicationId, text),
       account,
       expectFailure
     );
   }
 
+  public async leaveRole(account: KeyringPair, text: string, expectFailure: boolean): Promise<void> {
+    const workerId: BN = await this.getWorkerIdByRoleAccount(account.address);
+    return this.sender.signAndSend(this.api.tx.storageWorkingGroup.leaveRole(workerId, text), account, expectFailure);
+  }
+
+  public async batchLeaveRole(roleAccounts: KeyringPair[], text: string, expectFailure: boolean): Promise<void[]> {
+    return Promise.all(
+      roleAccounts.map(async keyPair => {
+        await this.leaveRole(keyPair, text, expectFailure);
+      })
+    );
+  }
+
   public async getStorageRoleParameters(): Promise<RoleParameters> {
     return (await this.api.query.actors.parameters<Option<RoleParameters>>('StorageProvider')).unwrap();
   }
@@ -995,16 +1069,16 @@ export class ApiWrapper {
     return this.api.query.councilElection.minVotingStake<BalanceOf>();
   }
 
-  public async getNextWorkerOpeningId(): Promise<BN> {
-    return this.api.query.storageWorkingGroup.nextWorkerOpeningId<u32>();
+  public async getNextOpeningId(): Promise<BN> {
+    return this.api.query.storageWorkingGroup.nextOpeningId<u32>();
   }
 
   public async getNextApplicationId(): Promise<BN> {
-    return this.api.query.storageWorkingGroup.nextWorkerApplicationId<u32>();
+    return this.api.query.storageWorkingGroup.nextApplicationId<u32>();
   }
 
-  public async getWorkerOpening(id: BN): Promise<WorkerOpening> {
-    return ((await this.api.query.storageWorkingGroup.workerOpeningById<Codec[]>(id))[0] as unknown) as WorkerOpening;
+  public async getOpening(id: BN): Promise<Opening> {
+    return ((await this.api.query.storageWorkingGroup.openingById<Codec[]>(id))[0] as unknown) as Opening;
   }
 
   public async getWorkers(): Promise<Worker[]> {
@@ -1021,33 +1095,33 @@ export class ApiWrapper {
     const ids: WorkerId[] = (workersAndIds[0] as unknown) as WorkerId[];
     let index: number;
     workers.forEach((worker, i) => {
-      if (worker.role_account.toString() === address) index = i;
+      if (worker.role_account_id.toString() === address) index = i;
     });
     return ids[index!];
   }
 
-  public async getWorkerApplicationsIdsByRoleAccount(address: string): Promise<BN[]> {
-    const applicationsAndIds = await this.api.query.storageWorkingGroup.workerApplicationById<Codec[]>();
-    const applications: WorkerApplication[] = (applicationsAndIds[1] as unknown) as WorkerApplication[];
-    const ids: WorkerApplicationId[] = (applicationsAndIds[0] as unknown) as WorkerApplicationId[];
+  public async getApplicationsIdsByRoleAccount(address: string): Promise<BN[]> {
+    const applicationsAndIds = await this.api.query.storageWorkingGroup.applicationById<Codec[]>();
+    const applications: Application[] = (applicationsAndIds[1] as unknown) as Application[];
+    const ids: ApplicationId[] = (applicationsAndIds[0] as unknown) as ApplicationId[];
     return applications
-      .map((application, index) => (application.role_account.toString() === address ? ids[index] : undefined))
+      .map((application, index) => (application.role_account_id.toString() === address ? ids[index] : undefined))
       .filter(index => index !== undefined) as BN[];
   }
 
-  public async getApplicationById(id: BN): Promise<Application> {
-    return ((await this.api.query.hiring.applicationById<Codec[]>(id))[0] as unknown) as Application;
+  public async getApplicationById(id: BN): Promise<HiringApplication> {
+    return ((await this.api.query.hiring.applicationById<Codec[]>(id))[0] as unknown) as HiringApplication;
   }
 
-  public async getActiveWorkerApplicationsIdsByRoleAccount(address: string): Promise<BN[]> {
-    const applicationsAndIds = await this.api.query.storageWorkingGroup.workerApplicationById<Codec[]>();
-    const applications: WorkerApplication[] = (applicationsAndIds[1] as unknown) as WorkerApplication[];
-    const ids: WorkerApplicationId[] = (applicationsAndIds[0] as unknown) as WorkerApplicationId[];
+  public async getActiveApplicationsIdsByRoleAccount(address: string): Promise<BN[]> {
+    const applicationsAndIds = await this.api.query.storageWorkingGroup.applicationById<Codec[]>();
+    const applications: Application[] = (applicationsAndIds[1] as unknown) as Application[];
+    const ids: ApplicationId[] = (applicationsAndIds[0] as unknown) as ApplicationId[];
     return (
       await Promise.all(
         applications.map(async (application, index) => {
           if (
-            application.role_account.toString() === address &&
+            application.role_account_id.toString() === address &&
             (await this.getApplicationById(application.application_id)).stage.type === 'Active'
           ) {
             return ids[index];

+ 12 - 1
types/.gitignore

@@ -1 +1,12 @@
-/lib/
+# Don't track build artifacts
+**/*.js
+**/*.d.ts
+
+# JSON files imported by hiring types
+hiring/schemas/role.schema.json
+
+# from prior versions
+lib/
+
+# artifact of webpack when building pioneer?
+build/

+ 6 - 0
types/.npmignore

@@ -0,0 +1,6 @@
+# keep src/ files, packages is compiled when installed
+# src/
+
+# old build artifacts
+lib/
+build/

+ 8 - 8
types/package.json

@@ -2,7 +2,8 @@
   "name": "@joystream/types",
   "version": "0.11.0",
   "description": "Types for Joystream Substrate Runtime - nicaea release",
-  "main": "lib/index.js",
+  "main": "index.js",
+  "types": "index.d.ts",
   "scripts": {
     "prepublish": "npm run build",
     "build": "tsc --build tsconfig.json"
@@ -11,15 +12,14 @@
   "maintainers": [],
   "dependencies": {
     "@polkadot/types": "^0.96.1",
+    "@polkadot/keyring": "^1.7.0-beta.5",
+    "@types/lodash": "^4.14.157",
     "@types/vfile": "^4.0.0",
     "ajv": "^6.11.0",
     "lodash": "^4.17.15"
   },
-  "directories": {
-    "lib": "lib"
-  },
   "devDependencies": {
-    "typescript": "^3.6.4"
+    "typescript": "^3.7.2"
   },
   "engines": {
     "node": ">=10.0"
@@ -30,7 +30,7 @@
   },
   "repository": {
     "type": "git",
-    "url": "git+https://github.com/Joystream/apps.git"
+    "url": "git+https://github.com/Joystream/joystream.git"
   },
   "keywords": [
     "substrate",
@@ -39,7 +39,7 @@
   ],
   "license": "Apache-2.0",
   "bugs": {
-    "url": "https://github.com/Joystream/apps/issues"
+    "url": "https://github.com/Joystream/joystream/issues"
   },
-  "homepage": "https://github.com/Joystream/packages/joy-types/README.md"
+  "homepage": "https://github.com/Joystream/joystream"
 }

+ 16 - 1
types/src/content-working-group/index.ts

@@ -245,6 +245,11 @@ export class Curator extends JoyStruct<ICurator> {
     return this.getField<GenericAccountId>('role_account')
   }
 
+  // Helper for working-group compatibility
+  get role_account_id(): GenericAccountId {
+    return this.role_account;
+  }
+
   get reward_relationship(): Option<RewardRelationshipId> {
     return this.getField<Option<RewardRelationshipId>>('reward_relationship')
   }
@@ -290,12 +295,17 @@ export class CuratorApplication extends JoyStruct<ICuratorApplication> {
     return this.getField<GenericAccountId>('role_account')
   }
 
+  // Helper for working-group compatibility
+  get role_account_id(): GenericAccountId {
+    return this.role_account;
+  }
+
   get curator_opening_id(): CuratorOpeningId {
     return this.getField<CuratorOpeningId>('curator_opening_id')
   }
 
   // Helper for working-group compatibility
-  get worker_opening_id(): CuratorOpeningId {
+  get opening_id(): CuratorOpeningId {
     return this.curator_opening_id;
   }
 
@@ -431,6 +441,11 @@ export class CuratorOpening extends JoyStruct<ICuratorOpening> {
   get opening_id(): OpeningId {
     return this.getField<OpeningId>('opening_id')
   }
+
+  // Helper for working-group compatibility
+  get hiring_opening_id(): OpeningId {
+    return this.opening_id;
+  }
 };
 
 export type IExitedLeadRole = {

+ 89 - 91
types/src/working-group/index.ts

@@ -7,60 +7,34 @@ import { RewardRelationshipId } from '../recurring-rewards';
 import { StakeId } from '../stake';
 import { ApplicationId, OpeningId, ApplicationRationingPolicy, StakingPolicy } from '../hiring';
 
-export type ILead = {
-  member_id: MemberId,
-  role_account_id: AccountId
-};
-
-// This type is also defined in /content-workig-group (and those are incosistent), but here
-// it is beeing registered as "LeadOf" (which is an alias used by the runtime working-group module),
-// so it shouldn't cause any conflicts)
-export class Lead extends JoyStruct<ILead> {
-  constructor (value?: ILead) {
-    super({
-      member_id: MemberId,
-      role_account_id: "AccountId"
-    }, value);
-  }
-
-  get member_id(): MemberId {
-    return this.getField<MemberId>('member_id')
-  }
-
-  get role_account_id(): AccountId {
-    return this.getField<AccountId>('role_account_id')
-  }
-};
-
-export class WorkerApplicationId extends ApplicationId { };
-
-export class WorkerOpeningId extends OpeningId { };
-
 export class RationaleText extends Bytes { };
 
-export type IWorkerApplication = {
-  role_account: AccountId,
-  worker_opening_id: WorkerOpeningId,
+export type IApplication = {
+  role_account_id: AccountId,
+  opening_id: OpeningId,
   member_id: MemberId,
   application_id: ApplicationId
 };
 
-export class WorkerApplication extends JoyStruct<IWorkerApplication> {
-  constructor (value?: IWorkerApplication) {
+// This type is also defined in /hiring (and those are incosistent), but here
+// it is beeing registered as "ApplicationOf" (which is an alias used by the runtime working-group module),
+// so it shouldn't cause any conflicts
+export class Application extends JoyStruct<IApplication> {
+  constructor (value?: IApplication) {
     super({
-      role_account: "AccountId",
-      worker_opening_id: WorkerOpeningId,
+      role_account_id: "AccountId",
+      opening_id: OpeningId,
       member_id: MemberId,
       application_id: ApplicationId
     }, value);
   }
 
-  get role_account(): AccountId {
-    return this.getField<AccountId>('role_account');
+  get role_account_id(): AccountId {
+    return this.getField<AccountId>('role_account_id');
   }
 
-  get worker_opening_id(): WorkerOpeningId {
-    return this.getField<WorkerOpeningId>('worker_opening_id');
+  get opening_id(): OpeningId {
+    return this.getField<OpeningId>('opening_id');
   }
 
   get member_id(): MemberId {
@@ -76,19 +50,19 @@ export class WorkerId extends ActorId { };
 
 export class StorageProviderId extends WorkerId { };
 
-export class WorkerApplicationIdSet extends BTreeSet.with(WorkerApplicationId) { };
+export class ApplicationIdSet extends BTreeSet.with(ApplicationId) { };
 
-export class WorkerApplicationIdToWorkerIdMap extends BTreeMap.with(WorkerApplicationId, WorkerId) { };
+export class ApplicationIdToWorkerIdMap extends BTreeMap.with(ApplicationId, WorkerId) { };
 
 
-export type IWorkerRoleStakeProfile = {
+export type IRoleStakeProfile = {
   stake_id: StakeId,
   termination_unstaking_period: Option<BlockNumber>,
   exit_unstaking_period: Option<BlockNumber>,
 };
 
-export class WorkerRoleStakeProfile extends JoyStruct<IWorkerRoleStakeProfile> {
-  constructor (value?: IWorkerRoleStakeProfile) {
+export class RoleStakeProfile extends JoyStruct<IRoleStakeProfile> {
+  constructor (value?: IRoleStakeProfile) {
     super({
       stake_id: StakeId,
       termination_unstaking_period: "Option<BlockNumber>",
@@ -111,18 +85,18 @@ export class WorkerRoleStakeProfile extends JoyStruct<IWorkerRoleStakeProfile> {
 
 export type IWorker = {
   member_id: MemberId,
-  role_account: AccountId,
+  role_account_id: AccountId,
   reward_relationship: Option<RewardRelationshipId>,
-  role_stake_profile: Option<WorkerRoleStakeProfile>,
+  role_stake_profile: Option<RoleStakeProfile>,
 }
 
 export class Worker extends JoyStruct<IWorker> {
   constructor (value?: IWorker) {
     super({
       member_id: MemberId,
-      role_account: "AccountId",
+      role_account_id: "AccountId",
       reward_relationship: Option.with(RewardRelationshipId),
-      role_stake_profile: Option.with(WorkerRoleStakeProfile),
+      role_stake_profile: Option.with(RoleStakeProfile),
     }, value);
   }
 
@@ -130,16 +104,16 @@ export class Worker extends JoyStruct<IWorker> {
     return this.getField<MemberId>('member_id');
   }
 
-  get role_account(): AccountId {
-    return this.getField<AccountId>('role_account');
+  get role_account_id(): AccountId {
+    return this.getField<AccountId>('role_account_id');
   }
 
   get reward_relationship(): Option<RewardRelationshipId> {
     return this.getField<Option<RewardRelationshipId>>('reward_relationship');
   }
 
-  get role_stake_profile(): Option<WorkerRoleStakeProfile> {
-    return this.getField<Option<WorkerRoleStakeProfile>>('role_stake_profile');
+  get role_stake_profile(): Option<RoleStakeProfile> {
+    return this.getField<Option<RoleStakeProfile>>('role_stake_profile');
   }
 
   get is_active(): boolean {
@@ -185,20 +159,20 @@ export type IWorkingGroupOpeningPolicyCommitment = {
   fill_opening_successful_applicant_application_stake_unstaking_period: Option<BlockNumber>,
   fill_opening_failed_applicant_application_stake_unstaking_period: Option<BlockNumber>,
   fill_opening_failed_applicant_role_stake_unstaking_period: Option<BlockNumber>,
-  terminate_worker_application_stake_unstaking_period: Option<BlockNumber>,
-  terminate_worker_role_stake_unstaking_period: Option<BlockNumber>,
-  exit_worker_role_application_stake_unstaking_period: Option<BlockNumber>,
-  exit_worker_role_stake_unstaking_period: Option<BlockNumber>,
+  terminate_application_stake_unstaking_period: Option<BlockNumber>,
+  terminate_role_stake_unstaking_period: Option<BlockNumber>,
+  exit_role_application_stake_unstaking_period: Option<BlockNumber>,
+  exit_role_stake_unstaking_period: Option<BlockNumber>,
 };
 
 // This type represents OpeningPolicyCommitment defined inside the runtime's working-grpup module.
 // The only difference between this and the one defined in /content-working-group is in the names of some fields.
 //
 // There is also a minor issue here:
-// Because api metadata still says that ie. the "commitment" argument of "storageWorkingGroup.addWorkerOpening" extrinsic
+// Because api metadata still says that ie. the "commitment" argument of "storageWorkingGroup.addOpening" extrinsic
 // is of type "OpeningPolicyCommitment" (not the "WorkingGroupOpeningPolicyCommitment" defined here), the CWG's OpeningPolicyCommitment
 // type is used when sending this extrinsic (it has "terminate_curator_role_stake_unstaking_period" field insted
-// of "terminate_worker_role_stake_unstaking_period" etc.).
+// of "terminate_role_stake_unstaking_period" etc.).
 // Since both those types are basically the same structs (only filed names are different) nothing seems to break, but it's
 // very fragile atm and any change to this type in working-group module could result in "unsolvable" inconsistencies
 // (this won't be an issue after CWG gets refactored to use the working-grpup module too)
@@ -213,10 +187,10 @@ export class WorkingGroupOpeningPolicyCommitment extends JoyStruct<IWorkingGroup
       fill_opening_successful_applicant_application_stake_unstaking_period: "Option<BlockNumber>",
       fill_opening_failed_applicant_application_stake_unstaking_period: "Option<BlockNumber>",
       fill_opening_failed_applicant_role_stake_unstaking_period: "Option<BlockNumber>",
-      terminate_worker_application_stake_unstaking_period: "Option<BlockNumber>",
-      terminate_worker_role_stake_unstaking_period: "Option<BlockNumber>",
-      exit_worker_role_application_stake_unstaking_period: "Option<BlockNumber>",
-      exit_worker_role_stake_unstaking_period: "Option<BlockNumber>",
+      terminate_application_stake_unstaking_period: "Option<BlockNumber>",
+      terminate_role_stake_unstaking_period: "Option<BlockNumber>",
+      exit_role_application_stake_unstaking_period: "Option<BlockNumber>",
+      exit_role_stake_unstaking_period: "Option<BlockNumber>",
     }, value);
   }
 
@@ -252,67 +226,91 @@ export class WorkingGroupOpeningPolicyCommitment extends JoyStruct<IWorkingGroup
     return this.getField<Option<BlockNumber>>('fill_opening_failed_applicant_role_stake_unstaking_period')
   }
 
-  get terminate_worker_application_stake_unstaking_period(): Option<BlockNumber> {
-    return this.getField<Option<BlockNumber>>('terminate_worker_application_stake_unstaking_period')
+  get terminate_application_stake_unstaking_period(): Option<BlockNumber> {
+    return this.getField<Option<BlockNumber>>('terminate_application_stake_unstaking_period')
   }
 
-  get terminate_worker_role_stake_unstaking_period(): Option<BlockNumber> {
-    return this.getField<Option<BlockNumber>>('terminate_worker_role_stake_unstaking_period')
+  get terminate_role_stake_unstaking_period(): Option<BlockNumber> {
+    return this.getField<Option<BlockNumber>>('terminate_role_stake_unstaking_period')
   }
 
-  get exit_worker_role_application_stake_unstaking_period(): Option<BlockNumber> {
-    return this.getField<Option<BlockNumber>>('exit_worker_role_application_stake_unstaking_period')
+  get exit_role_application_stake_unstaking_period(): Option<BlockNumber> {
+    return this.getField<Option<BlockNumber>>('exit_role_application_stake_unstaking_period')
   }
 
-  get exit_worker_role_stake_unstaking_period(): Option<BlockNumber> {
-    return this.getField<Option<BlockNumber>>('exit_worker_role_stake_unstaking_period')
+  get exit_role_stake_unstaking_period(): Option<BlockNumber> {
+    return this.getField<Option<BlockNumber>>('exit_role_stake_unstaking_period')
   }
 };
 
-export type IWorkerOpening = {
-  opening_id: OpeningId,
-  worker_applications: BTreeSet<WorkerApplicationId>,
+export enum OpeningTypeKeys {
+  Leader = 'Leader',
+  Worker = 'Worker'
+};
+
+export class OpeningType extends Enum {
+  constructor (value?: any, index?: number) {
+    super(
+      {
+        Leader: Null,
+        Worker: Null
+      },
+      value, index
+    );
+  }
+};
+
+export type IOpening = {
+  hiring_opening_id: OpeningId,
+  applications: BTreeSet<ApplicationId>,
   policy_commitment: WorkingGroupOpeningPolicyCommitment,
+  opening_type: OpeningType
 }
 
-export class WorkerOpening extends JoyStruct<IWorkerOpening> {
+// This type is also defined in /hiring (and those are incosistent), but here
+// it is beeing registered as "OpeningOf" (which is an alias used by the runtime working-group module),
+// so it shouldn't cause any conflicts
+export class Opening extends JoyStruct<IOpening> {
   constructor (value?: IWorker) {
     super({
-      opening_id: OpeningId,
-      worker_applications: BTreeSet.with(WorkerApplicationId),
+      hiring_opening_id: OpeningId,
+      applications: BTreeSet.with(ApplicationId),
       policy_commitment: WorkingGroupOpeningPolicyCommitment,
+      opening_type: OpeningType
     }, value);
   }
 
-  get opening_id(): OpeningId {
-    return this.getField<OpeningId>('opening_id');
+  get hiring_opening_id(): OpeningId {
+    return this.getField<OpeningId>('hiring_opening_id');
   }
 
-  get worker_applications(): BTreeSet<WorkerApplicationId> {
-    return this.getField<BTreeSet<WorkerApplicationId>>('worker_applications');
+  get applications(): BTreeSet<ApplicationId> {
+    return this.getField<BTreeSet<ApplicationId>>('applications');
   }
 
   get policy_commitment(): WorkingGroupOpeningPolicyCommitment {
     return this.getField<WorkingGroupOpeningPolicyCommitment>('policy_commitment');
   }
+
+  get opening_type(): OpeningType {
+    return this.getField<OpeningType>('opening_type');
+  }
 }
 
 export function registerWorkingGroupTypes() {
   try {
     getTypeRegistry().register({
-      // Note that it actually HAS TO be "LeadOf" in the runtime,
-      // otherwise there would be conflicts with the current content-workig-group module
-      LeadOf: Lead,
       RationaleText,
-      WorkerApplication,
-      WorkerApplicationId,
-      WorkerApplicationIdSet,
-      WorkerApplicationIdToWorkerIdMap,
+      ApplicationOf: Application,
+      ApplicationIdSet,
+      ApplicationIdToWorkerIdMap,
       WorkerId,
       WorkerOf: Worker,
-      WorkerOpening,
-      WorkerOpeningId,
-      StorageProviderId
+      OpeningOf: Opening,
+      StorageProviderId,
+      OpeningType,
+      /// Alias used by the runtime working-group module
+      HiringApplicationId: ApplicationId
     });
   } catch (err) {
     console.error('Failed to register custom types of working-group module', err);

+ 11 - 6
types/tsconfig.json

@@ -11,14 +11,19 @@
     "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
     "experimentalDecorators": true,           /* Enables experimental support for ES7 decorators. */
     "declaration": true,
-    "outDir": "lib",
-	"resolveJsonModule": true,
-	"types" : [
-		"node"
-	]
-
+    "outDir": "./",
+    "resolveJsonModule": true,
+    "types" : [
+      "node"
+    ],
+    "forceConsistentCasingInFileNames": true
   },
   "include": [
     "src/**/*.ts"
   ],
+  "exclude": [
+    "node_modules",
+    "**/*.spec.ts",
+    "**/*.d.ts"
+  ]
 }

+ 7 - 0
yarn.lock

@@ -1740,7 +1740,9 @@
 "@joystream/types@./types", "@nicaea/types@./types":
   version "0.11.0"
   dependencies:
+    "@polkadot/keyring" "^1.7.0-beta.5"
     "@polkadot/types" "^0.96.1"
+    "@types/lodash" "^4.14.157"
     "@types/vfile" "^4.0.0"
     ajv "^6.11.0"
     lodash "^4.17.15"
@@ -3817,6 +3819,11 @@
   resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
   integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==
 
+"@types/lodash@^4.14.157":
+  version "4.14.157"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.157.tgz#fdac1c52448861dfde1a2e1515dbc46e54926dc8"
+  integrity sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ==
+
 "@types/marked@^0.7.0":
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.7.2.tgz#1393f076773b55cc7078c0fbeb86a497c69db97e"

Some files were not shown because too many files changed in this diff