Browse Source

Merge branch 'sumer' into query_node_predictable_ids

ondratra 3 years ago
parent
commit
06b4375468
33 changed files with 283 additions and 51 deletions
  1. 4 4
      cli/README.md
  2. 16 6
      cli/src/base/ApiCommandBase.ts
  3. 2 2
      cli/src/base/UploadCommandBase.ts
  4. 1 1
      cli/src/commands/account/create.ts
  5. 4 2
      cli/src/commands/account/export.ts
  6. 1 1
      cli/src/commands/account/import.ts
  7. 1 1
      cli/src/commands/account/transferTokens.ts
  8. 1 1
      cli/src/commands/content/addCuratorToGroup.ts
  9. 9 1
      cli/src/commands/content/createChannel.ts
  10. 9 1
      cli/src/commands/content/createChannelCategory.ts
  11. 1 1
      cli/src/commands/content/createCuratorGroup.ts
  12. 10 1
      cli/src/commands/content/createVideo.ts
  13. 9 1
      cli/src/commands/content/createVideoCategory.ts
  14. 1 1
      cli/src/commands/content/reuploadAssets.ts
  15. 1 1
      cli/src/commands/content/setCuratorGroupStatus.ts
  16. 1 1
      cli/src/commands/content/updateChannelCensorshipStatus.ts
  17. 1 1
      cli/src/commands/content/updateVideoCensorshipStatus.ts
  18. 2 2
      cli/src/commands/content/videos.ts
  19. 2 2
      cli/src/commands/working-groups/createOpening.ts
  20. 1 1
      cli/src/commands/working-groups/fillOpening.ts
  21. 1 1
      cli/src/commands/working-groups/leaveRole.ts
  22. 1 1
      cli/src/commands/working-groups/setDefaultGroup.ts
  23. 1 1
      cli/src/commands/working-groups/slashWorker.ts
  24. 1 1
      cli/src/commands/working-groups/terminateApplication.ts
  25. 1 1
      cli/src/commands/working-groups/updateRewardAccount.ts
  26. 1 1
      cli/src/commands/working-groups/updateRoleAccount.ts
  27. 1 1
      cli/src/commands/working-groups/updateRoleStorage.ts
  28. 1 1
      cli/src/helpers/InputOutput.ts
  29. 1 1
      cli/tsconfig.json
  30. 70 0
      query-node/generated/types/members.ts
  31. 1 0
      query-node/manifest.yml
  32. 45 3
      query-node/mappings/src/common.ts
  33. 81 7
      query-node/mappings/src/membership.ts

+ 4 - 4
cli/README.md

@@ -45,7 +45,7 @@ $ npm install -g @joystream/cli
 $ joystream-cli COMMAND
 running command...
 $ joystream-cli (-v|--version|version)
-@joystream/cli/0.5.0 linux-x64 node-v13.12.0
+@joystream/cli/0.5.0 linux-x64 node-v14.16.1
 $ joystream-cli --help [COMMAND]
 USAGE
   $ joystream-cli COMMAND
@@ -314,7 +314,7 @@ EXAMPLES
   $ joystream-cli autocomplete --refresh-cache
 ```
 
-_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v0.2.0/src/commands/autocomplete/index.ts)_
+_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v0.2.1/src/commands/autocomplete/index.ts)_
 
 ## `joystream-cli content:addCuratorToGroup [GROUPID] [CURATORID]`
 
@@ -506,7 +506,7 @@ _See code: [src/commands/content/removeCuratorFromGroup.ts](https://github.com/J
 
 ## `joystream-cli content:reuploadAssets`
 
-Allows reuploading assets that were not succesfully uploaded during channel/video creation
+Allows reuploading assets that were not successfully uploaded during channel/video creation
 
 ```
 USAGE
