Forráskód Böngészése

Merge branch 'cli-minor-improvements' into cli-pre-publish

Leszek Wiesner 4 éve
szülő
commit
554fb5ee80

+ 37 - 0
.github/workflows/network-tests.yml

@@ -0,0 +1,37 @@
+name: network-tests
+on: [pull_request, push]
+
+jobs:
+  network_build_ubuntu:
+    name: Ubuntu Checks
+    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: checks
+      run: |
+        yarn install --frozen-lockfile
+        yarn workspace joystream-testing checks
+
+  network_build_osx:
+    name: MacOS Checks
+    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: checks
+      run: |
+        yarn install --frozen-lockfile --network-timeout 120000
+        yarn workspace joystream-testing checks

+ 37 - 0
.github/workflows/storage-node.yml

@@ -0,0 +1,37 @@
+name: storage-node
+on: [pull_request, push]
+
+jobs:
+  storage_node_build_ubuntu:
+    name: Ubuntu Checks
+    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: checks
+      run: |
+        yarn install --frozen-lockfile
+        yarn workspace storage-node checks
+
+  storage_node_build_osx:
+    name: MacOS Checks
+    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: checks
+      run: |
+        yarn install --frozen-lockfile --network-timeout 120000
+        yarn workspace storage-node checks

+ 37 - 4
cli/src/Api.ts

@@ -3,7 +3,7 @@ import { registerJoystreamTypes } from '@joystream/types/'
 import { ApiPromise, WsProvider } from '@polkadot/api'
 import { QueryableStorageMultiArg } from '@polkadot/api/types'
 import { formatBalance } from '@polkadot/util'
-import { Hash, Balance, Moment } from '@polkadot/types/interfaces'
+import { Hash, Balance, Moment, BlockNumber } from '@polkadot/types/interfaces'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { Codec } from '@polkadot/types/types'
 import { Option, Vec } from '@polkadot/types'
@@ -20,6 +20,9 @@ import {
   GroupOpeningStage,
   GroupOpening,
   GroupApplication,
+  openingPolicyUnstakingPeriodsKeys,
+  UnstakingPeriods,
+  StakingPolicyUnstakingPeriodKey,
 } from './Types'
 import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types'
 import { CLIError } from '@oclif/errors'
@@ -38,6 +41,7 @@ import {
   ApplicationStageKeys,
   ApplicationId,
   OpeningId,
+  StakingPolicy,
 } from '@joystream/types/hiring'
 import { MemberId, Profile } from '@joystream/types/members'
 import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards'
@@ -46,7 +50,7 @@ import { LinkageResult } from '@polkadot/types/codec/Linkage'
 
 import { InputValidationLengthConstraint } from '@joystream/types/common'
 
-export const DEFAULT_API_URI = 'wss://rome-rpc-endpoint.joystream.org:9944/'
+export const DEFAULT_API_URI = 'ws://localhost:9944/'
 const DEFAULT_DECIMALS = new u32(12)
 
 // Mapping of working group to api module