@@ -707,7 +707,7 @@ OPTIONS
   --all  see all commands in CLI
 ```
 
-_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v2.2.3/src/commands/help.ts)_
+_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.2/src/commands/help.ts)_
 
 ## `joystream-cli working-groups:application WGAPPLICATIONID`
 

+ 16 - 6
cli/src/base/ApiCommandBase.ts

@@ -6,7 +6,7 @@ import { getTypeDef, Option, Tuple, TypeRegistry } from '@polkadot/types'
 import { Registry, Codec, CodecArg, TypeDef, TypeDefInfo } from '@polkadot/types/types'
 
 import { Vec, Struct, Enum } from '@polkadot/types/codec'
-import { ApiPromise, WsProvider } from '@polkadot/api'
+import { ApiPromise, SubmittableResult, WsProvider } from '@polkadot/api'
 import { KeyringPair } from '@polkadot/keyring/types'
 import chalk from 'chalk'
 import { InterfaceTypes } from '@polkadot/types/types/registry'
@@ -16,6 +16,7 @@ import { SubmittableExtrinsic } from '@polkadot/api/types'
 import { DistinctQuestion } from 'inquirer'
 import { BOOL_PROMPT_OPTIONS } from '../helpers/prompting'
 import { DispatchError } from '@polkadot/types/interfaces/system'
+import { Event } from '@polkadot/types/interfaces'
 
 export class ExtrinsicFailedError extends Error {}
 
@@ -351,7 +352,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     return values
   }
 
-  sendExtrinsic(account: KeyringPair, tx: SubmittableExtrinsic<'promise'>) {
+  sendExtrinsic(account: KeyringPair, tx: SubmittableExtrinsic<'promise'>): Promise<SubmittableResult> {
     return new Promise((resolve, reject) => {
       let unsubscribe: () => void
       tx.signAndSend(account, {}, (result) => {
@@ -401,11 +402,11 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     account: KeyringPair,
     tx: SubmittableExtrinsic<'promise'>,
     warnOnly = false // If specified - only warning will be displayed in case of failure (instead of error beeing thrown)
-  ): Promise<boolean> {
+  ): Promise<SubmittableResult | false> {
     try {
-      await this.sendExtrinsic(account, tx)
+      const res = await this.sendExtrinsic(account, tx)
       this.log(chalk.green(`Extrinsic successful!`))
-      return true
+      return res
     } catch (e) {
       if (e instanceof ExtrinsicFailedError && warnOnly) {
         this.warn(`Extrinsic failed! ${e.message}`)
@@ -424,12 +425,21 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     method: string,
     params: CodecArg[],
     warnOnly = false
-  ): Promise<boolean> {
+  ): Promise<SubmittableResult | false> {
     this.log(chalk.magentaBright(`\nSending ${module}.${method} extrinsic...`))
     const tx = await this.getOriginalApi().tx[module][method](...params)
     return await this.sendAndFollowTx(account, tx, warnOnly)
   }
 
+  // TODO:
+  // Switch to:
+  // public findEvent<S extends keyof AugmentedEvents<'promise'> & string, M extends keyof AugmentedEvents<'promise'>[S] & string>
+  //          (result: SubmittableResult, section: S, method: M): Event | undefined {
+  // Once augment-api is supported
+  public findEvent(result: SubmittableResult, section: string, method: string): Event | undefined {
+    return result.findRecord(section, method)?.event
+  }
+
   async buildAndSendExtrinsic(
     account: KeyringPair,
     module: string,

+ 2 - 2
cli/src/base/UploadCommandBase.ts

@@ -263,7 +263,7 @@ export default abstract class UploadCommandBase extends ContentDirectoryCommandB
     )
     if (rejectedAssetsOutput.length) {
       this.warn(
-        `Some assets were not uploaded succesfully. Try reuploading them with ${chalk.magentaBright(
+        `Some assets were not uploaded successfully. Try reuploading them with ${chalk.magentaBright(
           'content:reuploadAssets'
         )}!`
       )
@@ -271,7 +271,7 @@ export default abstract class UploadCommandBase extends ContentDirectoryCommandB
       const outputPath = inputFilePath.replace('.json', `${outputFilePostfix}.json`)
       try {
         fs.writeFileSync(outputPath, JSON.stringify(rejectedAssetsOutput, null, 4))
-        this.log(`Rejected content ids succesfully saved to: ${chalk.magentaBright(outputPath)}!`)
+        this.log(`Rejected content ids successfully saved to: ${chalk.magentaBright(outputPath)}!`)
       } catch (e) {
         console.error(e)
         this.warn(

+ 1 - 1
cli/src/commands/account/create.ts

@@ -40,7 +40,7 @@ export default class AccountCreate extends AccountsCommandBase {
 
     this.saveAccount(keys, password)
 
-    this.log(chalk.greenBright(`\nAccount succesfully created!`))
+    this.log(chalk.greenBright(`\nAccount successfully created!`))
     this.log(chalk.magentaBright(`${chalk.bold('Name:    ')}${args.name}`))
     this.log(chalk.magentaBright(`${chalk.bold('Address: ')}${keys.address}`))
   }

+ 4 - 2
cli/src/commands/account/export.ts

@@ -59,7 +59,9 @@ export default class AccountExport extends AccountsCommandBase {
         this.error(`Failed to create the export folder (${destPath})`, { exit: ExitCodes.FsOperationFailed })
       }
       for (const account of accounts) this.exportAccount(account, destPath)
-      this.log(chalk.greenBright(`All accounts succesfully exported succesfully to: ${chalk.magentaBright(destPath)}!`))
+      this.log(
+        chalk.greenBright(`All accounts successfully exported successfully to: ${chalk.magentaBright(destPath)}!`)
+      )
     } else {
       const destPath: string = args.path
       const choosenAccount: NamedKeyringPair = await this.promptForAccount(
@@ -68,7 +70,7 @@ export default class AccountExport extends AccountsCommandBase {
         'Select an account to export'
       )
       const exportedFilePath: string = this.exportAccount(choosenAccount, destPath)
-      this.log(chalk.greenBright(`Account succesfully exported to: ${chalk.magentaBright(exportedFilePath)}`))
+      this.log(chalk.greenBright(`Account successfully exported to: ${chalk.magentaBright(exportedFilePath)}`))
     }
   }
 }

+ 1 - 1
cli/src/commands/account/import.ts

@@ -37,7 +37,7 @@ export default class AccountImport extends AccountsCommandBase {
       })
     }
 
-    this.log(chalk.bold.greenBright(`ACCOUNT IMPORTED SUCCESFULLY!`))
+    this.log(chalk.bold.greenBright(`ACCOUNT IMPORTED SUCCESSFULLY!`))
     this.log(chalk.bold.magentaBright(`NAME:    `), accountName)
     this.log(chalk.bold.magentaBright(`ADDRESS: `), accountAddress)
   }

+ 1 - 1
cli/src/commands/account/transferTokens.ts

@@ -58,7 +58,7 @@ export default class AccountTransferTokens extends AccountsCommandBase {
 
     try {
       const txHash: Hash = await tx.signAndSend(selectedAccount)
-      this.log(chalk.greenBright('Transaction succesfully sent!'))
+      this.log(chalk.greenBright('Transaction successfully sent!'))
       this.log(chalk.magentaBright('Hash:', txHash.toString()))
     } catch (e) {
       this.error('Could not send the transaction.', { exit: ExitCodes.UnexpectedException })

+ 1 - 1
cli/src/commands/content/addCuratorToGroup.ts

@@ -39,7 +39,7 @@ export default class AddCuratorToGroupCommand extends ContentDirectoryCommandBas
 
     console.log(
       chalk.green(
-        `Curator ${chalk.magentaBright(curatorId)} succesfully added to group ${chalk.magentaBright(groupId)}!`
+        `Curator ${chalk.magentaBright(curatorId)} successfully added to group ${chalk.magentaBright(groupId)}!`
       )
     )
   }

+ 9 - 1
cli/src/commands/content/createChannel.ts

@@ -7,6 +7,7 @@ import { ChannelCreationParameters } from '@joystream/types/content'
 import { ChannelInputSchema } from '../../json-schemas/ContentDirectory'
 import ContentDirectoryCommandBase from '../../base/ContentDirectoryCommandBase'
 import UploadCommandBase from '../../base/UploadCommandBase'
+import chalk from 'chalk'
 
 export default class CreateChannelCommand extends UploadCommandBase {
   static description = 'Create channel inside content directory.'
@@ -55,7 +56,14 @@ export default class CreateChannelCommand extends UploadCommandBase {
 
     await this.requireConfirmation('Do you confirm the provided input?', true)
 
-    await this.sendAndFollowNamedTx(account, 'content', 'createChannel', [actor, channelCreationParameters])
+    const result = await this.sendAndFollowNamedTx(account, 'content', 'createChannel', [
+      actor,
+      channelCreationParameters,
+    ])
+    if (result) {
+      const event = this.findEvent(result, 'content', 'ChannelCreated')
+      this.log(chalk.green(`Channel with id ${chalk.cyanBright(event?.data[1].toString())} successfully created!`))
+    }
 
     await this.uploadAssets(inputAssets, input)
   }

+ 9 - 1
cli/src/commands/content/createChannelCategory.ts

@@ -6,6 +6,7 @@ import { flags } from '@oclif/command'
 import { CreateInterface } from '@joystream/types'
 import { ChannelCategoryCreationParameters } from '@joystream/types/content'
 import { ChannelCategoryInputSchema } from '../../json-schemas/ContentDirectory'
+import chalk from 'chalk'
 
 export default class CreateChannelCategoryCommand extends ContentDirectoryCommandBase {
   static description = 'Create channel category inside content directory.'
@@ -38,9 +39,16 @@ export default class CreateChannelCategoryCommand extends ContentDirectoryComman
 
     await this.requireConfirmation('Do you confirm the provided input?', true)
 
-    await this.sendAndFollowNamedTx(currentAccount, 'content', 'createChannelCategory', [
+    const result = await this.sendAndFollowNamedTx(currentAccount, 'content', 'createChannelCategory', [
       actor,
       channelCategoryCreationParameters,
     ])
+
+    if (result) {
+      const event = this.findEvent(result, 'content', 'ChannelCategoryCreated')
+      this.log(
+        chalk.green(`ChannelCategory with id ${chalk.cyanBright(event?.data[0].toString())} successfully created!`)
+      )
+    }
   }
 }

+ 1 - 1
cli/src/commands/content/createCuratorGroup.ts

@@ -13,6 +13,6 @@ export default class CreateCuratorGroupCommand extends ContentDirectoryCommandBa
     await this.buildAndSendExtrinsic(account, 'content', 'createCuratorGroup')
 
     const newGroupId = (await this.getApi().nextCuratorGroupId()) - 1
-    console.log(chalk.green(`New group succesfully created! (ID: ${chalk.magentaBright(newGroupId)})`))
+    console.log(chalk.green(`New group successfully created! (ID: ${chalk.magentaBright(newGroupId)})`))
   }
 }

+ 10 - 1
cli/src/commands/content/createVideo.ts

@@ -7,6 +7,7 @@ import { flags } from '@oclif/command'
 import { VideoCreationParameters } from '@joystream/types/content'
 import { MediaType, VideoMetadata } from '@joystream/content-metadata-protobuf'
 import { VideoInputSchema } from '../../json-schemas/ContentDirectory'
+import chalk from 'chalk'
 
 export default class CreateVideoCommand extends UploadCommandBase {
   static description = 'Create video under specific channel inside content directory.'
@@ -78,7 +79,15 @@ export default class CreateVideoCommand extends UploadCommandBase {
 
     await this.requireConfirmation('Do you confirm the provided input?', true)
 
-    await this.sendAndFollowNamedTx(account, 'content', 'createVideo', [actor, channelId, videoCreationParameters])
+    const result = await this.sendAndFollowNamedTx(account, 'content', 'createVideo', [
+      actor,
+      channelId,
+      videoCreationParameters,
+    ])
+    if (result) {
+      const event = this.findEvent(result, 'content', 'VideoCreated')
+      this.log(chalk.green(`Video with id ${chalk.cyanBright(event?.data[2].toString())} successfully created!`))
+    }
 
     // Upload assets
     await this.uploadAssets(inputAssets, input)

+ 9 - 1
cli/src/commands/content/createVideoCategory.ts

@@ -6,6 +6,7 @@ import { flags } from '@oclif/command'
 import { CreateInterface } from '@joystream/types'
 import { VideoCategoryCreationParameters } from '@joystream/types/content'
 import { VideoCategoryInputSchema } from '../../json-schemas/ContentDirectory'
+import chalk from 'chalk'
 
 export default class CreateVideoCategoryCommand extends ContentDirectoryCommandBase {
   static description = 'Create video category inside content directory.'
@@ -38,9 +39,16 @@ export default class CreateVideoCategoryCommand extends ContentDirectoryCommandB
 
     await this.requireConfirmation('Do you confirm the provided input?', true)
 
-    await this.sendAndFollowNamedTx(currentAccount, 'content', 'createVideoCategory', [
+    const result = await this.sendAndFollowNamedTx(currentAccount, 'content', 'createVideoCategory', [
       actor,
       videoCategoryCreationParameters,
     ])
+
+    if (result) {
+      const event = this.findEvent(result, 'content', 'VideoCategoryCreated')
+      this.log(
+        chalk.green(`VideoCategory with id ${chalk.cyanBright(event?.data[1].toString())} successfully created!`)
+      )
+    }
   }
 }

+ 1 - 1
cli/src/commands/content/reuploadAssets.ts

@@ -6,7 +6,7 @@ import { flags } from '@oclif/command'
 import { ContentId } from '@joystream/types/storage'
 
 export default class ReuploadVideoAssetsCommand extends UploadCommandBase {
-  static description = 'Allows reuploading assets that were not succesfully uploaded during channel/video creation'
+  static description = 'Allows reuploading assets that were not successfully uploaded during channel/video creation'
 
   static flags = {
     input: flags.string({

+ 1 - 1
cli/src/commands/content/setCuratorGroupStatus.ts

@@ -52,7 +52,7 @@ export default class SetCuratorGroupStatusCommand extends ContentDirectoryComman
 
     console.log(
       chalk.green(
-        `Curator Group ${chalk.magentaBright(id)} status succesfully changed to: ${chalk.magentaBright(
+        `Curator Group ${chalk.magentaBright(id)} status successfully changed to: ${chalk.magentaBright(
           status ? 'Active' : 'Inactive'
         )}!`
       )

+ 1 - 1
cli/src/commands/content/updateChannelCensorshipStatus.ts

@@ -70,7 +70,7 @@ export default class UpdateChannelCensorshipStatusCommand extends ContentDirecto
 
     console.log(
       chalk.green(
-        `Channel ${chalk.magentaBright(id)} censorship status succesfully changed to: ${chalk.magentaBright(
+        `Channel ${chalk.magentaBright(id)} censorship status successfully changed to: ${chalk.magentaBright(
           status ? 'Censored' : 'Not censored'
         )}!`
       )

+ 1 - 1
cli/src/commands/content/updateVideoCensorshipStatus.ts

@@ -71,7 +71,7 @@ export default class UpdateVideoCensorshipStatusCommand extends ContentDirectory
 
     console.log(
       chalk.green(
-        `Video ${chalk.magentaBright(id)} censorship status succesfully changed to: ${chalk.magentaBright(
+        `Video ${chalk.magentaBright(id)} censorship status successfully changed to: ${chalk.magentaBright(
           status ? 'Censored' : 'Not censored'
         )}!`
       )

+ 2 - 2
cli/src/commands/content/videos.ts

@@ -8,7 +8,7 @@ export default class VideosCommand extends ContentDirectoryCommandBase {
   static args = [
     {
       name: 'channelId',
-      required: true,
+      required: false,
       description: 'ID of the Channel',
     },
   ]
@@ -34,7 +34,7 @@ export default class VideosCommand extends ContentDirectoryCommandBase {
         3
       )
     } else {
-      this.log('There are no videos yet')
+      this.log(`There are no videos${channelId ? ' in this channel' : ''} yet`)
     }
   }
 }

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

@@ -198,7 +198,7 @@ export default class WorkingGroupsCreateOpening extends WorkingGroupsCommandBase
       if (output) {
         try {
           saveOutputJsonToFile(output, rememberedInput)
-          this.log(chalk.green(`Output succesfully saved in: ${chalk.magentaBright(output)}!`))
+          this.log(chalk.green(`Output successfully saved in: ${chalk.magentaBright(output)}!`))
         } catch (e) {
           this.warn(`Could not save output to ${output}!`)
         }
@@ -218,7 +218,7 @@ export default class WorkingGroupsCreateOpening extends WorkingGroupsCommandBase
 
       // Display a success message on success or ask to try again on error
       if (txSuccess) {
-        this.log(chalk.green('Opening succesfully created!'))
+        this.log(chalk.green('Opening successfully created!'))
         tryAgain = false
       } else {
         tryAgain = await this.simplePrompt({ type: 'confirm', message: 'Try again with remembered input?' })

+ 1 - 1
cli/src/commands/working-groups/fillOpening.ts

@@ -39,7 +39,7 @@ export default class WorkingGroupsFillOpening extends WorkingGroupsCommandBase {
       rewardPolicyOpt,
     ])
 
-    this.log(chalk.green(`Opening ${chalk.magentaBright(openingId)} succesfully filled!`))
+    this.log(chalk.green(`Opening ${chalk.magentaBright(openingId)} successfully filled!`))
     this.log(
       chalk.green('Accepted working group application IDs: ') +
         chalk.magentaBright(applicationIds.length ? applicationIds.join(chalk.green(', ')) : 'NONE')

+ 1 - 1
cli/src/commands/working-groups/leaveRole.ts

@@ -23,6 +23,6 @@ export default class WorkingGroupsLeaveRole extends WorkingGroupsCommandBase {
 
     await this.sendAndFollowNamedTx(account, apiModuleByGroup[this.group], 'leaveRole', [worker.workerId, rationale])
 
-    this.log(chalk.green(`Succesfully left the role! (worker id: ${chalk.magentaBright(worker.workerId.toNumber())})`))
+    this.log(chalk.green(`Successfully left the role! (worker id: ${chalk.magentaBright(worker.workerId.toNumber())})`))
   }
 }

+ 1 - 1
cli/src/commands/working-groups/setDefaultGroup.ts

@@ -17,6 +17,6 @@ export default class SetDefaultGroupCommand extends WorkingGroupsCommandBase {
 
     await this.setPreservedState({ defaultWorkingGroup: group })
 
-    this.log(chalk.green(`${chalk.magentaBright(group)} succesfully set as default working group context`))
+    this.log(chalk.green(`${chalk.magentaBright(group)} successfully set as default working group context`))
   }
 }

+ 1 - 1
cli/src/commands/working-groups/slashWorker.ts

@@ -45,7 +45,7 @@ export default class WorkingGroupsSlashWorker extends WorkingGroupsCommandBase {
       chalk.green(
         `${chalk.magentaBright(formatBalance(balance))} from worker ${chalk.magentaBright(
           workerId
-        )} stake has been succesfully slashed!`
+        )} stake has been successfully slashed!`
       )
     )
   }

+ 1 - 1
cli/src/commands/working-groups/terminateApplication.ts

@@ -32,6 +32,6 @@ export default class WorkingGroupsTerminateApplication extends WorkingGroupsComm
 
     await this.sendAndFollowNamedTx(account, apiModuleByGroup[this.group], 'terminateApplication', [applicationId])
 
-    this.log(chalk.green(`Application ${chalk.magentaBright(applicationId)} has been succesfully terminated!`))
+    this.log(chalk.green(`Application ${chalk.magentaBright(applicationId)} has been successfully terminated!`))
   }
 }

+ 1 - 1
cli/src/commands/working-groups/updateRewardAccount.ts

@@ -43,6 +43,6 @@ export default class WorkingGroupsUpdateRewardAccount extends WorkingGroupsComma
       newRewardAccount,
     ])
 
-    this.log(chalk.green(`Succesfully updated the reward account to: ${chalk.magentaBright(newRewardAccount)})`))
+    this.log(chalk.green(`Successfully updated the reward account to: ${chalk.magentaBright(newRewardAccount)})`))
   }
 }

+ 1 - 1
cli/src/commands/working-groups/updateRoleAccount.ts

@@ -37,7 +37,7 @@ export default class WorkingGroupsUpdateRoleAccount extends WorkingGroupsCommand
       newRoleAccount,
     ])
 
-    this.log(chalk.green(`Succesfully updated the role account to: ${chalk.magentaBright(newRoleAccount)})`))
+    this.log(chalk.green(`Successfully updated the role account to: ${chalk.magentaBright(newRoleAccount)})`))
 
     const matchingAccount = cliAccounts.find((account) => account.address === newRoleAccount)
     if (matchingAccount) {

+ 1 - 1
cli/src/commands/working-groups/updateRoleStorage.ts

@@ -31,6 +31,6 @@ export default class WorkingGroupsUpdateRoleStorage extends WorkingGroupsCommand
       storage,
     ])
 
-    this.log(chalk.green(`Succesfully updated the associated worker storage to: ${chalk.magentaBright(storage)})`))
+    this.log(chalk.green(`Successfully updated the associated worker storage to: ${chalk.magentaBright(storage)})`))
   }
 }