@@ -401,11 +405,39 @@ export default class Api {
     const applications = await this.groupOpeningApplications(group, wgOpeningId)
     const stage = await this.parseOpeningStage(opening.stage)
     const type = groupOpening.opening_type
+    const { application_staking_policy: applSP, role_staking_policy: roleSP } = opening
     const stakes = {
-      application: opening.application_staking_policy.unwrapOr(undefined),
-      role: opening.role_staking_policy.unwrapOr(undefined),
+      application: applSP.unwrapOr(undefined),
+      role: roleSP.unwrapOr(undefined),
     }
 
+    const unstakingPeriod = (period: Option<BlockNumber>) => period.unwrapOr(new BN(0)).toNumber()
+    const spUnstakingPeriod = (sp: Option<StakingPolicy>, key: StakingPolicyUnstakingPeriodKey) =>
+      sp.isSome ? unstakingPeriod(sp.unwrap()[key]) : 0
+
+    const unstakingPeriods: Partial<UnstakingPeriods> = {
+      ['review_period_expired_application_stake_unstaking_period_length']: spUnstakingPeriod(
+        applSP,
+        'review_period_expired_unstaking_period_length'
+      ),
+      ['crowded_out_application_stake_unstaking_period_length']: spUnstakingPeriod(
+        applSP,
+        'crowded_out_unstaking_period_length'
+      ),
+      ['review_period_expired_role_stake_unstaking_period_length']: spUnstakingPeriod(
+        roleSP,
+        'review_period_expired_unstaking_period_length'
+      ),
+      ['crowded_out_role_stake_unstaking_period_length']: spUnstakingPeriod(
+        roleSP,
+        'crowded_out_unstaking_period_length'
+      ),
+    }
+
+    openingPolicyUnstakingPeriodsKeys.forEach((key) => {
+      unstakingPeriods[key] = unstakingPeriod(groupOpening.policy_commitment[key])
+    })
+
     return {
       wgOpeningId,
       openingId,
@@ -414,6 +446,7 @@ export default class Api {
       stakes,
       applications,
       type,
+      unstakingPeriods: unstakingPeriods as UnstakingPeriods,
     }
   }
 

+ 30 - 0
cli/src/Types.ts

@@ -142,6 +142,35 @@ export type GroupOpeningStakes = {
   role?: StakingPolicy
 }
 
+export const stakingPolicyUnstakingPeriodKeys = [
+  'crowded_out_unstaking_period_length',
+  'review_period_expired_unstaking_period_length',
+] as const
+
+export type StakingPolicyUnstakingPeriodKey = typeof stakingPolicyUnstakingPeriodKeys[number]
+
+export const openingPolicyUnstakingPeriodsKeys = [
+  'fill_opening_failed_applicant_application_stake_unstaking_period',
+  'fill_opening_failed_applicant_role_stake_unstaking_period',
+  'fill_opening_successful_applicant_application_stake_unstaking_period',
+  'terminate_application_stake_unstaking_period',
+  'terminate_role_stake_unstaking_period',
+  'exit_role_application_stake_unstaking_period',
+  'exit_role_stake_unstaking_period',
+] as const
+
+export type OpeningPolicyUnstakingPeriodsKey = typeof openingPolicyUnstakingPeriodsKeys[number]
+export type UnstakingPeriodsKey =
+  | OpeningPolicyUnstakingPeriodsKey
+  | 'crowded_out_application_stake_unstaking_period_length'
+  | 'crowded_out_role_stake_unstaking_period_length'
+  | 'review_period_expired_application_stake_unstaking_period_length'
+  | 'review_period_expired_role_stake_unstaking_period_length'
+
+export type UnstakingPeriods = {
+  [k in UnstakingPeriodsKey]: number
+}
+
 export type GroupOpening = {
   wgOpeningId: number
   openingId: number
@@ -150,6 +179,7 @@ export type GroupOpening = {
   stakes: GroupOpeningStakes
   applications: GroupApplication[]
   type: OpeningType
+  unstakingPeriods: UnstakingPeriods
 }
 
 // Some helper structs for generating human_readable_text in working group opening extrinsic

+ 11 - 3
cli/src/base/AccountsCommandBase.ts

@@ -140,14 +140,22 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
   async getRequiredSelectedAccount(promptIfMissing = true): Promise<NamedKeyringPair> {
     let selectedAccount: NamedKeyringPair | null = this.getSelectedAccount()
     if (!selectedAccount) {
-      this.warn('No default account selected! Use account:choose to set the default account!')
-      if (!promptIfMissing) this.exit(ExitCodes.NoAccountSelected)
+      if (!promptIfMissing) {
+        this.error('No default account selected! Use account:choose to set the default account.', {
+          exit: ExitCodes.NoAccountSelected,
+        })
+      }
+
       const accounts: NamedKeyringPair[] = this.fetchAccounts()
       if (!accounts.length) {
-        this.error('There are no accounts available!', { exit: ExitCodes.NoAccountFound })
+        this.error('No accounts available! Use account:import in order to import accounts into the CLI.', {
+          exit: ExitCodes.NoAccountFound,
+        })
       }
 
+      this.warn('No default account selected!')
       selectedAccount = await this.promptForAccount(accounts)
+      await this.setSelectedAccount(selectedAccount)
     }
 
     return selectedAccount

+ 63 - 8
cli/src/base/ApiCommandBase.ts

@@ -5,7 +5,7 @@ import Api from '../Api'
 import { getTypeDef, createType, Option, Tuple, Bytes } from '@polkadot/types'
 import { Codec, TypeDef, TypeDefInfo, Constructor } from '@polkadot/types/types'
 import { Vec, Struct, Enum } from '@polkadot/types/codec'
-import { ApiPromise } from '@polkadot/api'
+import { ApiPromise, WsProvider } from '@polkadot/api'
 import { KeyringPair } from '@polkadot/keyring/types'
 import chalk from 'chalk'
 import { SubmittableResultImpl } from '@polkadot/api/types'
@@ -20,6 +20,7 @@ class ExtrinsicFailedError extends Error {}
  */
 export default abstract class ApiCommandBase extends StateAwareCommandBase {
   private api: Api | null = null
+  forceSkipApiUriPrompt = false
 
   getApi(): Api {
     if (!this.api) throw new CLIError('Tried to get API before initialization.', { exit: ExitCodes.ApiError })
@@ -33,10 +34,60 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
 
   async init() {
     await super.init()
-    const apiUri: string = this.getPreservedState().apiUri
+    let apiUri: string = this.getPreservedState().apiUri
+    if (!apiUri) {
+      this.warn("You haven't provided a node/endpoint for the CLI to connect to yet!")
+      apiUri = await this.promptForApiUri()
+    }
     this.api = await Api.create(apiUri)
   }
 
+  async promptForApiUri(): Promise<string> {
+    let selectedNodeUri = await this.simplePrompt({
+      type: 'list',
+      message: 'Choose a node/endpoint:',
+      choices: [
+        {
+          name: 'Local node (ws://localhost:9944)',
+          value: 'ws://localhost:9944',
+        },
+        {
+          name: 'Current Testnet official Joystream node (wss://rome-rpc-endpoint.joystream.org:9944/)',
+          value: 'wss://rome-rpc-endpoint.joystream.org:9944/',
+        },
+        {
+          name: 'Custom endpoint',
+          value: '',
+        },
+      ],
+    })
+
+    if (!selectedNodeUri) {
+      do {
+        selectedNodeUri = await this.simplePrompt({
+          type: 'input',
+          message: 'Provide a WS endpoint uri',
+        })
+        if (!this.isApiUriValid(selectedNodeUri)) {
+          this.warn('Provided uri seems incorrect! Please try again...')
+        }
+      } while (!this.isApiUriValid(selectedNodeUri))
+    }
+
+    await this.setPreservedState({ apiUri: selectedNodeUri })
+
+    return selectedNodeUri
+  }
+
+  isApiUriValid(uri: string) {
+    try {
+      new WsProvider(uri)
+    } catch (e) {
+      return false
+    }
+    return true
+  }
+
   // This is needed to correctly handle some structs, enums etc.
   // Where the main typeDef doesn't provide enough information
   protected getRawTypeDef(type: string) {
@@ -67,11 +118,15 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
 
   // Prompt for simple/plain value (provided as string) of given type
   async promptForSimple(typeDef: TypeDef, paramOptions?: ApiParamOptions): Promise<Codec> {
+    // If no default provided - get default value resulting from providing empty string
+    const defaultValueString =
+      paramOptions?.value?.default?.toString() || createType(typeDef.type as any, '').toString()
     const providedValue = await this.simplePrompt({
       message: `Provide value for ${this.paramName(typeDef)}`,
       type: 'input',
-      // If not default provided - show default value resulting from providing empty string
-      default: paramOptions?.value?.default?.toString() || createType(typeDef.type as any, '').toString(),
+      // We want to avoid showing default value like '0x', because it falsely suggests
+      // that user needs to provide the value as hex
+      default: (defaultValueString === '0x' ? '' : defaultValueString) || undefined,
       validate: paramOptions?.validator,
     })
     return createType(typeDef.type as any, providedValue)
@@ -137,8 +192,8 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
       const fieldOptions = paramOptions?.nestedOptions && paramOptions.nestedOptions[subtype.name!]
       const fieldDefaultValue = fieldOptions?.value?.default || (structDefault && structDefault.get(subtype.name!))
       const finalFieldOptions: ApiParamOptions = {
-        ...fieldOptions,
         forcedName: subtype.name,
+        ...fieldOptions, // "forcedName" above should be overriden with "fieldOptions.forcedName" if available
         value: fieldDefaultValue && { ...fieldOptions?.value, default: fieldDefaultValue },
       }
       structValues[subtype.name!] = await this.promptForParam(subtype.type, finalFieldOptions)
@@ -249,16 +304,16 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
   }
 
   async promptForJsonBytes(
-    JsonStruct: Constructor<Struct>,
+    jsonStruct: Constructor<Struct>,
     argName?: string,
     defaultValue?: Bytes,
     schemaValidator?: ajv.ValidateFunction
   ) {
-    const rawType = new JsonStruct().toRawType()
+    const rawType = new jsonStruct().toRawType()
     const typeDef = getTypeDef(rawType)
 
     const defaultStruct =
-      defaultValue && new JsonStruct(JSON.parse(Buffer.from(defaultValue.toHex().replace('0x', ''), 'hex').toString()))
+      defaultValue && new jsonStruct(JSON.parse(Buffer.from(defaultValue.toHex().replace('0x', ''), 'hex').toString()))
 
     if (argName) {
       typeDef.name = argName

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

@@ -2,7 +2,6 @@ import fs from 'fs'
 import path from 'path'
 import ExitCodes from '../ExitCodes'
 import { CLIError } from '@oclif/errors'
-import { DEFAULT_API_URI } from '../Api'
 import lockFile from 'proper-lockfile'
 import DefaultCommandBase from './DefaultCommandBase'
 import os from 'os'
@@ -16,7 +15,7 @@ type StateObject = {
 // State object default values
 const DEFAULT_STATE: StateObject = {
   selectedAccountFilename: '',
-  apiUri: DEFAULT_API_URI,
+  apiUri: '',
 }
 
 // State file path (relative to getAppDataPath())
@@ -93,7 +92,8 @@ export default abstract class StateAwareCommandBase extends DefaultCommandBase {
   getPreservedState(): StateObject {
     let preservedState: StateObject
     try {
-      preservedState = require(this.getStateFilePath()) as StateObject
+      // Use readFileSync instead of "require" in order to always get a "fresh" state
+      preservedState = JSON.parse(fs.readFileSync(this.getStateFilePath()).toString()) as StateObject
     } catch (e) {
       throw this.createDataReadError()
     }

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

@@ -18,6 +18,7 @@ import fs from 'fs'
 import path from 'path'
 import _ from 'lodash'
 import { ApplicationStageKeys } from '@joystream/types/hiring'
+import chalk from 'chalk'
 
 const DEFAULT_GROUP = WorkingGroups.StorageProviders
 const DRAFTS_FOLDER = 'opening-drafts'
@@ -267,5 +268,7 @@ export default abstract class WorkingGroupsCommandBase extends AccountsCommandBa
       })
     }
     this.group = flags.group as WorkingGroups
+
+    this.log(chalk.white('Group: ' + flags.group))
   }
 }

+ 20 - 11
cli/src/commands/api/setUri.ts

@@ -1,28 +1,37 @@
-import StateAwareCommandBase from '../../base/StateAwareCommandBase'
 import chalk from 'chalk'
-import { WsProvider } from '@polkadot/api'
+import ApiCommandBase from '../../base/ApiCommandBase'
 import ExitCodes from '../../ExitCodes'
 
 type ApiSetUriArgs = { uri: string }
 
-export default class ApiSetUri extends StateAwareCommandBase {
+export default class ApiSetUri extends ApiCommandBase {
   static description = 'Set api WS provider uri'
   static args = [
     {
       name: 'uri',
-      required: true,
-      description: 'Uri of the node api WS provider',
+      required: false,
+      description: 'Uri of the node api WS provider (if skipped, a prompt will be displayed)',
     },
   ]
 
+  async init() {
+    this.forceSkipApiUriPrompt = true
+    super.init()
+  }
+
   async run() {
     const args: ApiSetUriArgs = this.parse(ApiSetUri).args as ApiSetUriArgs
-    try {
-      new WsProvider(args.uri)
-    } catch (e) {
-      this.error('The WS provider uri seems to be incorrect', { exit: ExitCodes.InvalidInput })
+    let newUri = ''
+    if (args.uri) {
+      if (this.isApiUriValid(args.uri)) {
+        await this.setPreservedState({ apiUri: args.uri })
+        newUri = args.uri
+      } else {
+        this.error('Provided uri seems to be incorrect!', { exit: ExitCodes.InvalidInput })
+      }
+    } else {
+      newUri = await this.promptForApiUri()
     }
-    await this.setPreservedState({ apiUri: args.uri })
-    this.log(chalk.greenBright('Api uri successfuly changed! New uri: ') + chalk.white(args.uri))
+    this.log(chalk.greenBright('Api uri successfuly changed! New uri: ') + chalk.white(newUri))
   }
 }

+ 9 - 1
cli/src/commands/working-groups/opening.ts

@@ -1,7 +1,7 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { displayTable, displayCollapsedRow, displayHeader } from '../../helpers/display'
 import _ from 'lodash'
-import { OpeningStatus, GroupOpeningStage, GroupOpeningStakes } from '../../Types'
+import { OpeningStatus, GroupOpeningStage, GroupOpeningStakes, UnstakingPeriodsKey } from '../../Types'
 import { StakingAmountLimitModeKeys, StakingPolicy } from '@joystream/types/hiring'
 import { formatBalance } from '@polkadot/util'
 import chalk from 'chalk'
@@ -65,6 +65,14 @@ export default class WorkingGroupsOpening extends WorkingGroupsCommandBase {
     }
     displayCollapsedRow(openingRow)
 
+    displayHeader('Unstaking periods')
+    const periodsRow: { [k: string]: string } = {}
+    for (const key of Object.keys(opening.unstakingPeriods).sort()) {
+      const displayKey = _.startCase(key) + ':  '
+      periodsRow[displayKey] = opening.unstakingPeriods[key as UnstakingPeriodsKey].toLocaleString() + ' blocks'
+    }
+    displayCollapsedRow(periodsRow)
+
     displayHeader(`Applications (${opening.applications.length})`)
     const applicationsRows = opening.applications.map((a) => ({
       'WG appl. ID': a.wgApplicationId,

+ 0 - 1
cli/src/commands/working-groups/openings.ts

@@ -13,7 +13,6 @@ export default class WorkingGroupsOpenings extends WorkingGroupsCommandBase {
 
     const openingsRows = openings.map((o) => ({
       'WG Opening ID': o.wgOpeningId,
-      'Opening ID': o.openingId,
       Type: o.type.type,
       Stage: `${_.startCase(o.stage.status)}${o.stage.block ? ` (#${o.stage.block})` : ''}`,
       Applications: o.applications.length,

+ 10 - 1
cli/src/commands/working-groups/overview.ts

@@ -25,16 +25,25 @@ export default class WorkingGroupsOverview extends WorkingGroupsCommandBase {
       this.log(chalk.yellow('No lead assigned!'))
     }
 
+    const accounts = this.fetchAccounts()
+
     displayHeader('Members')
     const membersRows = members.map((m) => ({
-      '': lead?.workerId.eq(m.workerId) ? '\u{2B50}' : '', // A nice star for the lead
       'Worker id': m.workerId.toString(),
       'Member id': m.memberId.toString(),
       'Member handle': m.profile.handle.toString(),
       Stake: formatBalance(m.stake),
       Earned: formatBalance(m.reward?.totalRecieved),
       'Role account': shortAddress(m.roleAccount),
+      '':
+        (lead?.workerId.eq(m.workerId) ? '\u{2B50}' : '  ') +
+        ' ' +
+        (accounts.some((a) => a.address === m.roleAccount.toString()) ? '\u{1F511}' : '  '),
     }))
     displayTable(membersRows, 5)
+
+    displayHeader('Legend')
+    this.log('\u{2B50} - Leader')
+    this.log('\u{1F511} - Role key available in CLI')
   }
 }

+ 13 - 0
cli/src/promptOptions/addWorkerOpening.ts

@@ -17,6 +17,19 @@ class OpeningPolicyCommitmentOptions implements ApiParamsOptions {
       locked: true,
     },
   }
+  // Rename fields containing "curator" (solivg minor UI issue related to flat namespace)
+  public terminate_curator_application_stake_unstaking_period: ApiParamOptions = {
+    forcedName: 'terminate_application_stake_unstaking_period',
+  }
+  public terminate_curator_role_stake_unstaking_period: ApiParamOptions = {
+    forcedName: 'terminate_role_stake_unstaking_period',
+  }
+  public exit_curator_role_application_stake_unstaking_period: ApiParamOptions = {
+    forcedName: 'exit_role_application_stake_unstaking_period',
+  }
+  public exit_curator_role_stake_unstaking_period: ApiParamOptions = {
+    forcedName: 'exit_role_stake_unstaking_period',
+  }
 }
 
 class AddWrokerOpeningOptions implements ApiParamsOptions {

+ 30 - 0
devops/eslint-config/index.js

@@ -43,6 +43,36 @@ module.exports = {
     // should prefer using 'debug' package at least to allow control of
     // output verbosity if logging to console.
     'no-console': 'off',
+    '@typescript-eslint/camelcase': 'off',
+    '@typescript-eslint/class-name-casing': 'off',
+    "@typescript-eslint/naming-convention": [
+      "error",
+      {
+        selector: 'default',
+        format: ['camelCase'],
+      },
+      {
+        selector: 'variable',
+        format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
+      },
+      {
+        selector: 'property',
+        format: [] // Don't force format of object properties, so they can be ie.: { "Some thing": 123 }, { some_thing: 123 } etc.
+      },
+      {
+        selector: 'accessor',
+        format: ['camelCase', 'snake_case']
+      },
+      {
+        selector: 'enumMember',
+        format: ['PascalCase']
+      },
+      {
+        selector: 'typeLike',
+        format: [],
+        custom: { regex: '^([A-Z][a-z0-9]*_?)+', match: true }, // combined PascalCase and snake_case to allow ie. OpeningType_Worker
+      }
+    ],
   },
   plugins: [
     'standard',

+ 1 - 0
storage-node/.eslintrc.js

@@ -13,6 +13,7 @@ module.exports = {
     'no-console': 'off', // we use console in the project
     '@typescript-eslint/no-var-requires': 'warn',
     '@typescript-eslint/camelcase': 'warn',
+    '@typescript-eslint/naming-convention': 'off',
   },
   overrides: [
     {

+ 1 - 1
storage-node/package.json

@@ -32,7 +32,7 @@
   ],
   "scripts": {
     "test": "wsrun --serial test",
-    "lint": "eslint --ignore-path .gitignore .",
+    "lint": "eslint --ext .js,.ts --ignore-path .gitignore .",
     "build": "yarn workspace @joystream/storage-cli run build",
     "checks": "yarn lint && prettier . --check",
     "format": "prettier . --write"

+ 1 - 1
storage-node/packages/cli/package.json

@@ -28,7 +28,7 @@
   },
   "scripts": {
     "test": "mocha 'dist/test/**/*.js'",
-    "lint": "eslint --ext .ts,.tsx . && tsc --noEmit --pretty",
+    "lint": "eslint --ext .js,.ts . && tsc --noEmit --pretty",
     "build": "tsc --build"
   },
   "bin": {

+ 12 - 6
tests/network-tests/src/nicaea/dto/fillOpeningParameters.ts

@@ -32,28 +32,34 @@ export class FillOpeningParameters {
     return this.workingGroup;
   }
 
-  public setAmountPerPayout(value: BN) {
+  public setAmountPerPayout(value: BN): FillOpeningParameters {
     this.amountPerPayout = value;
+    return this;
   }
 
-  public setNextPaymentAtBlock(value: BN) {
+  public setNextPaymentAtBlock(value: BN): FillOpeningParameters {
     this.nextPaymentAtBlock = value;
+    return this;
   }
 
-  public setPayoutInterval(value: BN) {
+  public setPayoutInterval(value: BN): FillOpeningParameters {
     this.payoutInterval = value;
+    return this;
   }
 
-  public setOpeningId(value: BN) {
+  public setOpeningId(value: BN): FillOpeningParameters {
     this.openingId = value;
+    return this;
   }
 
-  public setSuccessfulApplicationId(value: BN) {
+  public setSuccessfulApplicationId(value: BN): FillOpeningParameters {
     this.successfulApplicationId = value;
+    return this;
   }
 
-  public setWorkingGroup(value: string) {
+  public setWorkingGroup(value: string): FillOpeningParameters {
     this.workingGroup = value;
+    return this;
   }
 
   constructor() {

+ 49 - 20
tests/network-tests/src/nicaea/dto/workingGroupOpening.ts

@@ -102,84 +102,104 @@ export class WorkingGroupOpening {
     return this.openingType;
   }
 
-  public setActivateAtBlock(value: BN | undefined) {
+  public setActivateAtBlock(value: BN | undefined): WorkingGroupOpening {
     this.activateAtBlock = value;
+    return this;
   }
 
-  public setMaxActiveApplicants(value: BN) {
+  public setMaxActiveApplicants(value: BN): WorkingGroupOpening {
     this.maxActiveApplicants = value;
+    return this;
   }
 
-  public setMaxReviewPeriodLength(value: BN) {
+  public setMaxReviewPeriodLength(value: BN): WorkingGroupOpening {
     this.maxReviewPeriodLength = value;
+    return this;
   }
 
-  public setApplicationStakingPolicyAmount(value: BN) {
+  public setApplicationStakingPolicyAmount(value: BN): WorkingGroupOpening {
     this.applicationStakingPolicyAmount = value;
+    return this;
   }
 
-  public setApplicationCrowdedOutUnstakingPeriodLength(value: BN) {
+  public setApplicationCrowdedOutUnstakingPeriodLength(value: BN): WorkingGroupOpening {
     this.applicationCrowdedOutUnstakingPeriodLength = value;
+    return this;
   }
 
-  public setApplicationExpiredUnstakingPeriodLength(value: BN) {
+  public setApplicationExpiredUnstakingPeriodLength(value: BN): WorkingGroupOpening {
     this.applicationExpiredUnstakingPeriodLength = value;
+    return this;
   }
 
-  public setRoleStakingPolicyAmount(value: BN) {
+  public setRoleStakingPolicyAmount(value: BN): WorkingGroupOpening {
     this.roleStakingPolicyAmount = value;
+    return this;
   }
 
-  public setRoleCrowdedOutUnstakingPeriodLength(value: BN) {
+  public setRoleCrowdedOutUnstakingPeriodLength(value: BN): WorkingGroupOpening {
     this.roleCrowdedOutUnstakingPeriodLength = value;
+    return this;
   }
 
-  public setRoleExpiredUnstakingPeriodLength(value: BN) {
+  public setRoleExpiredUnstakingPeriodLength(value: BN): WorkingGroupOpening {
     this.roleExpiredUnstakingPeriodLength = value;
+    return this;
   }
 
-  public setSlashableMaxCount(value: BN) {
+  public setSlashableMaxCount(value: BN): WorkingGroupOpening {
     this.slashableMaxCount = value;
+    return this;
   }
 
-  public setSlashableMaxPercentPtsPerTime(value: BN) {
+  public setSlashableMaxPercentPtsPerTime(value: BN): WorkingGroupOpening {
     this.slashableMaxPercentPtsPerTime = value;
+    return this;
   }
 
-  public setSuccessfulApplicantApplicationStakeUnstakingPeriod(value: BN) {
+  public setSuccessfulApplicantApplicationStakeUnstakingPeriod(value: BN): WorkingGroupOpening {
     this.successfulApplicantApplicationStakeUnstakingPeriod = value;
+    return this;
   }
 
-  public setFailedApplicantApplicationStakeUnstakingPeriod(value: BN) {
+  public setFailedApplicantApplicationStakeUnstakingPeriod(value: BN): WorkingGroupOpening {
     this.failedApplicantApplicationStakeUnstakingPeriod = value;
+    return this;
   }
 
-  public setFailedApplicantRoleStakeUnstakingPeriod(value: BN) {
+  public setFailedApplicantRoleStakeUnstakingPeriod(value: BN): WorkingGroupOpening {
     this.failedApplicantRoleStakeUnstakingPeriod = value;
+    return this;
   }
 
-  public setTerminateApplicationStakeUnstakingPeriod(value: BN) {
+  public setTerminateApplicationStakeUnstakingPeriod(value: BN): WorkingGroupOpening {
     this.terminateApplicationStakeUnstakingPeriod = value;
+    return this;
   }
 
-  public setTerminateRoleStakeUnstakingPeriod(value: BN) {
+  public setTerminateRoleStakeUnstakingPeriod(value: BN): WorkingGroupOpening {
     this.terminateRoleStakeUnstakingPeriod = value;
+    return this;
   }
 
-  public setExitRoleApplicationStakeUnstakingPeriod(value: BN) {
+  public setExitRoleApplicationStakeUnstakingPeriod(value: BN): WorkingGroupOpening {
     this.exitRoleApplicationStakeUnstakingPeriod = value;
+    return this;
   }
 
-  public setExitRoleStakeUnstakingPeriod(value: BN) {
+  public setExitRoleStakeUnstakingPeriod(value: BN): WorkingGroupOpening {
     this.exitRoleStakeUnstakingPeriod = value;
+    return this;
   }
 
-  public setText(value: string) {
+  public setText(value: string): WorkingGroupOpening {
     this.text = value;
+    return this;
   }
 
-  public setOpeningType(value: string) {
+  public setOpeningType(value: string): WorkingGroupOpening {
     this.openingType = value;
+    return this;
   }
 
   constructor() {
@@ -223,4 +243,13 @@ export class WorkingGroupOpening {
       exit_role_stake_unstaking_period: this.exitRoleStakeUnstakingPeriod,
     };
   }
+
+  public getAddOpeningParameters(workingGroup: string) {
+    return {
+      activate_at: this.getActivateAt(),
+      commitment: this.getCommitment(),
+      human_readable_text: this.getText(),
+      working_group: workingGroup,
+    }
+  }
 }

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

@@ -23,25 +23,25 @@ export async function createWorkingGroupLeaderOpening(
   await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, proposalFee.add(proposalStake));
 
   // Opening construction
-  const opening = new WorkingGroupOpening();
-  opening.setMaxActiveApplicants(new BN(m1KeyPairs.length));
-  opening.setMaxReviewPeriodLength(new BN(32));
-  opening.setApplicationStakingPolicyAmount(new BN(applicationStake));
-  opening.setApplicationCrowdedOutUnstakingPeriodLength(new BN(1));
-  opening.setApplicationExpiredUnstakingPeriodLength(new BN(1));
-  opening.setRoleStakingPolicyAmount(new BN(roleStake));
-  opening.setRoleCrowdedOutUnstakingPeriodLength(new BN(1));
-  opening.setRoleExpiredUnstakingPeriodLength(new BN(1));
-  opening.setSlashableMaxCount(new BN(1));
-  opening.setSlashableMaxPercentPtsPerTime(new BN(100));
-  opening.setSuccessfulApplicantApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setFailedApplicantApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setFailedApplicantRoleStakeUnstakingPeriod(new BN(1));
-  opening.setTerminateApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setTerminateRoleStakeUnstakingPeriod(new BN(1));
-  opening.setExitRoleApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setExitRoleStakeUnstakingPeriod(new BN(1));
-  opening.setText(uuid().substring(0, 8));
+  const opening = new WorkingGroupOpening()
+    .setMaxActiveApplicants(new BN(m1KeyPairs.length))
+    .setMaxReviewPeriodLength(new BN(32))
+    .setApplicationStakingPolicyAmount(new BN(applicationStake))
+    .setApplicationCrowdedOutUnstakingPeriodLength(new BN(1))
+    .setApplicationExpiredUnstakingPeriodLength(new BN(1))
+    .setRoleStakingPolicyAmount(new BN(roleStake))
+    .setRoleCrowdedOutUnstakingPeriodLength(new BN(1))
+    .setRoleExpiredUnstakingPeriodLength(new BN(1))
+    .setSlashableMaxCount(new BN(1))
+    .setSlashableMaxPercentPtsPerTime(new BN(100))
+    .setSuccessfulApplicantApplicationStakeUnstakingPeriod(new BN(1))
+    .setFailedApplicantApplicationStakeUnstakingPeriod(new BN(1))
+    .setFailedApplicantRoleStakeUnstakingPeriod(new BN(1))
+    .setTerminateApplicationStakeUnstakingPeriod(new BN(1))
+    .setTerminateRoleStakeUnstakingPeriod(new BN(1))
+    .setExitRoleApplicationStakeUnstakingPeriod(new BN(1))
+    .setExitRoleStakeUnstakingPeriod(new BN(1))
+    .setText(uuid().substring(0, 8));
 
   // Proposal creation
   const proposalPromise = apiWrapper.expectProposalCreated();
@@ -113,13 +113,13 @@ export async function fillLeaderOpeningProposal(
     await apiWrapper.getActiveApplicationsIdsByRoleAccount(applicantRoleAccountAddress, workingGroup)
   )[0];
   const now = await apiWrapper.getBestBlock();
-  const fillOpeningParameters: FillOpeningParameters = new FillOpeningParameters();
-  fillOpeningParameters.setAmountPerPayout(payoutAmount);
-  fillOpeningParameters.setNextPaymentAtBlock(now.add(firstRewardInterval));
-  fillOpeningParameters.setPayoutInterval(rewardInterval);
-  fillOpeningParameters.setOpeningId(openingId);
-  fillOpeningParameters.setSuccessfulApplicationId(applicationId);
-  fillOpeningParameters.setWorkingGroup(workingGroupString);
+  const fillOpeningParameters: FillOpeningParameters = new FillOpeningParameters()
+    .setAmountPerPayout(payoutAmount)
+    .setNextPaymentAtBlock(now.add(firstRewardInterval))
+    .setPayoutInterval(rewardInterval)
+    .setOpeningId(openingId)
+    .setSuccessfulApplicationId(applicationId)
+    .setWorkingGroup(workingGroupString);
 
   const proposalPromise = apiWrapper.expectProposalCreated();
   await apiWrapper.proposeFillLeaderOpening(

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

@@ -19,7 +19,7 @@ export function workingGroupMintCapacityProposalTest(
   tap.test('Mint capacity proposal test', async () => {
     // Setup
     sudo = keyring.addFromUri(sudoUri);
-    const description: string = 'spending proposal which is used for API network testing';
+    const description: string = 'Mint capacity proposal which is used for API network testing';
     const runtimeVoteFee: BN = apiWrapper.estimateVoteForProposalFee();
     const initialMintingCapacity: BN = await apiWrapper.getContentWorkingGroupMintCapacity();
 

+ 49 - 43
tests/network-tests/src/nicaea/tests/workingGroup/impl/workingGroupModule.ts

@@ -24,30 +24,30 @@ export async function addWorkerOpening(
   expectFailure: boolean
 ): Promise<BN> {
   // Worker opening construction
-  const opening = new WorkingGroupOpening();
   const activateAtBlock: BN | undefined = activationDelay.eqn(0)
     ? undefined
     : (await apiWrapper.getBestBlock()).add(activationDelay);
-  opening.setActivateAtBlock(activateAtBlock);
-  opening.setMaxActiveApplicants(new BN(membersKeyPairs.length));
-  opening.setMaxReviewPeriodLength(new BN(32));
-  opening.setApplicationStakingPolicyAmount(new BN(applicationStake));
-  opening.setApplicationCrowdedOutUnstakingPeriodLength(new BN(1));
-  opening.setApplicationExpiredUnstakingPeriodLength(new BN(1));
-  opening.setRoleStakingPolicyAmount(new BN(roleStake));
-  opening.setRoleCrowdedOutUnstakingPeriodLength(new BN(1));
-  opening.setRoleExpiredUnstakingPeriodLength(new BN(1));
-  opening.setSlashableMaxCount(new BN(1));
-  opening.setSlashableMaxPercentPtsPerTime(new BN(100));
-  opening.setSuccessfulApplicantApplicationStakeUnstakingPeriod(unstakingPeriod);
-  opening.setFailedApplicantApplicationStakeUnstakingPeriod(unstakingPeriod);
-  opening.setFailedApplicantRoleStakeUnstakingPeriod(unstakingPeriod);
-  opening.setTerminateApplicationStakeUnstakingPeriod(unstakingPeriod);
-  opening.setTerminateRoleStakeUnstakingPeriod(unstakingPeriod);
-  opening.setExitRoleApplicationStakeUnstakingPeriod(unstakingPeriod);
-  opening.setExitRoleStakeUnstakingPeriod(unstakingPeriod);
-  opening.setText(uuid().substring(0, 8));
-  opening.setOpeningType('Worker');
+  const opening = new WorkingGroupOpening()
+    .setActivateAtBlock(activateAtBlock)
+    .setMaxActiveApplicants(new BN(membersKeyPairs.length))
+    .setMaxReviewPeriodLength(new BN(32))
+    .setApplicationStakingPolicyAmount(new BN(applicationStake))
+    .setApplicationCrowdedOutUnstakingPeriodLength(new BN(1))
+    .setApplicationExpiredUnstakingPeriodLength(new BN(1))
+    .setRoleStakingPolicyAmount(new BN(roleStake))
+    .setRoleCrowdedOutUnstakingPeriodLength(new BN(1))
+    .setRoleExpiredUnstakingPeriodLength(new BN(1))
+    .setSlashableMaxCount(new BN(1))
+    .setSlashableMaxPercentPtsPerTime(new BN(100))
+    .setSuccessfulApplicantApplicationStakeUnstakingPeriod(unstakingPeriod)
+    .setFailedApplicantApplicationStakeUnstakingPeriod(unstakingPeriod)
+    .setFailedApplicantRoleStakeUnstakingPeriod(unstakingPeriod)
+    .setTerminateApplicationStakeUnstakingPeriod(unstakingPeriod)
+    .setTerminateRoleStakeUnstakingPeriod(unstakingPeriod)
+    .setExitRoleApplicationStakeUnstakingPeriod(unstakingPeriod)
+    .setExitRoleStakeUnstakingPeriod(unstakingPeriod)
+    .setText(uuid().substring(0, 8))
+    .setOpeningType('Worker');
 
   // Fee estimation and transfer
   const addOpeningFee: BN = apiWrapper.estimateAddOpeningFee(opening, module);
@@ -76,27 +76,27 @@ export async function addLeaderOpening(
   const activateAtBlock: BN | undefined = activationDelay.eqn(0)
     ? undefined
     : (await apiWrapper.getBestBlock()).add(activationDelay);
-  const opening = new WorkingGroupOpening();
-  opening.setActivateAtBlock(activateAtBlock);
-  opening.setMaxActiveApplicants(new BN(membersKeyPairs.length));
-  opening.setMaxReviewPeriodLength(new BN(32));
-  opening.setApplicationStakingPolicyAmount(new BN(applicationStake));
-  opening.setApplicationCrowdedOutUnstakingPeriodLength(new BN(1));
-  opening.setApplicationExpiredUnstakingPeriodLength(new BN(1));
-  opening.setRoleStakingPolicyAmount(new BN(roleStake));
-  opening.setRoleCrowdedOutUnstakingPeriodLength(new BN(1));
-  opening.setRoleExpiredUnstakingPeriodLength(new BN(1));
-  opening.setSlashableMaxCount(new BN(1));
-  opening.setSlashableMaxPercentPtsPerTime(new BN(100));
-  opening.setSuccessfulApplicantApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setFailedApplicantApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setFailedApplicantRoleStakeUnstakingPeriod(new BN(1));
-  opening.setTerminateApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setTerminateRoleStakeUnstakingPeriod(new BN(1));
-  opening.setExitRoleApplicationStakeUnstakingPeriod(new BN(1));
-  opening.setExitRoleStakeUnstakingPeriod(new BN(1));
-  opening.setText(uuid().substring(0, 8));
-  opening.setOpeningType('leader');
+  const opening = new WorkingGroupOpening()
+    .setActivateAtBlock(activateAtBlock)
+    .setMaxActiveApplicants(new BN(membersKeyPairs.length))
+    .setMaxReviewPeriodLength(new BN(32))
+    .setApplicationStakingPolicyAmount(new BN(applicationStake))
+    .setApplicationCrowdedOutUnstakingPeriodLength(new BN(1))
+    .setApplicationExpiredUnstakingPeriodLength(new BN(1))
+    .setRoleStakingPolicyAmount(new BN(roleStake))
+    .setRoleCrowdedOutUnstakingPeriodLength(new BN(1))
+    .setRoleExpiredUnstakingPeriodLength(new BN(1))
+    .setSlashableMaxCount(new BN(1))
+    .setSlashableMaxPercentPtsPerTime(new BN(100))
+    .setSuccessfulApplicantApplicationStakeUnstakingPeriod(new BN(1))
+    .setFailedApplicantApplicationStakeUnstakingPeriod(new BN(1))
+    .setFailedApplicantRoleStakeUnstakingPeriod(new BN(1))
+    .setTerminateApplicationStakeUnstakingPeriod(new BN(1))
+    .setTerminateRoleStakeUnstakingPeriod(new BN(1))
+    .setExitRoleApplicationStakeUnstakingPeriod(new BN(1))
+    .setExitRoleStakeUnstakingPeriod(new BN(1))
+    .setText(uuid().substring(0, 8))
+    .setOpeningType('leader');
 
   const addOpeningPromise: Promise<BN> = apiWrapper.expectOpeningAdded();
   await apiWrapper.sudoAddOpening(sudo, opening, module);
@@ -286,6 +286,12 @@ export async function fillLeaderOpening(
       `Role account ids does not match, leader account: ${worker.role_account_id}, application account ${application.role_account_id}`
     );
   });
+  const leadWorkerId: BN = (await apiWrapper.getLeadWorkerId(module))!;
+  const openingLeaderAccount: string = (await apiWrapper.getWorkerById(leadWorkerId, module)).role_account_id.toString();
+  assert(
+    openingLeaderAccount === membersKeyPairs[0].address, 
+    `Unexpected leader account ${openingLeaderAccount}, expected ${membersKeyPairs[0].address}`
+    );
 }
 
 export async function increaseStake(
@@ -519,7 +525,7 @@ export async function expectLeaderSet(
   const leaderApplicationId = (await apiWrapper.getApplicationsIdsByRoleAccount(leaderAddress, module))[0];
   const application: Application = await apiWrapper.getApplicationById(leaderApplicationId, module);
   assert(
-    worker.role_account_id.toString() === application.role_account_id.toString(),
+    worker.role_account_id.eq(application.role_account_id),
     `Role account ids does not match, leader account: ${worker.role_account_id}, application account ${application.role_account_id}`
   );
   return leadWorkerId;

+ 36 - 28
tests/network-tests/src/nicaea/utils/apiWrapper.ts

@@ -11,7 +11,7 @@ import { RoleParameters } from '@nicaea/types/roles';
 import { Seat } from '@nicaea/types/council';
 import { Balance, EventRecord, AccountId, BlockNumber, BalanceOf } from '@polkadot/types/interfaces';
 import BN from 'bn.js';
-import { SubmittableExtrinsic } from '@polkadot/api/types';
+import { SubmittableExtrinsic, UnsubscribePromise } from '@polkadot/api/types';
 import { Sender } from './sender';
 import { Utils } from './utils';
 import { Stake, StakedState } from '@nicaea/types/stake';
@@ -47,7 +47,8 @@ export class ApiWrapper {
       case WorkingGroups.storageWorkingGroup:
         return 'Storage';
       default:
-        return 'Undefined';
+        throw new Error(`Invalid working group string representation: ${workingGroup}`);
+        ;
     }
   }
 
@@ -335,7 +336,7 @@ export class ApiWrapper {
         {
           activate_at: 'CurrentBlock',
           commitment: {
-            application_rationing_policy: { max_active_applicants: '32' },
+            application_rationing_policy: { max_active_applicants: 32 },
             max_review_period_length: 32,
             application_staking_policy: {
               amount: 0,
@@ -384,13 +385,13 @@ export class ApiWrapper {
   }
 
   public estimateProposeFillLeaderOpeningFee(): BN {
-    const fillOpeningParameters: FillOpeningParameters = new FillOpeningParameters();
-    fillOpeningParameters.setAmountPerPayout(new BN(1));
-    fillOpeningParameters.setNextPaymentAtBlock(new BN(99999));
-    fillOpeningParameters.setPayoutInterval(new BN(99999));
-    fillOpeningParameters.setOpeningId(new BN(0));
-    fillOpeningParameters.setSuccessfulApplicationId(new BN(0));
-    fillOpeningParameters.setWorkingGroup('Storage');
+    const fillOpeningParameters: FillOpeningParameters = new FillOpeningParameters()
+      .setAmountPerPayout(new BN(1))
+      .setNextPaymentAtBlock(new BN(99999))
+      .setPayoutInterval(new BN(99999))
+      .setOpeningId(new BN(0))
+      .setSuccessfulApplicationId(new BN(0))
+      .setWorkingGroup('Storage');
 
     return this.estimateTxFee(
       this.api.tx.proposalsCodex.createFillWorkingGroupLeaderOpeningProposal(
@@ -808,9 +809,10 @@ export class ApiWrapper {
 
   public expectProposalCreated(): Promise<BN> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'ProposalCreated') {
+            unsubscribe();
             resolve(new BN(record.event.data[1].toString()));
           }
         });
@@ -820,9 +822,10 @@ export class ApiWrapper {
 
   public expectRuntimeUpgraded(): Promise<void> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method.toString() === 'RuntimeUpdated') {
+            unsubscribe();
             resolve();
           }
         });
@@ -832,13 +835,14 @@ export class ApiWrapper {
 
   public expectProposalFinalized(): Promise<void> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (
             record.event.method &&
             record.event.method.toString() === 'ProposalStatusUpdated' &&
             record.event.data[1].toString().includes('Executed')
           ) {
+            unsubscribe();
             resolve();
           }
         });
@@ -848,9 +852,10 @@ export class ApiWrapper {
 
   public expectOpeningFilled(): Promise<ApplicationIdToWorkerIdMap> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'OpeningFilled') {
+            unsubscribe();
             resolve((record.event.data[1] as unknown) as ApplicationIdToWorkerIdMap);
           }
         });
@@ -860,9 +865,10 @@ export class ApiWrapper {
 
   public expectOpeningAdded(): Promise<BN> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'OpeningAdded') {
+            unsubscribe();
             resolve((record.event.data as unknown) as BN);
           }
         });
@@ -872,9 +878,10 @@ export class ApiWrapper {
 
   public expectLeaderSet(): Promise<BN> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'LeaderSet') {
+            unsubscribe();
             resolve((record.event.data as unknown) as BN);
           }
         });
@@ -884,9 +891,10 @@ export class ApiWrapper {
 
   public expectLeaderTerminated(): Promise<void> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'TerminatedLeader') {
+            unsubscribe();
             resolve();
           }
         });
@@ -896,9 +904,10 @@ export class ApiWrapper {
 
   public expectWorkerRewardAmountUpdated(): Promise<void> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'WorkerRewardAmountUpdated') {
+            unsubscribe();
             resolve();
           }
         });
@@ -908,9 +917,10 @@ export class ApiWrapper {
 
   public expectWorkerStakeDecreased(): Promise<void> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'StakeDecreased') {
+            unsubscribe();
             resolve();
           }
         });
@@ -920,9 +930,10 @@ export class ApiWrapper {
 
   public expectWorkerStakeSlashed(): Promise<void> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'StakeSlashed') {
+            unsubscribe();
             resolve();
           }
         });
@@ -932,9 +943,10 @@ export class ApiWrapper {
 
   public expectApplicationReviewBegan(): Promise<BN> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'BeganApplicationReview') {
+            unsubscribe();
             resolve((record.event.data as unknown) as BN);
           }
         });
@@ -944,9 +956,10 @@ export class ApiWrapper {
 
   public expectMintCapacityChanged(): Promise<BN> {
     return new Promise(async resolve => {
-      await this.api.query.system.events<Vec<EventRecord>>(events => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>(events => {
         events.forEach(record => {
           if (record.event.method && record.event.method.toString() === 'MintCapacityChanged') {
+            unsubscribe();
             resolve((record.event.data[1] as unknown) as BN);
           }
         });
@@ -1040,12 +1053,7 @@ export class ApiWrapper {
         title,
         description,
         proposalStake,
-        {
-          activate_at: opening.getActivateAt(),
-          commitment: opening.getCommitment(),
-          human_readable_text: opening.getText(),
-          working_group: workingGroup,
-        }
+        opening.getAddOpeningParameters(workingGroup)
       ),
       account,
       false

+ 2 - 1
types/.eslintrc.js

@@ -3,6 +3,7 @@ module.exports = {
     '@typescript-eslint/camelcase': 'off',
     '@typescript-eslint/class-name-casing': 'off',
     'no-unused-vars': 'off', // Required by the typescript rule below
-    '@typescript-eslint/no-unused-vars': ['error']
+    '@typescript-eslint/no-unused-vars': ['error'],
+    "@typescript-eslint/naming-convention": 'off'
   }
 }