+ 1 - 1
cli/src/helpers/InputOutput.ts

@@ -60,7 +60,7 @@ export function saveOutputJson(outputPath: string | undefined, fileName: string,
     }
     saveOutputJsonToFile(outputFilePath, data)
 
-    console.log(`${chalk.green('Output succesfully saved to:')} ${chalk.magentaBright(outputFilePath)}`)
+    console.log(`${chalk.green('Output successfully saved to:')} ${chalk.magentaBright(outputFilePath)}`)
   }
 }
 

+ 1 - 1
cli/tsconfig.json

@@ -12,7 +12,7 @@
     "noUnusedLocals": true,
     "baseUrl": ".",
     "paths": {
-      "@polkadot/types/augment": ["../types/augment-codec/augment-types.ts"],
+      "@polkadot/types/augment": ["../types/augment-codec/augment-types.ts"]
     },
     "resolveJsonModule": true,
     "skipLibCheck": true

+ 70 - 0
query-node/generated/types/members.ts

@@ -613,4 +613,74 @@ export namespace Members {
       ]);
     }
   }
+  /**
+   *  Update member's all or some of handle, avatar and about text.
+   */
+  export class UpdateMembershipCall {
+    public readonly extrinsic: SubstrateExtrinsic;
+    public readonly expectedArgTypes = [
+      "MemberId",
+      "Option<Bytes>",
+      "Option<Bytes>",
+      "Option<Bytes>",
+    ];
+
+    constructor(public readonly ctx: SubstrateEvent) {
+      if (ctx.extrinsic === undefined) {
+        throw new Error(`No call data has been provided`);
+      }
+      this.extrinsic = ctx.extrinsic;
+    }
+
+    get args(): UpdateMembership_Args {
+      return new UpdateMembership_Args(this.extrinsic);
+    }
+
+    validateArgs(): boolean {
+      if (this.expectedArgTypes.length !== this.extrinsic.args.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedArgTypes.forEach((type, i) => {
+        if (type !== this.extrinsic.args[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class UpdateMembership_Args {
+    constructor(public readonly extrinsic: SubstrateExtrinsic) {}
+
+    get memberId(): MemberId {
+      return createTypeUnsafe<MemberId & Codec>(typeRegistry, "MemberId", [
+        this.extrinsic.args[0].value,
+      ]);
+    }
+
+    get handle(): Option<Bytes> {
+      return createTypeUnsafe<Option<Bytes> & Codec>(
+        typeRegistry,
+        "Option<Bytes>",
+        [this.extrinsic.args[1].value]
+      );
+    }
+
+    get avatarUri(): Option<Bytes> {
+      return createTypeUnsafe<Option<Bytes> & Codec>(
+        typeRegistry,
+        "Option<Bytes>",
+        [this.extrinsic.args[2].value]
+      );
+    }
+
+    get about(): Option<Bytes> {
+      return createTypeUnsafe<Option<Bytes> & Codec>(
+        typeRegistry,
+        "Option<Bytes>",
+        [this.extrinsic.args[3].value]
+      );
+    }
+  }
 }

+ 1 - 0
query-node/manifest.yml

@@ -74,6 +74,7 @@ typegen:
     - members.changeMemberHandle
     - members.setRootAccount
     - members.setControllerAccount
+    - members.updateMembership
 
     # content directory
     - content.create_curator_group

+ 45 - 3
query-node/mappings/src/common.ts

@@ -107,8 +107,12 @@ export interface ISudoCallArgs<T> extends ExtrinsicArg {
 */
 export function extractExtrinsicArgs<DataParams, EventObject extends IGenericExtrinsicObject<DataParams>>(
   rawEvent: SubstrateEvent,
-  callFactory: new (event: SubstrateEvent) => EventObject
-): DataParams {
+  callFactory: new (event: SubstrateEvent) => EventObject,
+
+  // in ideal world this parameter would not be needed, but there is no way to associate parameters
+  // used in sudo to extrinsic parameters without it
+  argsIndeces: Record<keyof DataParams, number>,
+): EventObject['args'] { // this is equal to DataParams but only this notation works properly
   // escape when extrinsic info is not available
   if (!rawEvent.extrinsic) {
     throw 'Invalid event - no extrinsic set' // this should never happen
@@ -121,6 +125,44 @@ export function extractExtrinsicArgs<DataParams, EventObject extends IGenericExt
 
   // sudo extrinsic call
 
+  const callArgs = extractSudoCallParameters<DataParams>(rawEvent)
+
+  // convert naming convention (underscore_names to camelCase)
+  const clearArgs = Object.keys(callArgs.args).reduce((acc, key) => {
+    const formattedName = key.replace(/_([a-z])/g, tmp => tmp[1].toUpperCase())
+
+    acc[formattedName] = callArgs.args[key]
+
+    return acc
+  }, {} as DataParams)
+
+  // prepare partial event object
+  const partialEvent = {
+    extrinsic: {
+      args: Object.keys(argsIndeces).reduce((acc, key) => {
+        acc[(argsIndeces)[key]] = {
+          value: clearArgs[key]
+        }
+
+        return acc
+      }, [] as unknown[]),
+    } as unknown as SubstrateExtrinsic
+  } as SubstrateEvent
+
+  // create event object and extract processed args
+  const finalArgs = (new callFactory(partialEvent)).args
+
+  return finalArgs
+}
+
+/*
+  Extracts extrinsic call parameters used inside of sudo call.
+*/
+export function extractSudoCallParameters<DataParams>(rawEvent: SubstrateEvent): ISudoCallArgs<DataParams> {
+  if (!rawEvent.extrinsic) {
+    throw 'Invalid event - no extrinsic set' // this should never happen
+  }
+
   // see Substrate's sudo frame for more info about sudo extrinsics and `call` argument index
   const argIndex = false
     || (rawEvent.extrinsic.method == 'sudoAs' && 1) // who, *call*
@@ -136,7 +178,7 @@ export function extractExtrinsicArgs<DataParams, EventObject extends IGenericExt
   // typecast call arguments
   const callArgs = rawEvent.extrinsic.args[argIndex].value as unknown as ISudoCallArgs<DataParams>
 
-  return callArgs.args
+  return callArgs
 }
 
 /////////////////// Logger /////////////////////////////////////////////////////

+ 81 - 7
query-node/mappings/src/membership.ts

@@ -10,6 +10,7 @@ import {
   inconsistentState,
   logger,
   extractExtrinsicArgs,
+  extractSudoCallParameters,
 } from './common'
 import { Members } from '../../generated/types'
 import { MembershipEntryMethod, Membership } from 'query-node'
@@ -19,7 +20,15 @@ import { EntryMethod } from '@joystream/types/augment'
 export async function members_MemberRegistered(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
   const { accountId, memberId, entryMethod } = new Members.MemberRegisteredEvent(event).data
-  const { avatarUri, about, handle } = extractExtrinsicArgs(event, Members.BuyMembershipCall)
+  const { avatarUri, about, handle } = extractExtrinsicArgs(
+    event,
+    Members.BuyMembershipCall,
+    {
+      handle: 1,
+      avatarUri: 2,
+      about: 3,
+    },
+  )
 
   // create new membership
   const member = new Membership({
@@ -48,7 +57,11 @@ export async function members_MemberRegistered(db: DatabaseManager, event: Subst
 // eslint-disable-next-line @typescript-eslint/naming-convention
 export async function members_MemberUpdatedAboutText(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
-  const { text, memberId } = extractExtrinsicArgs(event, Members.ChangeMemberAboutTextCall)
+  const { text, memberId } = isUpdateMembershipExtrinsic(event)
+    ? unpackUpdateMembershipOptions(
+        extractExtrinsicArgs(event, Members.UpdateMembershipCall, {memberId: 0, about: 3})
+      )
+    : extractExtrinsicArgs(event, Members.ChangeMemberAboutTextCall, {memberId: 0, text: 1})
 
   // load member
   const member = await db.get(Membership, { where: { id: memberId.toString() } as FindConditions<Membership> })
@@ -74,7 +87,11 @@ export async function members_MemberUpdatedAboutText(db: DatabaseManager, event:
 // eslint-disable-next-line @typescript-eslint/naming-convention
 export async function members_MemberUpdatedAvatar(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
-  const { uri, memberId } = extractExtrinsicArgs(event, Members.ChangeMemberAvatarCall)
+  const { uri, memberId } = isUpdateMembershipExtrinsic(event)
+    ? unpackUpdateMembershipOptions(
+        extractExtrinsicArgs(event, Members.UpdateMembershipCall, {memberId: 0, avatarUri: 2})
+      )
+    : extractExtrinsicArgs(event, Members.ChangeMemberAvatarCall, {memberId: 0, uri: 1})
 
   // load member
   const member = await db.get(Membership, { where: { id: memberId.toString() } as FindConditions<Membership> })
@@ -100,7 +117,11 @@ export async function members_MemberUpdatedAvatar(db: DatabaseManager, event: Su
 // eslint-disable-next-line @typescript-eslint/naming-convention
 export async function members_MemberUpdatedHandle(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
-  const { handle, memberId } = extractExtrinsicArgs(event, Members.ChangeMemberHandleCall)
+  const { handle, memberId } = isUpdateMembershipExtrinsic(event)
+    ? unpackUpdateMembershipOptions(
+        extractExtrinsicArgs(event, Members.UpdateMembershipCall, {memberId: 0, handle: 1})
+      )
+    : extractExtrinsicArgs(event, Members.ChangeMemberHandleCall, {memberId: 0, handle: 1})
 
   // load member
   const member = await db.get(Membership, { where: { id: memberId.toString() } as FindConditions<Membership> })
@@ -126,7 +147,7 @@ export async function members_MemberUpdatedHandle(db: DatabaseManager, event: Su
 // eslint-disable-next-line @typescript-eslint/naming-convention
 export async function members_MemberSetRootAccount(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
-  const { newRootAccount, memberId } = extractExtrinsicArgs(event, Members.SetRootAccountCall)
+  const { newRootAccount, memberId } = extractExtrinsicArgs(event, Members.SetRootAccountCall, {memberId: 0, newRootAccount: 1})
 
   // load member
   const member = await db.get(Membership, { where: { id: memberId.toString() } as FindConditions<Membership> })
@@ -152,7 +173,11 @@ export async function members_MemberSetRootAccount(db: DatabaseManager, event: S
 // eslint-disable-next-line @typescript-eslint/naming-convention
 export async function members_MemberSetControllerAccount(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
-  const { newControllerAccount, memberId } = extractExtrinsicArgs(event, Members.SetControllerAccountCall)
+  const { newControllerAccount, memberId } = extractExtrinsicArgs(
+    event,
+    Members.SetControllerAccountCall,
+    {memberId: 0, newControllerAccount: 1},
+  )
 
   // load member
   const member = await db.get(Membership, { where: { id: memberId.toString() } as FindConditions<Membership> })
@@ -185,7 +210,14 @@ function convertBytesToString(b: Bytes | null): string {
     return ''
   }
 
-  return Buffer.from(b.toU8a(true)).toString()
+  const result = Buffer.from(b.toU8a(true)).toString()
+
+  // prevent utf-8 null character
+  if (result.match(/^\0$/)) {
+    return ''
+  }
+
+  return result
 }
 
 function convertEntryMethod(entryMethod: EntryMethod): MembershipEntryMethod {
@@ -208,3 +240,45 @@ function convertEntryMethod(entryMethod: EntryMethod): MembershipEntryMethod {
   logger.error('Not implemented entry method', {entryMethod: entryMethod.toString()})
   throw 'Not implemented entry method'
 }
+
+/*
+  Returns true if event is emitted inside of `update_membership` extrinsic.
+*/
+function isUpdateMembershipExtrinsic(event: SubstrateEvent): boolean {
+  if (!event.extrinsic) { // this should never happen
+    return false
+  }
+
+  if (event.extrinsic.method == 'updateMembership') {
+    return true
+  }
+
+  // no sudo was used to update membership -> this is not updateMembership
+  if (event.extrinsic.section != 'sudo') {
+    return false
+  }
+
+  const sudoCallParameters = extractSudoCallParameters<unknown[]>(event)
+
+  // very trivial check if update_membership extrinsic was used
+  return sudoCallParameters.args.length == 4 // memberId, handle, avatarUri, about
+}
+
+interface IUnpackedUpdateMembershipOptions {
+  memberId: MemberId
+  handle: Bytes
+  uri: Bytes
+  text: Bytes
+}
+
+/*
+  Returns unwrapped data + unite naming of uri/avatarUri and about/text
+*/
+function unpackUpdateMembershipOptions(args: Members.UpdateMembershipCall['args']): IUnpackedUpdateMembershipOptions {
+  return {
+    memberId: args.memberId,
+    handle: args.handle.unwrapOrDefault(),
+    uri: args.avatarUri.unwrapOrDefault(),
+    text: args.about.unwrapOrDefault(),
+  }
+}