Просмотр исходного кода

Merge branch 'giza-protobuf-and-query-node' into distributor-node

Leszek Wiesner 3 лет назад
Родитель
Сommit
cac520fae1
48 измененных файлов с 1713 добавлено и 669 удалено
  1. 1 0
      .dockerignore
  2. 5 4
      .env
  3. 3 0
      Cargo.lock
  4. 10 2
      apps.Dockerfile
  5. 10 18
      docker-compose.yml
  6. 1 1
      metadata-protobuf/package.json
  7. 0 1
      query-node/build.sh
  8. 17 0
      query-node/kill-img.sh
  9. 18 37
      query-node/mappings/content/channel.ts
  10. 153 102
      query-node/mappings/content/utils.ts
  11. 2 2
      query-node/mappings/content/video.ts
  12. 2 1
      query-node/mappings/genesis-data/storageSystem.json
  13. 23 25
      query-node/mappings/storage/index.ts
  14. 71 0
      query-node/mappings/storage/utils.ts
  15. 4 4
      query-node/package.json
  16. 10 4
      query-node/schemas/content.graphql
  17. 3 0
      query-node/schemas/storage.graphql
  18. 23 0
      query-node/start-img.sh
  19. 8 2
      query-node/start.sh
  20. 1 11
      runtime-modules/common/src/lib.rs
  21. 7 13
      runtime-modules/common/src/storage.rs
  22. 10 2
      runtime-modules/content/Cargo.toml
  23. 14 1
      runtime-modules/content/src/errors.rs
  24. 325 217
      runtime-modules/content/src/lib.rs
  25. 3 4
      runtime-modules/content/src/permissions/mod.rs
  26. 307 43
      runtime-modules/content/src/tests/channels.rs
  27. 364 30
      runtime-modules/content/src/tests/mock.rs
  28. 148 17
      runtime-modules/content/src/tests/videos.rs
  29. 25 7
      runtime-modules/storage/src/lib.rs
  30. 1 0
      runtime-modules/storage/src/tests/mocks.rs
  31. 10 6
      runtime-modules/storage/src/tests/mod.rs
  32. 1 34
      runtime/src/lib.rs
  33. 1 1
      types/augment-codec/all.ts
  34. 16 0
      types/augment-codec/augment-api-errors.ts
  35. 7 6
      types/augment-codec/augment-api-events.ts
  36. 1 1
      types/augment-codec/augment-api-query.ts
  37. 3 2
      types/augment-codec/augment-api-tx.ts
  38. 0 0
      types/augment-codec/augment-types.ts
  39. 24 20
      types/augment/all/defs.json
  40. 25 20
      types/augment/all/types.ts
  41. 16 0
      types/augment/augment-api-errors.ts
  42. 7 6
      types/augment/augment-api-events.ts
  43. 1 1
      types/augment/augment-api-query.ts
  44. 3 2
      types/augment/augment-api-tx.ts
  45. 0 0
      types/augment/augment-types.ts
  46. 3 0
      types/src/common.ts
  47. 25 22
      types/src/content/index.ts
  48. 1 0
      types/src/scripts/generateRegistryJson.ts

+ 1 - 0
.dockerignore

@@ -7,3 +7,4 @@ query-node/lib
 cli/
 tests/
 devops/
+metadata-protobuf/lib

+ 5 - 4
.env

@@ -2,14 +2,14 @@ COMPOSE_PROJECT_NAME=joystream
 PROJECT_NAME=query_node
 
 # We will use a single postgres service with multiple databases
-# The env variables below are by default used by all services and should be 
+# The env variables below are by default used by all services and should be
 # overriden in local env files
 # DB config
 INDEXER_DB_NAME=query_node_indexer
 DB_NAME=query_node_processor
 DB_USER=postgres
 DB_PASS=postgres
-DB_HOST=localhost
+DB_HOST=db
 DB_PORT=5432
 DEBUG=index-builder:*
 TYPEORM_LOGGING=error
@@ -30,9 +30,10 @@ BLOCK_HEIGHT=0
 ###############################
 
 GRAPHQL_SERVER_PORT=4002
-GRAPHQL_SERVER_HOST=localhost
+GRAPHQL_SERVER_HOST=graphql-server
+
 WARTHOG_APP_PORT=4002
-WARTHOG_APP_HOST=localhost
+WARTHOG_APP_HOST=hydra-indexer-gateway
 
 # Default configuration is to use the docker container
 WS_PROVIDER_ENDPOINT_URI=ws://joystream-node:9944/

+ 3 - 0
Cargo.lock

@@ -3828,6 +3828,9 @@ dependencies = [
  "frame-system",
  "pallet-balances",
  "pallet-common",
+ "pallet-membership",
+ "pallet-randomness-collective-flip",
+ "pallet-storage",
  "pallet-timestamp",
  "parity-scale-codec",
  "serde",

+ 10 - 2
apps.Dockerfile

@@ -1,8 +1,17 @@
+FROM mikefarah/yq as manifest-maker
+# Change metadata.source in manifest file. It's not possible to override it via flag/env.
+USER root
+ARG WS_PROVIDER_ENDPOINT_URI
+COPY ./query-node/manifest.yml /joystream/qn-manifest.yml
+RUN yq e -i ".typegen.metadata.source = \"$WS_PROVIDER_ENDPOINT_URI\"" /joystream/qn-manifest.yml
+
 FROM --platform=linux/x86-64 node:14 as builder
 
 WORKDIR /joystream
 COPY . /joystream
-RUN  rm -fr /joystream/pioneer
+COPY --from=manifest-maker /joystream/qn-manifest.yml /joystream/query-node/manifest.yml
+
+RUN rm -fr /joystream/pioneer
 
 # Do not set NODE_ENV=production until after running yarn install
 # to ensure dev dependencies are installed.
@@ -11,7 +20,6 @@ RUN yarn --forzen-lockfile
 RUN yarn workspace @joystream/types build
 RUN yarn workspace @joystream/metadata-protobuf build
 RUN yarn workspace query-node-root build
-RUN yarn workspace storage-node build
 
 # Second stage to reduce image size, enable it when
 # all packages have correctly identified what is a devDependency and what is not.

+ 10 - 18
docker-compose.yml

@@ -45,8 +45,6 @@ services:
     env_file:
       # relative to working directory where docker-compose was run from
       - .env
-    environment:
-      - WS_PROVIDER_ENDPOINT_URI=${WS_PROVIDER_ENDPOINT_URI}
     ports:
       - '127.0.0.1:3001:3001'
     command: colossus --dev --ws-provider ${WS_PROVIDER_ENDPOINT_URI} --ipfs-host ipfs
@@ -104,12 +102,12 @@ services:
     build:
       context: .
       dockerfile: apps.Dockerfile
+      network: joystream_default
+      args:
+        - WS_PROVIDER_ENDPOINT_URI=${WS_PROVIDER_ENDPOINT_URI}
     env_file:
       # relative to working directory where docker-compose was run from
       - .env
-    environment:
-      - DB_HOST=db
-      - DB_NAME=${DB_NAME}
     ports:
       - "127.0.0.1:8081:${GRAPHQL_SERVER_PORT}"
     depends_on:
@@ -122,9 +120,6 @@ services:
     env_file:
       # relative to working directory where docker-compose was run from
       - .env
-    environment:
-      - DB_HOST=db
-      - DB_NAME=${DB_NAME}
     ports:
       - "127.0.0.1:8081:${GRAPHQL_SERVER_PORT}"
     depends_on:
@@ -142,15 +137,17 @@ services:
     build:
       context: .
       dockerfile: apps.Dockerfile
+      network: joystream_default
+      args:
+        - WS_PROVIDER_ENDPOINT_URI=${WS_PROVIDER_ENDPOINT_URI}
     env_file:
       # relative to working directory where docker-compose was run from
       - .env
     environment:
       - INDEXER_ENDPOINT_URL=http://hydra-indexer-gateway:${WARTHOG_APP_PORT}/graphql
-      - TYPEORM_HOST=db
+      - TYPEORM_HOST=${DB_HOST}
       - TYPEORM_DATABASE=${DB_NAME}
-      - DEBUG=index-builder:*
-      - WS_PROVIDER_ENDPOINT_URI=ws://joystream-node:9944
+      - WS_PROVIDER_ENDPOINT_URI=${WS_PROVIDER_ENDPOINT_URI}
     volumes:
       - ./types/augment/all/defs.json:/joystream/query-node/mappings/lib/generated/types/typedefs.json
     depends_on:
@@ -165,10 +162,8 @@ services:
       - .env
     environment:
       - INDEXER_ENDPOINT_URL=http://hydra-indexer-gateway:${WARTHOG_APP_PORT}/graphql
-      - TYPEORM_HOST=db
+      - TYPEORM_HOST=${DB_HOST}
       - TYPEORM_DATABASE=${DB_NAME}
-      - DEBUG=index-builder:*
-      - WS_PROVIDER_ENDPOINT_URI=ws://joystream-node:9944
     depends_on:
       - hydra-indexer-gateway
     volumes:
@@ -185,12 +180,9 @@ services:
       # relative to working directory where docker-compose was run from
       - .env
     environment:
-      - DB_HOST=db
       - DB_NAME=${INDEXER_DB_NAME}
       - INDEXER_WORKERS=5
       - REDIS_URI=redis://redis:6379/0
-      - DEBUG=index-builder:*
-      - WS_PROVIDER_ENDPOINT_URI=${WS_PROVIDER_ENDPOINT_URI}
       - TYPES_JSON=types.json
     depends_on:
       - db
@@ -217,7 +209,7 @@ services:
       - PORT=${WARTHOG_APP_PORT}
       - DEBUG=*
     ports:
-      - "127.0.0.1:4000:4002"
+      - "127.0.0.1:4000:${WARTHOG_APP_PORT}"
     depends_on:
       - redis
       - db

+ 1 - 1
metadata-protobuf/package.json

@@ -20,7 +20,7 @@
   "license": "MIT",
   "private": false,
   "scripts": {
-    "build": "yarn compile && tsc",
+    "build": "yarn compile && rm -rf lib && tsc",
     "compile": "yarn ts-node ./scripts/compile.ts",
     "generate-doc": "./generate-md-doc.sh",
     "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha --inline-diffs -r ts-node/register 'test/**/*.ts'",

+ 0 - 1
query-node/build.sh

@@ -9,7 +9,6 @@ set -a
 . ../.env
 set +a
 
-# only use this when new Hydra releases and contents of `generated/` folder needs to be refreshed
 yarn clean
 yarn codegen:noinstall
 yarn typegen # if this fails try to run this command outside of yarn workspaces

+ 17 - 0
query-node/kill-img.sh

@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+set -e
+
+SCRIPT_PATH="$(dirname "${BASH_SOURCE[0]}")"
+cd $SCRIPT_PATH
+
+set -a
+. ../.env
+set +a
+
+# Only remove query-node related services
+docker-compose rm -vsf processor
+docker-compose rm -vsf graphql-server
+docker-compose rm -vsf indexer
+docker-compose rm -vsf hydra-indexer-gateway
+docker-compose rm -vsf redis
+docker-compose rm -vsf db

+ 18 - 37
query-node/mappings/content/channel.ts

@@ -2,19 +2,21 @@
 eslint-disable @typescript-eslint/naming-convention
 */
 import { EventContext, StoreContext } from '@joystream/hydra-common'
-import { AccountId } from '@polkadot/types/interfaces'
-import { Option } from '@polkadot/types/codec'
 import { Content } from '../generated/types'
 import { convertContentActorToChannelOwner, processChannelMetadata } from './utils'
-import { AssetNone, Channel, ChannelCategory } from 'query-node/dist/model'
+import { AssetNone, Channel, ChannelCategory, StorageDataObject } from 'query-node/dist/model'
 import { deserializeMetadata, inconsistentState, logger } from '../common'
 import { ChannelCategoryMetadata, ChannelMetadata } from '@joystream/metadata-protobuf'
 import { integrateMeta } from '@joystream/metadata-protobuf/utils'
+import { In } from 'typeorm'
+import { removeDataObject } from '../storage/utils'
 
 export async function content_ChannelCreated(ctx: EventContext & StoreContext): Promise<void> {
   const { store, event } = ctx
   // read event data
-  const [contentActor, channelId, , channelCreationParameters] = new Content.ChannelCreatedEvent(event).params
+  const [contentActor, channelId, runtimeChannel, channelCreationParameters] = new Content.ChannelCreatedEvent(
+    event
+  ).params
 
   // create entity
   const channel = new Channel({
@@ -23,6 +25,8 @@ export async function content_ChannelCreated(ctx: EventContext & StoreContext):
     isCensored: false,
     videos: [],
     createdInBlock: event.blockNumber,
+    rewardAccount: channelCreationParameters.reward_account.unwrapOr(undefined)?.toString(),
+    deletionPrizeDestAccount: runtimeChannel.deletion_prize_source_account_id.toString(),
     // assets
     coverPhoto: new AssetNone(),
     avatarPhoto: new AssetNone(),
@@ -63,7 +67,7 @@ export async function content_ChannelUpdated(ctx: EventContext & StoreContext):
   //  update metadata if it was changed
   if (newMetadataBytes) {
     const newMetadata = deserializeMetadata(ChannelMetadata, newMetadataBytes) || {}
-    await processChannelMetadata(ctx, channel, newMetadata, channelUpdateParameters.assets.unwrapOr([]))
+    await processChannelMetadata(ctx, channel, newMetadata, channelUpdateParameters.assets.unwrapOr(undefined))
   }
 
   // prepare changed reward account
@@ -72,7 +76,7 @@ export async function content_ChannelUpdated(ctx: EventContext & StoreContext):
   // reward account change happened?
   if (newRewardAccount) {
     // this will change the `channel`!
-    handleChannelRewardAccountChange(channel, newRewardAccount)
+    channel.rewardAccount = newRewardAccount.unwrapOr(undefined)?.toString()
   }
 
   // set last update time
@@ -86,18 +90,14 @@ export async function content_ChannelUpdated(ctx: EventContext & StoreContext):
 }
 
 export async function content_ChannelAssetsRemoved({ store, event }: EventContext & StoreContext): Promise<void> {
-  // TODO: Storage v2 integration
-  // // read event data
-  // const [, , contentIds] = new Content.ChannelAssetsRemovedEvent(event).params
-  // const assets = await store.getMany(StorageDataObject, {
-  //   where: {
-  //     id: In(contentIds.toArray().map((item) => item.toString())),
-  //   },
-  // })
-  // // delete assets
-  // await Promise.all(assets.map((a) => store.remove<StorageDataObject>(a)))
-  // // emit log event
-  // logger.info('Channel assets have been removed', { ids: contentIds })
+  const [, , dataObjectIds] = new Content.ChannelAssetsRemovedEvent(event).params
+  const assets = await store.getMany(StorageDataObject, {
+    where: {
+      id: In(Array.from(dataObjectIds).map((item) => item.toString())),
+    },
+  })
+  await Promise.all(assets.map((a) => removeDataObject(store, a)))
+  logger.info('Channel assets have been removed', { ids: dataObjectIds.toJSON() })
 }
 
 export async function content_ChannelCensorshipStatusUpdated({
@@ -209,22 +209,3 @@ export async function content_ChannelCategoryDeleted({ store, event }: EventCont
   // emit log event
   logger.info('Channel category has been deleted', { id: channelCategory.id })
 }
-
-/// //////////////// Helpers ////////////////////////////////////////////////////
-
-function handleChannelRewardAccountChange(
-  channel: Channel, // will be modified inside of the function!
-  reward_account: Option<AccountId>
-) {
-  const rewardAccount = reward_account.unwrapOr(null)
-
-  // new different reward account set?
-  if (rewardAccount) {
-    channel.rewardAccount = rewardAccount.toString()
-    return
-  }
-
-  // reward account removed
-
-  channel.rewardAccount = undefined // plan deletion (will have effect when saved to db)
-}

+ 153 - 102
query-node/mappings/content/utils.ts

@@ -1,5 +1,5 @@
 import { DatabaseManager, EventContext, StoreContext } from '@joystream/hydra-common'
-import { FindConditions } from 'typeorm'
+import { FindConditions, Raw } from 'typeorm'
 import {
   IVideoMetadata,
   IPublishedBeforeJoystream,
@@ -8,7 +8,7 @@ import {
   IChannelMetadata,
 } from '@joystream/metadata-protobuf'
 import { integrateMeta, isSet, isValidLanguageCode } from '@joystream/metadata-protobuf/utils'
-import { invalidMetadata, inconsistentState, logger } from '../common'
+import { invalidMetadata, inconsistentState, unexpectedData, logger } from '../common'
 import {
   // primary entities
   CuratorGroup,
@@ -25,23 +25,25 @@ import {
   VideoMediaEncoding,
   ChannelCategory,
   AssetNone,
+  AssetExternal,
+  AssetJoystreamStorage,
+  StorageDataObject,
 } from 'query-node/dist/model'
 // Joystream types
-import { NewAsset, ContentActor } from '@joystream/types/augment'
+import { NewAssets, ContentActor } from '@joystream/types/augment'
 import { DecodedMetadataObject } from '@joystream/metadata-protobuf/types'
 import BN from 'bn.js'
+import { getMostRecentlyCreatedDataObjects } from '../storage/utils'
+import { DataObjectCreationParameters as ObjectCreationParams } from '@joystream/types/storage'
+import { registry } from '@joystream/types'
 
 export async function processChannelMetadata(
   ctx: EventContext & StoreContext,
   channel: Channel,
   meta: DecodedMetadataObject<IChannelMetadata>,
-  assets: NewAsset[]
+  assets?: NewAssets
 ): Promise<Channel> {
-  // TODO: Assets processing (Storage v2)
-  // const assetsOwner = new DataObjectOwnerChannel()
-  // assetsOwner.channelId = channel.id
-
-  // const processedAssets = await Promise.all(assets.map((asset) => processNewAsset(ctx, asset, assetsOwner)))
+  const processedAssets = assets ? await processNewAssets(ctx, assets) : []
 
   integrateMeta(channel, meta, ['title', 'description', 'isPublic'])
 
@@ -50,23 +52,21 @@ export async function processChannelMetadata(
     channel.category = await processChannelCategory(ctx, channel.category, parseInt(meta.category))
   }
 
-  channel.coverPhoto = new AssetNone()
-  channel.avatarPhoto = new AssetNone()
-  // // prepare cover photo asset if needed
-  // if (isSet(meta.coverPhoto)) {
-  //   const asset = findAssetByIndex(processedAssets, meta.coverPhoto, 'channel cover photo')
-  //   if (asset) {
-  //     channel.coverPhoto = asset
-  //   }
-  // }
-
-  // // prepare avatar photo asset if needed
-  // if (isSet(meta.avatarPhoto)) {
-  //   const asset = findAssetByIndex(processedAssets, meta.avatarPhoto, 'channel avatar photo')
-  //   if (asset) {
-  //     channel.avatarPhoto = asset
-  //   }
-  // }
+  // prepare cover photo asset if needed
+  if (isSet(meta.coverPhoto)) {
+    const asset = findAssetByIndex(processedAssets, meta.coverPhoto, 'channel cover photo')
+    if (asset) {
+      channel.coverPhoto = asset
+    }
+  }
+
+  // prepare avatar photo asset if needed
+  if (isSet(meta.avatarPhoto)) {
+    const asset = findAssetByIndex(processedAssets, meta.avatarPhoto, 'channel avatar photo')
+    if (asset) {
+      channel.avatarPhoto = asset
+    }
+  }
 
   // prepare language if needed
   if (isSet(meta.language)) {
@@ -78,16 +78,11 @@ export async function processChannelMetadata(
 
 export async function processVideoMetadata(
   ctx: EventContext & StoreContext,
-  channel: Channel,
   video: Video,
   meta: DecodedMetadataObject<IVideoMetadata>,
-  assets: NewAsset[]
+  assets?: NewAssets
 ): Promise<Video> {
-  // TODO: Assets processing (Storage v2)
-  // const assetsOwner = new DataObjectOwnerChannel()
-  // assetsOwner.channelId = channel.id
-
-  // const processedAssets = await Promise.all(assets.map((asset) => processNewAsset(ctx, asset, assetsOwner)))
+  const processedAssets = assets ? await processNewAssets(ctx, assets) : []
 
   integrateMeta(video, meta, ['title', 'description', 'duration', 'hasMarketing', 'isExplicit', 'isPublic'])
 
@@ -99,7 +94,7 @@ export async function processVideoMetadata(
   // prepare media meta information if needed
   if (isSet(meta.mediaType) || isSet(meta.mediaPixelWidth) || isSet(meta.mediaPixelHeight)) {
     // prepare video file size if poosible
-    const videoSize = 0 // TODO: extractVideoSize(assets, meta.video)
+    const videoSize = extractVideoSize(assets, meta.video)
     video.mediaMetadata = await processVideoMediaMetadata(ctx, video.mediaMetadata, meta, videoSize)
   }
 
@@ -108,23 +103,21 @@ export async function processVideoMetadata(
     await updateVideoLicense(ctx, video, meta.license)
   }
 
-  video.thumbnailPhoto = new AssetNone()
-  video.media = new AssetNone()
-  // // prepare thumbnail photo asset if needed
-  // if (isSet(meta.thumbnailPhoto)) {
-  //   const asset = findAssetByIndex(processedAssets, meta.thumbnailPhoto, 'thumbnail photo')
-  //   if (asset) {
-  //     video.thumbnailPhoto = asset
-  //   }
-  // }
-
-  // // prepare video asset if needed
-  // if (isSet(meta.video)) {
-  //   const asset = findAssetByIndex(processedAssets, meta.video, 'video')
-  //   if (asset) {
-  //     video.media = asset
-  //   }
-  // }
+  // prepare thumbnail photo asset if needed
+  if (isSet(meta.thumbnailPhoto)) {
+    const asset = findAssetByIndex(processedAssets, meta.thumbnailPhoto, 'thumbnail photo')
+    if (asset) {
+      video.thumbnailPhoto = asset
+    }
+  }
+
+  // prepare video asset if needed
+  if (isSet(meta.video)) {
+    const asset = findAssetByIndex(processedAssets, meta.video, 'video')
+    if (asset) {
+      video.media = asset
+    }
+  }
 
   // prepare language if needed
   if (isSet(meta.language)) {
@@ -279,57 +272,64 @@ function processPublishedBeforeJoystream(
   return new Date(timestamp)
 }
 
-// TODO: Assets processing (Storage v2)
-// async function processNewAsset(
-//   ctx: EventContext & StoreContext,
-//   asset: NewAsset,
-//   owner: typeof DataObjectOwner
-// ): Promise<typeof Asset> {
-//   if (asset.isUrls) {
-//     const urls = asset.asUrls.toArray().map((url) => url.toString())
-//     const resultAsset = new AssetExternal()
-//     resultAsset.urls = JSON.stringify(urls)
-//     return resultAsset
-//   } else if (asset.isUpload) {
-//     const contentParameters: ContentParameters = asset.asUpload
-//     const dataObject = await createDataObject(ctx, contentParameters, owner)
-
-//     const resultAsset = new AssetJoystreamStorage()
-//     resultAsset.dataObjectId = dataObject.id
-//     return resultAsset
-//   } else {
-//     unexpectedData('Unrecognized asset type', asset.type)
-//   }
-// }
-
-// function extractVideoSize(assets: NewAsset[], assetIndex: number | null | undefined): number | undefined {
-//   // escape if no asset is required
-//   if (!isSet(assetIndex)) {
-//     return undefined
-//   }
-
-//   // ensure asset index is valid
-//   if (assetIndex > assets.length) {
-//     invalidMetadata(`Non-existing asset video size extraction requested`, { assetsProvided: assets.length, assetIndex })
-//     return undefined
-//   }
-
-//   const rawAsset = assets[assetIndex]
-
-//   // escape if asset is describing URLs (can't get size)
-//   if (rawAsset.isUrls) {
-//     return undefined
-//   }
-
-//   // !rawAsset.isUrls && rawAsset.isUpload // asset is in storage
-
-//   // convert generic content parameters coming from processor to custom Joystream data type
-//   const customContentParameters = new Custom_ContentParameters(registry, rawAsset.asUpload.toJSON() as any)
-//   // extract video size
-//   const videoSize = customContentParameters.size_in_bytes.toNumber()
-
-//   return videoSize
-// }
+async function processNewAssets(ctx: EventContext & StoreContext, assets: NewAssets): Promise<Array<typeof Asset>> {
+  if (assets.isUrls) {
+    return assets.asUrls.map((assetUrls) => {
+      const resultAsset = new AssetExternal()
+      resultAsset.urls = JSON.stringify(assetUrls.map((u) => u.toString()))
+      return resultAsset
+    })
+  } else if (assets.isUpload) {
+    const assetsUploaded = assets.asUpload.object_creation_list.length
+    // FIXME: Ideally the runtime would provide object ids in ChannelCreated/VideoCreated/ChannelUpdated(...) events
+    const objects = await getMostRecentlyCreatedDataObjects(ctx.store, assetsUploaded)
+    return objects.map((o) => {
+      const resultAsset = new AssetJoystreamStorage()
+      resultAsset.dataObjectId = o.id
+      return resultAsset
+    })
+  } else {
+    unexpectedData('Unrecognized assets type', assets.type)
+  }
+}
+
+function extractVideoSize(assets: NewAssets | undefined, assetIndex: number | null | undefined): number | undefined {
+  // escape if no assetIndex is set
+  if (!isSet(assetIndex)) {
+    return undefined
+  }
+
+  // index provided, but there are no assets
+  if (!assets) {
+    invalidMetadata(`Non-existing asset video size extraction requested - no assets were uploaded!`, {
+      assetIndex,
+    })
+    return undefined
+  }
+
+  // cannot extract size from other asset types than "Upload"
+  if (!assets.isUpload) {
+    return undefined
+  }
+
+  const dataObjectsParams = assets.asUpload.object_creation_list
+
+  // ensure asset index is valid
+  if (assetIndex >= dataObjectsParams.length) {
+    invalidMetadata(`Non-existing asset video size extraction requested`, {
+      assetsProvided: dataObjectsParams.length,
+      assetIndex,
+    })
+    return undefined
+  }
+
+  // extract video size from objectParams
+  const objectParams = assets.asUpload.object_creation_list[assetIndex]
+  const params = new ObjectCreationParams(registry, objectParams.toJSON() as any)
+  const videoSize = params.getField('size').toNumber()
+
+  return videoSize
+}
 
 async function processLanguage(
   ctx: EventContext & StoreContext,
@@ -464,3 +464,54 @@ async function processChannelCategory(
 
   return category
 }
+
+// Needs to be done every time before data object is removed!
+export async function unsetAssetRelations(store: DatabaseManager, dataObject: StorageDataObject): Promise<void> {
+  const channelAssets = ['avatarPhoto', 'coverPhoto'] as const
+  const videoAssets = ['thumbnailPhoto', 'media'] as const
+
+  // NOTE: we don't need to retrieve multiple channels/videos via `store.getMany()` because dataObject
+  // is allowed to be associated only with one channel/video in runtime
+  const channel = await store.get(Channel, {
+    where: channelAssets.map((assetName) => ({
+      [assetName]: Raw((alias) => `${alias} ->> 'dataObjectId' = :id`, {
+        id: dataObject.id,
+      }),
+    })),
+  })
+  const video = await store.get(Video, {
+    where: videoAssets.map((assetName) => ({
+      [assetName]: Raw((alias) => `${alias} ->> 'dataObjectId' = :id`, {
+        id: dataObject.id,
+      }),
+    })),
+  })
+
+  if (channel) {
+    channelAssets.forEach((assetName) => {
+      if (channel[assetName] && (channel[assetName] as AssetJoystreamStorage).dataObjectId === dataObject.id) {
+        channel[assetName] = new AssetNone()
+      }
+    })
+    await store.save<Channel>(channel)
+
+    // emit log event
+    logger.info('Content has been disconnected from Channel', {
+      channelId: channel.id.toString(),
+      dataObjectId: dataObject.id,
+    })
+  } else if (video) {
+    videoAssets.forEach((assetName) => {
+      if (video[assetName] && (video[assetName] as AssetJoystreamStorage).dataObjectId === dataObject.id) {
+        video[assetName] = new AssetNone()
+      }
+    })
+    await store.save<Video>(video)
+
+    // emit log event
+    logger.info('Content has been disconnected from Video', {
+      videoId: video.id.toString(),
+      dataObjectId: dataObject.id,
+    })
+  }
+}

+ 2 - 2
query-node/mappings/content/video.ts

@@ -114,7 +114,7 @@ export async function content_VideoCreated(ctx: EventContext & StoreContext): Pr
   })
   // deserialize & process metadata
   const metadata = deserializeMetadata(VideoMetadata, videoCreationParameters.meta) || {}
-  await processVideoMetadata(ctx, channel, video, metadata, videoCreationParameters.assets)
+  await processVideoMetadata(ctx, video, metadata, videoCreationParameters.assets)
 
   // save video
   await store.save<Video>(video)
@@ -145,7 +145,7 @@ export async function content_VideoUpdated(ctx: EventContext & StoreContext): Pr
   // update metadata if it was changed
   if (newMetadataBytes) {
     const newMetadata = deserializeMetadata(VideoMetadata, newMetadataBytes) || {}
-    await processVideoMetadata(ctx, video.channel, video, newMetadata, videoUpdateParameters.assets.unwrapOr([]))
+    await processVideoMetadata(ctx, video, newMetadata, videoUpdateParameters.assets.unwrapOr(undefined))
   }
 
   // set last update time

+ 2 - 1
query-node/mappings/genesis-data/storageSystem.json

@@ -6,5 +6,6 @@
   "uploadingBlocked": false,
   "dataObjectFeePerMb": 0,
   "storageBucketMaxObjectsCountLimit": 0,
-  "storageBucketMaxObjectsSizeLimit": 0
+  "storageBucketMaxObjectsSizeLimit": 0,
+  "nextDataObjectId": 0
 }

+ 23 - 25
query-node/mappings/storage/index.ts

@@ -27,10 +27,8 @@ import {
   StorageBagStorageAssignment,
 } from 'query-node/dist/model'
 import BN from 'bn.js'
-import { getById, bytesToString } from '../common'
+import { getById } from '../common'
 import { BTreeSet } from '@polkadot/types'
-import { DataObjectCreationParameters } from '@joystream/types/storage'
-import { registry } from '@joystream/types'
 import { In } from 'typeorm'
 import _ from 'lodash'
 import { DataObjectId, BagId, DynamicBagId, StaticBagId } from '@joystream/types/augment/all'
@@ -39,6 +37,7 @@ import {
   processDistributionOperatorMetadata,
   processStorageOperatorMetadata,
 } from './metadata'
+import { createDataObjects, getStorageSystem, removeDataObject } from './utils'
 
 async function getDataObjectsInBag(
   store: DatabaseManager,
@@ -180,15 +179,6 @@ async function getDistributionBucketFamilyWithMetadata(
   return family
 }
 
-async function getStorageSystem(store: DatabaseManager) {
-  const storageSystem = await store.get(StorageSystemParameters, {})
-  if (!storageSystem) {
-    throw new Error('Storage system entity is missing!')
-  }
-
-  return storageSystem
-}
-
 // STORAGE BUCKETS
 
 export async function storage_StorageBucketCreated({ event, store }: EventContext & StoreContext): Promise<void> {
@@ -344,12 +334,30 @@ export async function storage_StorageBucketDeleted({ event, store }: EventContex
 
 // DYNAMIC BAGS
 export async function storage_DynamicBagCreated({ event, store }: EventContext & StoreContext): Promise<void> {
-  const [bagId] = new Storage.DynamicBagCreatedEvent(event).params
+  const [bagId, , storageBucketIdsSet, distributionBucketIdsSet] = new Storage.DynamicBagCreatedEvent(event).params
   const storageBag = new StorageBag({
     id: getDynamicBagId(bagId),
     owner: getDynamicBagOwner(bagId),
   })
+  const storageAssignments = Array.from(storageBucketIdsSet).map(
+    (bucketId) =>
+      new StorageBagStorageAssignment({
+        id: `${storageBag.id}-${bucketId.toString()}`,
+        storageBag,
+        storageBucket: new StorageBucket({ id: bucketId.toString() }),
+      })
+  )
+  const distributionAssignments = Array.from(distributionBucketIdsSet).map(
+    (bucketId) =>
+      new StorageBagDistributionAssignment({
+        id: `${storageBag.id}-${bucketId.toString()}`,
+        storageBag,
+        distributionBucket: new DistributionBucket({ id: bucketId.toString() }),
+      })
+  )
   await store.save<StorageBag>(storageBag)
+  await Promise.all(storageAssignments.map((a) => store.save<StorageBagStorageAssignment>(a)))
+  await Promise.all(distributionAssignments.map((a) => store.save<StorageBagDistributionAssignment>(a)))
 }
 
 export async function storage_DynamicBagDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
@@ -366,17 +374,7 @@ export async function storage_DataObjectsUploaded({ event, store }: EventContext
   const [dataObjectIds, uploadParams] = new Storage.DataObjectsUploadedEvent(event).params
   const { bagId, objectCreationList } = uploadParams
   const storageBag = await getBag(store, bagId)
-  const dataObjects = dataObjectIds.map((objectId, i) => {
-    const objectParams = new DataObjectCreationParameters(registry, objectCreationList[i].toJSON() as any)
-    return new StorageDataObject({
-      id: objectId.toString(),
-      isAccepted: false,
-      ipfsHash: bytesToString(objectParams.ipfsContentId),
-      size: new BN(objectParams.getField('size').toString()),
-      storageBag,
-    })
-  })
-  await Promise.all(dataObjects.map((o) => store.save<StorageDataObject>(o)))
+  await createDataObjects(store, objectCreationList, storageBag, dataObjectIds)
 }
 
 export async function storage_PendingDataObjectsAccepted({ event, store }: EventContext & StoreContext): Promise<void> {
@@ -405,7 +403,7 @@ export async function storage_DataObjectsMoved({ event, store }: EventContext &
 export async function storage_DataObjectsDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
   const [, bagId, dataObjectIds] = new Storage.DataObjectsDeletedEvent(event).params
   const dataObjects = await getDataObjectsInBag(store, bagId, dataObjectIds)
-  await Promise.all(dataObjects.map((o) => store.remove<StorageDataObject>(o)))
+  await Promise.all(dataObjects.map((o) => removeDataObject(store, o)))
 }
 
 // DISTRIBUTION FAMILY

+ 71 - 0
query-node/mappings/storage/utils.ts

@@ -0,0 +1,71 @@
+import { DatabaseManager } from '@joystream/hydra-common'
+import { DataObjectCreationParameters } from '@joystream/types/augment'
+import { registry } from '@joystream/types'
+import { DataObjectCreationParameters as ObjectCreationParams } from '@joystream/types/storage'
+import { StorageBag, StorageDataObject, StorageSystemParameters } from 'query-node/dist/model'
+import BN from 'bn.js'
+import { bytesToString, inconsistentState } from '../common'
+import { In } from 'typeorm'
+import { unsetAssetRelations } from '../content/utils'
+
+export async function getStorageSystem(store: DatabaseManager): Promise<StorageSystemParameters> {
+  const storageSystem = await store.get(StorageSystemParameters, {})
+  if (!storageSystem) {
+    throw new Error('Storage system entity is missing!')
+  }
+
+  return storageSystem
+}
+
+export async function createDataObjects(
+  store: DatabaseManager,
+  objectsParams: DataObjectCreationParameters[],
+  storageBag: StorageBag,
+  objectIds?: BN[]
+): Promise<StorageDataObject[]> {
+  const storageSystem = await getStorageSystem(store)
+
+  const dataObjects = objectsParams.map((objectParams, i) => {
+    const params = new ObjectCreationParams(registry, objectParams.toJSON() as any)
+    const objectId = objectIds ? objectIds[i] : storageSystem.nextDataObjectId
+    const object = new StorageDataObject({
+      id: objectId.toString(),
+      isAccepted: false,
+      ipfsHash: bytesToString(objectParams.ipfsContentId),
+      size: new BN(params.getField('size').toString()),
+      storageBag,
+    })
+    if (objectId.gte(storageSystem.nextDataObjectId)) {
+      storageSystem.nextDataObjectId = objectId.addn(1)
+    }
+    return object
+  })
+
+  await Promise.all(dataObjects.map((o) => store.save<StorageDataObject>(o)))
+  await store.save<StorageSystemParameters>(storageSystem)
+
+  return dataObjects
+}
+
+export async function getMostRecentlyCreatedDataObjects(
+  store: DatabaseManager,
+  numberOfObjects: number
+): Promise<StorageDataObject[]> {
+  const storageSystem = await getStorageSystem(store)
+  const objectIds = Array.from({ length: numberOfObjects }, (v, k) =>
+    storageSystem.nextDataObjectId.subn(k + 1).toString()
+  )
+  const objects = await store.getMany(StorageDataObject, { where: { id: In(objectIds) } })
+  if (objects.length < numberOfObjects) {
+    inconsistentState(`Could not get ${numberOfObjects} most recently created data objects`, {
+      expected: numberOfObjects,
+      got: objects.length,
+    })
+  }
+  return objects.sort((a, b) => new BN(a.id).cmp(new BN(b.id)))
+}
+
+export async function removeDataObject(store: DatabaseManager, object: StorageDataObject): Promise<void> {
+  await unsetAssetRelations(store, object)
+  await store.save<StorageDataObject>(object)
+}

+ 4 - 4
query-node/package.json

@@ -28,10 +28,10 @@
     "typegen:configure": "NODE_URL=${NODE_URL:-ws://localhost:9000} envsub typegen.template.yml typegen.yml",
     "typegen": "rm -rf ./mappings/generated && hydra-typegen typegen manifest.yml --debug",
     "mappings:build": "yarn workspace query-node-mappings build",
-    "docker:build": "docker build . -f docker/Dockerfile.hydra -t hydra-kit:latest",
-    "docker:db:up": "(cd ../ && docker-compose up -d db)",
-    "docker:db:migrate": "docker run --env-file .env --env DB_HOST=db --env TYPEORM_HOST=db --network container:${PWD##*/}_db_1 hydra-kit:latest yarn db:migrate",
-    "docker:up": "docker-compose up -d"
+    "start:dev": "./start.sh",
+    "start": "./start-img.sh",
+    "kill:dev": "./kill.sh",
+    "kill": "./kill-img.sh"
   },
   "author": "",
   "license": "ISC",

+ 10 - 4
query-node/schemas/content.graphql

@@ -53,17 +53,21 @@ type Channel @entity {
   "Reward account where revenue is sent if set."
   rewardAccount: String
 
+  "Destination account for the prize associated with channel deletion"
+  deletionPrizeDestAccount: String!
+
   "The title of the Channel"
   title: String @fulltext(query: "search")
 
   "The description of a Channel"
   description: String
 
+  # FIXME: Due to https://github.com/Joystream/hydra/issues/434, Asset is currently non-optional (use AssetNone to unset it)
   "Channel's cover (background) photo asset. Recommended ratio: 16:9."
-  coverPhoto: Asset
+  coverPhoto: Asset!
 
   "Channel's avatar photo asset."
-  avatarPhoto: Asset
+  avatarPhoto: Asset!
 
   ##########################
 
@@ -125,8 +129,9 @@ type Video @entity {
   "Video duration in seconds"
   duration: Int
 
+# FIXME: Due to https://github.com/Joystream/hydra/issues/434, Asset is currently non-optional (use AssetNone to unset it)
   "Video thumbnail asset (recommended ratio: 16:9)"
-  thumbnailPhoto: Asset
+  thumbnailPhoto: Asset!
 
   ##########################
 
@@ -151,8 +156,9 @@ type Video @entity {
   "License under the video is published"
   license: License
 
+  # FIXME: Due to https://github.com/Joystream/hydra/issues/434, Asset is currently non-optional (use AssetNone to unset it)
   "Video media asset"
-  media: Asset
+  media: Asset!
 
   ##########################
 

+ 3 - 0
query-node/schemas/storage.graphql

@@ -20,6 +20,9 @@ type StorageSystemParameters @entity {
 
   "Global max. size of objects a storage bucket can store (can also be further limitted the provider)"
   storageBucketMaxObjectsSizeLimit: BigInt!
+
+  "ID of the next data object when created"
+  nextDataObjectId: BigInt!
 }
 
 type StorageBucketOperatorStatusMissing @variant {

+ 23 - 0
query-node/start-img.sh

@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+set -e
+
+SCRIPT_PATH="$(dirname "${BASH_SOURCE[0]}")"
+cd $SCRIPT_PATH
+
+set -a
+. ../.env
+set +a
+
+# Start the joystream-node first to allow fetching Olympia metadata during build (typegen)
+docker-compose up -d joystream-node
+
+# Bring up db
+docker-compose up -d db
+
+# Setup the db
+docker run --rm --env-file ../.env --network joystream_default joystream/apps workspace query-node-root db:prepare
+docker run --rm --env-file ../.env --network joystream_default joystream/apps workspace query-node-root db:migrate
+
+# Start processor and graphql server
+docker-compose up -d processor
+docker-compose up -d graphql-server

+ 8 - 2
query-node/start.sh

@@ -17,6 +17,9 @@ docker-compose up -d joystream-node
 # Bring up db
 docker-compose up -d db
 
+# Override DB_HOST for db setup
+export DB_HOST=localhost
+
 # Make sure we use dev config for db migrations (prevents "Cannot create database..." and some other errors)
 yarn workspace query-node config:dev
 
@@ -24,7 +27,10 @@ yarn workspace query-node config:dev
 yarn workspace query-node-root db:prepare
 yarn workspace query-node-root db:migrate
 
-docker-compose up -d graphql-server-mnt
+# Set DB_HOST back to docker-service one
+export DB_HOST=db
 
-# Starting up processor will bring up all services it depends on
+# Start processor and graphql server
 docker-compose up -d processor-mnt
+docker-compose up -d graphql-server-mnt
+

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

@@ -24,6 +24,7 @@ pub type ActorId<T> = <T as MembershipTypes>::ActorId;
 
 /// HTTP Url string
 pub type Url = Vec<u8>;
+pub type AssetUrls = Vec<Url>;
 
 /// Generic trait for membership dependent pallets.
 pub trait MembershipTypes: frame_system::Trait {
@@ -65,17 +66,6 @@ pub trait StorageOwnership {
         + Ord
         + PartialEq;
 
-    /// DAO id representation.
-    type DAOId: Parameter
-        + Member
-        + BaseArithmetic
-        + Codec
-        + Default
-        + Copy
-        + MaybeSerialize
-        + Ord
-        + PartialEq;
-
     /// Content id representation.
     type ContentId: Parameter + Member + Codec + Default + Copy + MaybeSerialize + Ord + PartialEq;
 

+ 7 - 13
runtime-modules/common/src/storage.rs

@@ -16,42 +16,36 @@ pub struct ContentParameters<ContentId, DataObjectTypeId> {
 // New owner type for storage object struct
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)]
-pub enum StorageObjectOwner<MemberId, ChannelId, DAOId> {
+pub enum StorageObjectOwner<MemberId, ChannelId> {
     Member(MemberId),
-    Channel(ChannelId), // acts through content directory module, where again DAOs can own channels for example
+    Channel(ChannelId), // acts through content directory module,
     #[allow(clippy::upper_case_acronyms)]
-    DAO(DAOId), // acts through upcoming `content_finance` module
-    Council,            // acts through proposal frame_system
+    Council, // acts through proposal frame_system
     WorkingGroup(WorkingGroup), // acts through new extrinsic in working group
 }
 
-impl<MemberId, ChannelId, DAOId> Default for StorageObjectOwner<MemberId, ChannelId, DAOId> {
-    fn default() -> Self {
-        Self::Council
-    }
-}
 // To be implemented by current storage data_directory runtime module.
 // Defined in 'common' package
 pub trait StorageSystem<T: crate::StorageOwnership + crate::MembershipTypes> {
     fn atomically_add_content(
-        owner: StorageObjectOwner<T::MemberId, T::ChannelId, T::DAOId>,
+        owner: StorageObjectOwner<T::MemberId, T::ChannelId>,
         content_parameters: Vec<ContentParameters<T::ContentId, T::DataObjectTypeId>>,
     ) -> DispatchResult;
 
     // Checks if given owner can add provided content to the storage frame_system
     fn can_add_content(
-        owner: StorageObjectOwner<T::MemberId, T::ChannelId, T::DAOId>,
+        owner: StorageObjectOwner<T::MemberId, T::ChannelId>,
         content_parameters: Vec<ContentParameters<T::ContentId, T::DataObjectTypeId>>,
     ) -> DispatchResult;
 
     fn atomically_remove_content(
-        owner: &StorageObjectOwner<T::MemberId, T::ChannelId, T::DAOId>,
+        owner: &StorageObjectOwner<T::MemberId, T::ChannelId>,
         content_ids: &[T::ContentId],
     ) -> DispatchResult;
 
     // Checks if given owner can remove content under given content ids from the storage frame_system
     fn can_remove_content(
-        owner: &StorageObjectOwner<T::MemberId, T::ChannelId, T::DAOId>,
+        owner: &StorageObjectOwner<T::MemberId, T::ChannelId>,
         content_ids: &[T::ContentId],
     ) -> DispatchResult;
 }

+ 10 - 2
runtime-modules/content/Cargo.toml

@@ -13,12 +13,17 @@ sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'ht
 codec = { package = 'parity-scale-codec', version = '1.3.4', default-features = false, features = ['derive'] }
 serde = {version = '1.0.101', features = ['derive'], optional = true}
 common = { package = 'pallet-common', default-features = false, path = '../common'}
+storage = { package = 'pallet-storage', default-features = false, path = '../storage'}
+membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '2cd20966cc09b059817c3ebe12fc130cdd850d62'}
+
 
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '2cd20966cc09b059817c3ebe12fc130cdd850d62'}
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '2cd20966cc09b059817c3ebe12fc130cdd850d62'}
-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '2cd20966cc09b059817c3ebe12fc130cdd850d62'}
 pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '2cd20966cc09b059817c3ebe12fc130cdd850d62'}
+randomness-collective-flip = { package = 'pallet-randomness-collective-flip', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '2cd20966cc09b059817c3ebe12fc130cdd850d62'}
+
 
 [features]
 default = ['std']
@@ -30,5 +35,8 @@ std = [
 	'sp-arithmetic/std',
 	'codec/std',
 	'serde',
-	'common/std'
+	'common/std',
+	'storage/std',
+	'balances/std',
+	'membership/std',
 ]

+ 14 - 1
runtime-modules/content/src/errors.rs

@@ -62,6 +62,19 @@ decl_error! {
         VideoInSeason,
 
         /// Curators can only censor non-curator group owned channels
-        CannotCensoreCuratorGroupOwnedChannels
+        CannotCensoreCuratorGroupOwnedChannels,
+
+        /// No assets to be removed have been specified
+        NoAssetsSpecified,
+
+        /// Channel assets feasibility
+        InvalidAssetsProvided,
+
+        /// Channel Contains Video
+        ChannelContainsVideos,
+
+        /// Channel Contains Assets
+        ChannelContainsAssets,
+
     }
 }

Разница между файлами не показана из-за своего большого размера
+ 325 - 217
runtime-modules/content/src/lib.rs


+ 3 - 4
runtime-modules/content/src/permissions/mod.rs

@@ -5,7 +5,6 @@ pub use curator_group::*;
 pub use crate::errors::*;
 use crate::*;
 pub use codec::{Codec, Decode, Encode};
-pub use common::MembershipTypes;
 use core::fmt::Debug;
 use frame_support::{ensure, Parameter};
 #[cfg(feature = "std")]
@@ -15,7 +14,7 @@ use sp_runtime::traits::{MaybeSerializeDeserialize, Member};
 // use frame_system::ensure_root;
 
 /// Model of authentication manager.
-pub trait ContentActorAuthenticator: frame_system::Trait + MembershipTypes {
+pub trait ContentActorAuthenticator: frame_system::Trait + membership::Trait {
     /// Curator identifier
     type CuratorId: Parameter
         + Member
@@ -132,7 +131,7 @@ pub fn ensure_actor_authorized_to_create_channel<T: Trait>(
 pub fn ensure_actor_authorized_to_update_channel<T: Trait>(
     origin: T::Origin,
     actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
-    owner: &ChannelOwner<T::MemberId, T::CuratorGroupId, T::DAOId>,
+    owner: &ChannelOwner<T::MemberId, T::CuratorGroupId>,
 ) -> DispatchResult {
     // Only owner of a channel can update and delete channel assets.
     // Lead can update and delete curator group owned channel assets.
@@ -199,7 +198,7 @@ pub fn ensure_actor_authorized_to_set_featured_videos<T: Trait>(
 pub fn ensure_actor_authorized_to_censor<T: Trait>(
     origin: T::Origin,
     actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
-    owner: &ChannelOwner<T::MemberId, T::CuratorGroupId, T::DAOId>,
+    owner: &ChannelOwner<T::MemberId, T::CuratorGroupId>,
 ) -> DispatchResult {
     // Only lead and curators can censor channels and videos
     // Only lead can censor curator group owned channels and videos

+ 307 - 43
runtime-modules/content/src/tests/channels.rs

@@ -2,9 +2,273 @@
 
 use super::curators;
 use super::mock::*;
+use crate::sp_api_hidden_includes_decl_storage::hidden_include::traits::Currency;
 use crate::*;
 use frame_support::{assert_err, assert_ok};
 
+#[test]
+fn successful_channel_deletion() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        // create an account with enought balance
+        let _ = balances::Module::<Test>::deposit_creating(
+            &FIRST_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(100u32),
+        );
+
+        // 3 assets
+        let assets = NewAssets::<Test>::Upload(CreationUploadParameters {
+            object_creation_list: vec![
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"first".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"second".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"third".to_vec(),
+                },
+            ],
+            expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+        });
+
+        let channel_id = NextChannelId::<Test>::get();
+
+        // create channel
+        create_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParametersRecord {
+                assets: assets,
+                meta: vec![],
+                reward_account: None,
+            },
+            Ok(()),
+        );
+
+        // attempt to delete channel with non zero assets
+        delete_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            Err(Error::<Test>::ChannelContainsAssets.into()),
+        );
+
+        // delete assets
+        let assets_to_delete = [0u64, 1u64, 2u64]
+            .iter()
+            .map(|&x| x)
+            .collect::<BTreeSet<_>>();
+
+        // delete channel assets
+        delete_channel_assets_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            assets_to_delete,
+            Ok(()),
+        );
+
+        // successful deletion
+        delete_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            Ok(()),
+        );
+    })
+}
+
+#[test]
+fn successful_channel_assets_deletion() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        // create an account with enought balance
+        let _ = balances::Module::<Test>::deposit_creating(
+            &FIRST_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(100u32),
+        );
+
+        // 3 assets
+        let assets = NewAssets::<Test>::Upload(CreationUploadParameters {
+            object_creation_list: vec![
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"first".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"second".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"third".to_vec(),
+                },
+            ],
+            expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+        });
+
+        let channel_id = NextChannelId::<Test>::get();
+        // create channel
+        create_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParametersRecord {
+                assets: assets,
+                meta: vec![],
+                reward_account: None,
+            },
+            Ok(()),
+        );
+
+        // delete assets
+        let assets_to_delete = [0u64, 1u64].iter().map(|&x| x).collect::<BTreeSet<_>>();
+
+        // delete channel assets
+        delete_channel_assets_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            assets_to_delete,
+            Ok(()),
+        );
+    })
+}
+
+#[test]
+fn succesful_channel_update() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        // create an account with enought balance
+        let _ = balances::Module::<Test>::deposit_creating(
+            &FIRST_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(100u32),
+        );
+
+        // 2 + 1 assets to be uploaded
+        let assets = NewAssets::<Test>::Upload(CreationUploadParameters {
+            object_creation_list: vec![
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"first".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"second".to_vec(),
+                },
+            ],
+            expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+        });
+
+        let new_assets = NewAssets::<Test>::Upload(CreationUploadParameters {
+            object_creation_list: vec![
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"first".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"second".to_vec(),
+                },
+            ],
+            expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+        });
+
+        let channel_id = NextChannelId::<Test>::get();
+        // create channel
+        create_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParametersRecord {
+                assets: assets,
+                meta: vec![],
+                reward_account: None,
+            },
+            Ok(()),
+        );
+
+        // update channel
+        update_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            ChannelUpdateParametersRecord {
+                assets: Some(new_assets),
+                new_meta: None,
+                reward_account: None,
+            },
+            Ok(()),
+        );
+
+        // update with 0 assets
+        update_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            ChannelUpdateParametersRecord {
+                assets: None,
+                new_meta: None,
+                reward_account: None,
+            },
+            Ok(()),
+        );
+    })
+}
+
+#[test]
+fn succesful_channel_creation() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        // create an account with enought balance
+        let _ = balances::Module::<Test>::deposit_creating(
+            &FIRST_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(100u32),
+        );
+
+        // 3 assets to be uploaded
+        let assets = NewAssets::<Test>::Upload(CreationUploadParameters {
+            object_creation_list: vec![
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"first".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"second".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"third".to_vec(),
+                },
+            ],
+            expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+        });
+
+        // create channel
+        create_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParametersRecord {
+                assets: assets,
+                meta: vec![],
+                reward_account: None,
+            },
+            Ok(()),
+        );
+    })
+}
+
 #[test]
 fn lead_cannot_create_channel() {
     with_default_mock_builder(|| {
@@ -12,8 +276,8 @@ fn lead_cannot_create_channel() {
             Content::create_channel(
                 Origin::signed(LEAD_ORIGIN),
                 ContentActor::Lead,
-                ChannelCreationParameters {
-                    assets: vec![],
+                ChannelCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![]),
                     meta: vec![],
                     reward_account: None,
                 }
@@ -34,8 +298,8 @@ fn curator_owned_channels() {
             Content::create_channel(
                 Origin::signed(FIRST_CURATOR_ORIGIN),
                 ContentActor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
-                ChannelCreationParameters {
-                    assets: vec![],
+                ChannelCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![]),
                     meta: vec![],
                     reward_account: None,
                 }
@@ -51,8 +315,8 @@ fn curator_owned_channels() {
             Content::create_channel(
                 Origin::signed(SECOND_CURATOR_ORIGIN),
                 ContentActor::Curator(FIRST_CURATOR_GROUP_ID, SECOND_CURATOR_ID),
-                ChannelCreationParameters {
-                    assets: vec![],
+                ChannelCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![]),
                     meta: vec![],
                     reward_account: None,
                 }
@@ -65,8 +329,8 @@ fn curator_owned_channels() {
             Content::create_channel(
                 Origin::signed(SECOND_CURATOR_ORIGIN),
                 ContentActor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
-                ChannelCreationParameters {
-                    assets: vec![],
+                ChannelCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![]),
                     meta: vec![],
                     reward_account: None,
                 }
@@ -80,8 +344,8 @@ fn curator_owned_channels() {
         assert_ok!(Content::create_channel(
             Origin::signed(FIRST_CURATOR_ORIGIN),
             ContentActor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
-            ChannelCreationParameters {
-                assets: vec![],
+            ChannelCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![]),
                 meta: vec![],
                 reward_account: None,
             }
@@ -94,14 +358,14 @@ fn curator_owned_channels() {
                 channel_id,
                 ChannelRecord {
                     owner: ChannelOwner::CuratorGroup(FIRST_CURATOR_GROUP_ID),
-                    videos: vec![],
-                    playlists: vec![],
-                    series: vec![],
                     is_censored: false,
                     reward_account: None,
+                    deletion_prize_source_account_id: FIRST_CURATOR_ORIGIN,
+                    num_assets: 0,
+                    num_videos: 0,
                 },
-                ChannelCreationParameters {
-                    assets: vec![],
+                ChannelCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![]),
                     meta: vec![],
                     reward_account: None,
                 }
@@ -113,7 +377,7 @@ fn curator_owned_channels() {
             Origin::signed(FIRST_CURATOR_ORIGIN),
             ContentActor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
             channel_id,
-            ChannelUpdateParameters {
+            ChannelUpdateParametersRecord {
                 assets: None,
                 new_meta: None,
                 reward_account: None,
@@ -125,7 +389,7 @@ fn curator_owned_channels() {
             Origin::signed(LEAD_ORIGIN),
             ContentActor::Lead,
             channel_id,
-            ChannelUpdateParameters {
+            ChannelUpdateParametersRecord {
                 assets: None,
                 new_meta: None,
                 reward_account: None,
@@ -145,8 +409,8 @@ fn member_owned_channels() {
             Content::create_channel(
                 Origin::signed(UNKNOWN_ORIGIN),
                 ContentActor::Member(MEMBERS_COUNT + 1),
-                ChannelCreationParameters {
-                    assets: vec![],
+                ChannelCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![]),
                     meta: vec![],
                     reward_account: None,
                 }
@@ -160,8 +424,8 @@ fn member_owned_channels() {
         assert_ok!(Content::create_channel(
             Origin::signed(FIRST_MEMBER_ORIGIN),
             ContentActor::Member(FIRST_MEMBER_ID),
-            ChannelCreationParameters {
-                assets: vec![],
+            ChannelCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![]),
                 meta: vec![],
                 reward_account: None,
             }
@@ -174,14 +438,14 @@ fn member_owned_channels() {
                 channel_id_1,
                 ChannelRecord {
                     owner: ChannelOwner::Member(FIRST_MEMBER_ID),
-                    videos: vec![],
-                    playlists: vec![],
-                    series: vec![],
                     is_censored: false,
                     reward_account: None,
+                    deletion_prize_source_account_id: FIRST_MEMBER_ORIGIN,
+                    num_assets: 0,
+                    num_videos: 0,
                 },
-                ChannelCreationParameters {
-                    assets: vec![],
+                ChannelCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![]),
                     meta: vec![],
                     reward_account: None,
                 }
@@ -194,8 +458,8 @@ fn member_owned_channels() {
         assert_ok!(Content::create_channel(
             Origin::signed(SECOND_MEMBER_ORIGIN),
             ContentActor::Member(SECOND_MEMBER_ID),
-            ChannelCreationParameters {
-                assets: vec![],
+            ChannelCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![]),
                 meta: vec![],
                 reward_account: None,
             }
@@ -208,14 +472,14 @@ fn member_owned_channels() {
                 channel_id_2,
                 ChannelRecord {
                     owner: ChannelOwner::Member(SECOND_MEMBER_ID),
-                    videos: vec![],
-                    playlists: vec![],
-                    series: vec![],
                     is_censored: false,
                     reward_account: None,
+                    deletion_prize_source_account_id: SECOND_MEMBER_ORIGIN,
+                    num_assets: 0,
+                    num_videos: 0,
                 },
-                ChannelCreationParameters {
-                    assets: vec![],
+                ChannelCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![]),
                     meta: vec![],
                     reward_account: None,
                 }
@@ -227,7 +491,7 @@ fn member_owned_channels() {
             Origin::signed(FIRST_MEMBER_ORIGIN),
             ContentActor::Member(FIRST_MEMBER_ID),
             channel_id_1,
-            ChannelUpdateParameters {
+            ChannelUpdateParametersRecord {
                 assets: None,
                 new_meta: None,
                 reward_account: None,
@@ -241,13 +505,13 @@ fn member_owned_channels() {
                 channel_id_1,
                 ChannelRecord {
                     owner: ChannelOwner::Member(FIRST_MEMBER_ID),
-                    videos: vec![],
-                    playlists: vec![],
-                    series: vec![],
                     is_censored: false,
                     reward_account: None,
+                    deletion_prize_source_account_id: FIRST_MEMBER_ORIGIN,
+                    num_assets: 0,
+                    num_videos: 0,
                 },
-                ChannelUpdateParameters {
+                ChannelUpdateParametersRecord {
                     assets: None,
                     new_meta: None,
                     reward_account: None,
@@ -261,7 +525,7 @@ fn member_owned_channels() {
                 Origin::signed(FIRST_MEMBER_ORIGIN),
                 ContentActor::Member(FIRST_MEMBER_ID),
                 channel_id_2,
-                ChannelUpdateParameters {
+                ChannelUpdateParametersRecord {
                     assets: None,
                     new_meta: None,
                     reward_account: None,
@@ -282,8 +546,8 @@ fn channel_censoring() {
         assert_ok!(Content::create_channel(
             Origin::signed(FIRST_MEMBER_ORIGIN),
             ContentActor::Member(FIRST_MEMBER_ID),
-            ChannelCreationParameters {
-                assets: vec![],
+            ChannelCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![]),
                 meta: vec![],
                 reward_account: None,
             }
@@ -358,8 +622,8 @@ fn channel_censoring() {
         assert_ok!(Content::create_channel(
             Origin::signed(FIRST_CURATOR_ORIGIN),
             ContentActor::Curator(group_id, FIRST_CURATOR_ID),
-            ChannelCreationParameters {
-                assets: vec![],
+            ChannelCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![]),
                 meta: vec![],
                 reward_account: None,
             }

+ 364 - 30
runtime-modules/content/src/tests/mock.rs

@@ -2,25 +2,24 @@
 
 use crate::*;
 
+use frame_support::dispatch::{DispatchError, DispatchResult};
 use frame_support::traits::{OnFinalize, OnInitialize};
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
 use sp_core::H256;
 use sp_runtime::{
     testing::Header,
     traits::{BlakeTwo256, IdentityLookup},
-    Perbill,
+    ModuleId, Perbill,
 };
 
 use crate::ContentActorAuthenticator;
 use crate::Trait;
 use common::currency::GovernanceCurrency;
-use common::storage::StorageSystem;
 
 pub type CuratorId = <Test as ContentActorAuthenticator>::CuratorId;
 pub type CuratorGroupId = <Test as ContentActorAuthenticator>::CuratorGroupId;
 pub type MemberId = <Test as MembershipTypes>::MemberId;
 pub type ChannelId = <Test as StorageOwnership>::ChannelId;
-// pub type DAOId = <Test as StorageOwnership>::DAOId;
 
 /// Origins
 
@@ -55,11 +54,21 @@ mod content {
     pub use crate::Event;
 }
 
+mod storage_mod {
+    pub use storage::Event;
+}
+
+mod membership_mod {
+    pub use membership::Event;
+}
+
 impl_outer_event! {
     pub enum MetaEvent for Test {
         content<T>,
         frame_system<T>,
         balances<T>,
+        membership_mod<T>,
+        storage_mod<T>,
     }
 }
 
@@ -116,7 +125,6 @@ impl common::MembershipTypes for Test {
 
 impl common::StorageOwnership for Test {
     type ChannelId = u64;
-    type DAOId = u64;
     type ContentId = u64;
     type DataObjectTypeId = u64;
 }
@@ -139,6 +147,19 @@ impl GovernanceCurrency for Test {
     type Currency = balances::Module<Self>;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 5000;
+}
+
+impl membership::Trait for Test {
+    type Event = MetaEvent;
+    type MemberId = u64;
+    type PaidTermId = u64;
+    type SubscriptionId = u64;
+    type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ();
+}
+
 impl ContentActorAuthenticator for Test {
     type CuratorId = u64;
     type CuratorGroupId = u64;
@@ -166,39 +187,144 @@ impl ContentActorAuthenticator for Test {
     }
 }
 
-pub struct MockStorageSystem {}
+parameter_types! {
+    pub const MaxNumberOfDataObjectsPerBag: u64 = 4;
+    pub const MaxDistributionBucketFamilyNumber: u64 = 4;
+    pub const MaxDistributionBucketNumberPerFamily: u64 = 10;
+    pub const DataObjectDeletionPrize: u64 = 10;
+    pub const StorageModuleId: ModuleId = ModuleId(*b"mstorage"); // module storage
+    pub const BlacklistSizeLimit: u64 = 1;
+    pub const MaxNumberOfPendingInvitationsPerDistributionBucket: u64 = 1;
+    pub const StorageBucketsPerBagValueConstraint: storage::StorageBucketsPerBagValueConstraint =
+        storage::StorageBucketsPerBagValueConstraint {min: 3, max_min_diff: 7};
+    pub const InitialStorageBucketsNumberForDynamicBag: u64 = 3;
+    pub const MaxRandomIterationNumber: u64 = 3;
+    pub const DefaultMemberDynamicBagNumberOfStorageBuckets: u64 = 3;
+    pub const DefaultChannelDynamicBagNumberOfStorageBuckets: u64 = 4;
+    pub const DistributionBucketsPerBagValueConstraint: storage::DistributionBucketsPerBagValueConstraint =
+        storage::StorageBucketsPerBagValueConstraint {min: 3, max_min_diff: 7};
+    pub const MaxDataObjectSize: u64 = 400;
+}
 
-// Anyone can upload and delete without restriction
-impl StorageSystem<Test> for MockStorageSystem {
-    fn atomically_add_content(
-        _owner: StorageObjectOwner<Test>,
-        _content_parameters: Vec<ContentParameters<Test>>,
-    ) -> DispatchResult {
-        Ok(())
+pub const STORAGE_WG_LEADER_ACCOUNT_ID: u64 = 100001;
+pub const DEFAULT_STORAGE_PROVIDER_ACCOUNT_ID: u64 = 100002;
+pub const DEFAULT_DISTRIBUTION_PROVIDER_ACCOUNT_ID: u64 = 100003;
+pub const DISTRIBUTION_WG_LEADER_ACCOUNT_ID: u64 = 100004;
+pub const DEFAULT_STORAGE_PROVIDER_ID: u64 = 10;
+pub const ANOTHER_STORAGE_PROVIDER_ID: u64 = 11;
+pub const DEFAULT_DISTRIBUTION_PROVIDER_ID: u64 = 12;
+pub const ANOTHER_DISTRIBUTION_PROVIDER_ID: u64 = 13;
+
+impl storage::Trait for Test {
+    type Event = MetaEvent;
+    type DataObjectId = u64;
+    type StorageBucketId = u64;
+    type DistributionBucketId = u64;
+    type DistributionBucketFamilyId = u64;
+    type DistributionBucketOperatorId = u64;
+    type ChannelId = u64;
+    type MaxNumberOfDataObjectsPerBag = MaxNumberOfDataObjectsPerBag;
+    type DataObjectDeletionPrize = DataObjectDeletionPrize;
+    type BlacklistSizeLimit = BlacklistSizeLimit;
+    type ModuleId = StorageModuleId;
+    type MemberOriginValidator = ();
+    type StorageBucketsPerBagValueConstraint = StorageBucketsPerBagValueConstraint;
+    type DefaultMemberDynamicBagNumberOfStorageBuckets =
+        DefaultMemberDynamicBagNumberOfStorageBuckets;
+    type DefaultChannelDynamicBagNumberOfStorageBuckets =
+        DefaultChannelDynamicBagNumberOfStorageBuckets;
+    type Randomness = CollectiveFlip;
+    type MaxRandomIterationNumber = MaxRandomIterationNumber;
+    type MaxDistributionBucketFamilyNumber = MaxDistributionBucketFamilyNumber;
+    type MaxDistributionBucketNumberPerFamily = MaxDistributionBucketNumberPerFamily;
+    type DistributionBucketsPerBagValueConstraint = DistributionBucketsPerBagValueConstraint;
+    type MaxNumberOfPendingInvitationsPerDistributionBucket =
+        MaxNumberOfPendingInvitationsPerDistributionBucket;
+    type ContentId = u64;
+    type MaxDataObjectSize = MaxDataObjectSize;
+
+    fn ensure_storage_working_group_leader_origin(origin: Self::Origin) -> DispatchResult {
+        let account_id = ensure_signed(origin)?;
+
+        if account_id != STORAGE_WG_LEADER_ACCOUNT_ID {
+            Err(DispatchError::BadOrigin)
+        } else {
+            Ok(())
+        }
+    }
+
+    fn ensure_storage_worker_origin(origin: Self::Origin, _: u64) -> DispatchResult {
+        let account_id = ensure_signed(origin)?;
+
+        if account_id != DEFAULT_STORAGE_PROVIDER_ACCOUNT_ID {
+            Err(DispatchError::BadOrigin)
+        } else {
+            Ok(())
+        }
+    }
+
+    fn ensure_storage_worker_exists(worker_id: &u64) -> DispatchResult {
+        let allowed_storage_providers =
+            vec![DEFAULT_STORAGE_PROVIDER_ID, ANOTHER_STORAGE_PROVIDER_ID];
+
+        if !allowed_storage_providers.contains(worker_id) {
+            Err(DispatchError::Other("Invalid worker"))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn ensure_distribution_working_group_leader_origin(origin: Self::Origin) -> DispatchResult {
+        let account_id = ensure_signed(origin)?;
+
+        if account_id != DISTRIBUTION_WG_LEADER_ACCOUNT_ID {
+            Err(DispatchError::BadOrigin)
+        } else {
+            Ok(())
+        }
     }
 
-    fn can_add_content(
-        _owner: StorageObjectOwner<Test>,
-        _content_parameters: Vec<ContentParameters<Test>>,
-    ) -> DispatchResult {
-        Ok(())
+    fn ensure_distribution_worker_origin(origin: Self::Origin, _: u64) -> DispatchResult {
+        let account_id = ensure_signed(origin)?;
+
+        if account_id != DEFAULT_DISTRIBUTION_PROVIDER_ACCOUNT_ID {
+            Err(DispatchError::BadOrigin)
+        } else {
+            Ok(())
+        }
     }
 
-    fn atomically_remove_content(
-        _owner: &StorageObjectOwner<Test>,
-        _content_ids: &[u64],
-    ) -> DispatchResult {
-        Ok(())
+    fn ensure_distribution_worker_exists(worker_id: &u64) -> DispatchResult {
+        let allowed_providers = vec![
+            DEFAULT_DISTRIBUTION_PROVIDER_ID,
+            ANOTHER_DISTRIBUTION_PROVIDER_ID,
+        ];
+
+        if !allowed_providers.contains(worker_id) {
+            Err(DispatchError::Other("Invalid worker"))
+        } else {
+            Ok(())
+        }
     }
+}
 
-    fn can_remove_content(
-        _owner: &StorageObjectOwner<Test>,
-        _content_ids: &[u64],
-    ) -> DispatchResult {
-        Ok(())
+pub const DEFAULT_MEMBER_ID: u64 = 100;
+pub const DEFAULT_MEMBER_ACCOUNT_ID: u64 = 101;
+
+impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
+    fn ensure_actor_origin(origin: Origin, member_id: u64) -> Result<u64, &'static str> {
+        let signed_account_id = frame_system::ensure_signed(origin)?;
+
+        if signed_account_id == DEFAULT_MEMBER_ACCOUNT_ID && member_id == DEFAULT_MEMBER_ID {
+            Ok(signed_account_id)
+        } else {
+            Err(DispatchError::BadOrigin.into())
+        }
     }
 }
 
+// Anyone can upload and delete without restriction
+
 parameter_types! {
     pub const MaxNumberOfCuratorsPerGroup: u32 = 10;
     pub const ChannelOwnershipPaymentEscrowId: [u8; 8] = *b"12345678";
@@ -234,9 +360,6 @@ impl Trait for Test {
 
     /// The maximum number of curators per group constraint
     type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
-
-    // Type that handles asset uploads to storage frame_system
-    type StorageSystem = MockStorageSystem;
 }
 
 pub type System = frame_system::Module<Test>;
@@ -307,3 +430,214 @@ pub fn run_to_block(n: u64) {
         <System as OnInitialize<u64>>::on_initialize(System::block_number());
     }
 }
+
+pub type CollectiveFlip = randomness_collective_flip::Module<Test>;
+
+pub fn create_channel_mock(
+    sender: u64,
+    actor: ContentActor<CuratorGroupId, CuratorId, MemberId>,
+    params: ChannelCreationParameters<Test>,
+    result: DispatchResult,
+) {
+    let channel_id = Content::next_channel_id();
+
+    assert_eq!(
+        Content::create_channel(Origin::signed(sender), actor.clone(), params.clone()),
+        result.clone(),
+    );
+
+    if result.is_ok() {
+        let num_assets = match params.assets.clone() {
+            NewAssets::<Test>::Urls(v) => v.len() as u64,
+            NewAssets::<Test>::Upload(c) => c.object_creation_list.len() as u64,
+        };
+        let owner = Content::actor_to_channel_owner(&actor).unwrap();
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelCreated(
+                actor.clone(),
+                channel_id,
+                ChannelRecord {
+                    owner: owner,
+                    is_censored: false,
+                    reward_account: params.reward_account,
+                    deletion_prize_source_account_id: sender,
+                    num_assets: num_assets,
+                    num_videos: 0,
+                },
+                params.clone(),
+            ))
+        );
+    }
+}
+
+pub fn update_channel_mock(
+    sender: u64,
+    actor: ContentActor<CuratorGroupId, CuratorId, MemberId>,
+    channel_id: ChannelId,
+    params: ChannelUpdateParameters<Test>,
+    result: DispatchResult,
+) {
+    let channel_pre = ChannelById::<Test>::get(channel_id.clone());
+
+    assert_eq!(
+        Content::update_channel(
+            Origin::signed(sender),
+            actor.clone(),
+            channel_id.clone(),
+            params.clone()
+        ),
+        result.clone(),
+    );
+
+    if result.is_ok() {
+        let maybe_num_assets = params.assets.clone().map_or(None, |assets| match assets {
+            NewAssets::<Test>::Urls(v) => Some(v.len() as u64),
+            NewAssets::<Test>::Upload(c) => Some(c.object_creation_list.len() as u64),
+        });
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelUpdated(
+                actor.clone(),
+                channel_id,
+                ChannelRecord {
+                    owner: channel_pre.owner.clone(),
+                    is_censored: channel_pre.is_censored,
+                    reward_account: channel_pre.reward_account.clone(),
+                    deletion_prize_source_account_id: sender,
+                    num_assets: channel_pre.num_assets + maybe_num_assets.unwrap_or(0),
+                    num_videos: channel_pre.num_videos,
+                },
+                params.clone(),
+            ))
+        );
+    }
+}
+
+pub fn delete_channel_assets_mock(
+    sender: u64,
+    actor: ContentActor<CuratorGroupId, CuratorId, MemberId>,
+    channel_id: ChannelId,
+    assets: BTreeSet<<Test as storage::Trait>::DataObjectId>,
+    result: DispatchResult,
+) {
+    let channel_pre = ChannelById::<Test>::get(channel_id.clone());
+
+    assert_eq!(
+        Content::remove_channel_assets(
+            Origin::signed(sender),
+            actor.clone(),
+            channel_id.clone(),
+            assets.clone(),
+        ),
+        result.clone(),
+    );
+
+    if result.is_ok() {
+        let num_assets_removed = assets.len();
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelAssetsRemoved(
+                actor.clone(),
+                channel_id,
+                assets.clone(),
+                ChannelRecord {
+                    owner: channel_pre.owner.clone(),
+                    is_censored: channel_pre.is_censored,
+                    reward_account: channel_pre.reward_account.clone(),
+                    deletion_prize_source_account_id: sender,
+                    num_assets: channel_pre.num_assets - (num_assets_removed as u64),
+                    num_videos: channel_pre.num_videos,
+                },
+            ))
+        );
+    }
+}
+
+pub fn delete_channel_mock(
+    sender: u64,
+    actor: ContentActor<CuratorGroupId, CuratorId, MemberId>,
+    channel_id: ChannelId,
+    result: DispatchResult,
+) {
+    assert_eq!(
+        Content::delete_channel(Origin::signed(sender), actor.clone(), channel_id.clone()),
+        result.clone(),
+    );
+
+    if result.is_ok() {
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelDeleted(actor.clone(), channel_id))
+        )
+    }
+}
+
+pub fn create_video_mock(
+    sender: u64,
+    actor: ContentActor<CuratorGroupId, CuratorId, MemberId>,
+    channel_id: ChannelId,
+    params: VideoCreationParameters<Test>,
+    result: DispatchResult,
+) {
+    let video_id = Content::next_video_id();
+    let num_videos_pre = Content::channel_by_id(channel_id).num_videos;
+
+    assert_eq!(
+        Content::create_video(
+            Origin::signed(sender),
+            actor.clone(),
+            channel_id.clone(),
+            params.clone()
+        ),
+        result.clone(),
+    );
+
+    if result.is_ok() {
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::VideoCreated(
+                actor.clone(),
+                channel_id,
+                video_id,
+                params.clone(),
+            ))
+        );
+        assert_eq!(
+            num_videos_pre + 1,
+            Content::channel_by_id(channel_id).num_videos,
+        );
+    }
+}
+pub fn update_video_mock(
+    sender: u64,
+    actor: ContentActor<CuratorGroupId, CuratorId, MemberId>,
+    video_id: <Test as Trait>::VideoId,
+    params: VideoUpdateParameters<Test>,
+    result: DispatchResult,
+) {
+    // let channel_id = Content::video_by_id(video_id.clone()).in_channel;
+    // let num_videos_pre = Content::channel_by_id(channel_id).num_videos;
+
+    assert_eq!(
+        Content::update_video(
+            Origin::signed(sender),
+            actor.clone(),
+            video_id.clone(),
+            params.clone()
+        ),
+        result.clone(),
+    );
+
+    if result.is_ok() {
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::VideoUpdated(
+                actor.clone(),
+                video_id,
+                params.clone(),
+            ))
+        );
+    }
+}

+ 148 - 17
runtime-modules/content/src/tests/videos.rs

@@ -2,6 +2,7 @@
 
 use super::curators;
 use super::mock::*;
+use crate::sp_api_hidden_includes_decl_storage::hidden_include::traits::Currency;
 use crate::*;
 use frame_support::{assert_err, assert_ok};
 
@@ -12,8 +13,8 @@ fn create_member_channel() -> ChannelId {
     assert_ok!(Content::create_channel(
         Origin::signed(FIRST_MEMBER_ORIGIN),
         ContentActor::Member(FIRST_MEMBER_ID),
-        ChannelCreationParameters {
-            assets: vec![],
+        ChannelCreationParametersRecord {
+            assets: NewAssets::<Test>::Urls(vec![]),
             meta: vec![],
             reward_account: None,
         }
@@ -22,6 +23,136 @@ fn create_member_channel() -> ChannelId {
     channel_id
 }
 
+#[test]
+fn video_creation_successful() {
+    with_default_mock_builder(|| {
+        run_to_block(1);
+
+        // depositi initial balance
+        let _ = balances::Module::<Test>::deposit_creating(
+            &FIRST_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(100u32),
+        );
+
+        let channel_id = NextChannelId::<Test>::get();
+
+        create_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![]),
+                meta: vec![],
+                reward_account: None,
+            },
+            Ok(()),
+        );
+
+        let params = VideoCreationParametersRecord {
+            assets: NewAssets::<Test>::Upload(CreationUploadParameters {
+                object_creation_list: vec![
+                    DataObjectCreationParameters {
+                        size: 3,
+                        ipfs_content_id: b"first".to_vec(),
+                    },
+                    DataObjectCreationParameters {
+                        size: 3,
+                        ipfs_content_id: b"second".to_vec(),
+                    },
+                    DataObjectCreationParameters {
+                        size: 3,
+                        ipfs_content_id: b"third".to_vec(),
+                    },
+                ],
+                expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+            }),
+            meta: b"test".to_vec(),
+        };
+
+        create_video_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            params,
+            Ok(()),
+        )
+    })
+}
+
+#[test]
+fn video_update_successful() {
+    with_default_mock_builder(|| {
+        run_to_block(1);
+
+        let _ = balances::Module::<Test>::deposit_creating(
+            &FIRST_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(100u32),
+        );
+
+        let channel_id = NextChannelId::<Test>::get();
+
+        create_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![]),
+                meta: vec![],
+                reward_account: None,
+            },
+            Ok(()),
+        );
+
+        let params = VideoCreationParametersRecord {
+            assets: NewAssets::<Test>::Upload(CreationUploadParameters {
+                object_creation_list: vec![
+                    DataObjectCreationParameters {
+                        size: 3,
+                        ipfs_content_id: b"first".to_vec(),
+                    },
+                    DataObjectCreationParameters {
+                        size: 3,
+                        ipfs_content_id: b"second".to_vec(),
+                    },
+                    DataObjectCreationParameters {
+                        size: 3,
+                        ipfs_content_id: b"third".to_vec(),
+                    },
+                ],
+                expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+            }),
+            meta: b"test".to_vec(),
+        };
+
+        let video_id = Content::next_video_id();
+
+        create_video_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            params,
+            Ok(()),
+        );
+
+        let update_params = VideoUpdateParametersRecord {
+            assets: Some(NewAssets::<Test>::Upload(CreationUploadParameters {
+                object_creation_list: vec![DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"first".to_vec(),
+                }],
+                expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+            })),
+            new_meta: None,
+        };
+
+        update_video_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            update_params,
+            Ok(()),
+        );
+    })
+}
+
 #[test]
 fn member_can_create_videos() {
     with_default_mock_builder(|| {
@@ -34,8 +165,8 @@ fn member_can_create_videos() {
             Origin::signed(FIRST_MEMBER_ORIGIN),
             ContentActor::Member(FIRST_MEMBER_ID),
             channel_id,
-            VideoCreationParameters {
-                assets: vec![NewAsset::Urls(vec![b"https://somewhere.com/".to_vec()])],
+            VideoCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![vec![b"https://somewhere.com/".to_vec()]]),
                 meta: b"metablob".to_vec(),
             }
         ));
@@ -46,8 +177,8 @@ fn member_can_create_videos() {
                 ContentActor::Member(FIRST_MEMBER_ID),
                 channel_id,
                 video_id,
-                VideoCreationParameters {
-                    assets: vec![NewAsset::Urls(vec![b"https://somewhere.com/".to_vec()])],
+                VideoCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![vec![b"https://somewhere.com/".to_vec()]]),
                     meta: b"metablob".to_vec(),
                 }
             ))
@@ -62,10 +193,10 @@ fn member_can_create_videos() {
             Origin::signed(FIRST_MEMBER_ORIGIN),
             ContentActor::Member(FIRST_MEMBER_ID),
             video_id,
-            VideoUpdateParameters {
-                assets: Some(vec![NewAsset::Urls(vec![
+            VideoUpdateParametersRecord {
+                assets: Some(NewAssets::<Test>::Urls(vec![vec![
                     b"https://somewhere-else.com/".to_vec()
-                ])]),
+                ]])),
                 new_meta: Some(b"newmetablob".to_vec()),
             }
         ));
@@ -75,10 +206,10 @@ fn member_can_create_videos() {
             MetaEvent::content(RawEvent::VideoUpdated(
                 ContentActor::Member(FIRST_MEMBER_ID),
                 video_id,
-                VideoUpdateParameters {
-                    assets: Some(vec![NewAsset::Urls(vec![
+                VideoUpdateParametersRecord {
+                    assets: Some(NewAssets::<Test>::Urls(vec![vec![
                         b"https://somewhere-else.com/".to_vec()
-                    ])]),
+                    ]])),
                     new_meta: Some(b"newmetablob".to_vec()),
                 }
             ))
@@ -90,8 +221,8 @@ fn member_can_create_videos() {
                 Origin::signed(SECOND_MEMBER_ORIGIN),
                 ContentActor::Member(SECOND_MEMBER_ID),
                 channel_id,
-                VideoCreationParameters {
-                    assets: vec![],
+                VideoCreationParametersRecord {
+                    assets: NewAssets::<Test>::Urls(vec![]),
                     meta: vec![],
                 }
             ),
@@ -104,7 +235,7 @@ fn member_can_create_videos() {
                 Origin::signed(SECOND_MEMBER_ORIGIN),
                 ContentActor::Member(SECOND_MEMBER_ID),
                 video_id,
-                VideoUpdateParameters {
+                VideoUpdateParametersRecord {
                     assets: None,
                     new_meta: None,
                 }
@@ -151,8 +282,8 @@ fn curators_can_censor_videos() {
             Origin::signed(FIRST_MEMBER_ORIGIN),
             ContentActor::Member(FIRST_MEMBER_ID),
             channel_id,
-            VideoCreationParameters {
-                assets: vec![NewAsset::Urls(vec![b"https://somewhere.com/".to_vec()])],
+            VideoCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![vec![b"https://somewhere.com/".to_vec()]]),
                 meta: b"metablob".to_vec(),
             }
         ));

+ 25 - 7
runtime-modules/storage/src/lib.rs

@@ -118,6 +118,8 @@
 
 // Internal Substrate warning (decl_event).
 #![allow(clippy::unused_unit)]
+// needed for step iteration over DataObjectId range
+#![feature(step_trait)]
 
 #[cfg(test)]
 mod tests;
@@ -216,6 +218,9 @@ pub trait Trait: frame_system::Trait + balances::Trait + membership::Trait {
     /// Storage event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
+    /// Content id representation.
+    type ContentId: Parameter + Member + Codec + Default + Copy + MaybeSerialize + Ord + PartialEq;
+
     /// Data object ID type.
     type DataObjectId: Parameter
         + Member
@@ -224,7 +229,8 @@ pub trait Trait: frame_system::Trait + balances::Trait + membership::Trait {
         + Default
         + Copy
         + MaybeSerialize
-        + PartialEq;
+        + PartialEq
+        + iter::Step; // needed for iteration
 
     /// Storage bucket ID type.
     type StorageBucketId: Parameter
@@ -462,7 +468,7 @@ pub type BalanceOf<T> = <T as balances::Trait>::Balance;
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
 pub struct DataObject<Balance> {
-    /// Defines whether the data object was accepted by a liaison.
+    /// Defines whether the data object was accepted by a liason.
     pub accepted: bool,
 
     /// A reward for the data object deletion.
@@ -932,7 +938,7 @@ decl_storage! {
         /// "Max objects size for a storage bucket voucher" number limit.
         pub VoucherMaxObjectsSizeLimit get (fn voucher_max_objects_size_limit): u64;
 
-        /// "Max objects number for a storage bucket voucher" number limit.
+        /// "Max objects number for a storage  bucket voucher" number limit.
         pub VoucherMaxObjectsNumberLimit get (fn voucher_max_objects_number_limit): u64;
 
         /// DynamicBagCreationPolicy by bag type storage map.
@@ -1103,7 +1109,14 @@ decl_event! {
         /// Params
         /// - dynamic bag ID
         /// - optional DynamicBagDeletionPrize instance
-        DynamicBagCreated(DynamicBagId, Option<DynamicBagDeletionPrizeRecord<AccountId, Balance>>),
+        /// - assigned storage buckets' IDs
+        /// - assigned distribution buckets' IDs
+        DynamicBagCreated(
+            DynamicBagId,
+            Option<DynamicBagDeletionPrizeRecord<AccountId, Balance>>,
+            BTreeSet<StorageBucketId>,
+            BTreeSet<DistributionBucketId>,
+        ),
 
         /// Emits on changing the voucher for a storage bucket.
         /// Params
@@ -2716,9 +2729,9 @@ impl<T: Trait> DataObjectStorage<T> for Module<T> {
         let distribution_buckets = Self::pick_distribution_buckets_for_dynamic_bag(bag_type);
 
         let bag = Bag::<T> {
-            stored_by: storage_buckets,
+            stored_by: storage_buckets.clone(),
             deletion_prize: deletion_prize.clone().map(|dp| dp.prize),
-            distributed_by: distribution_buckets,
+            distributed_by: distribution_buckets.clone(),
             ..Default::default()
         };
 
@@ -2726,7 +2739,12 @@ impl<T: Trait> DataObjectStorage<T> for Module<T> {
 
         <Bags<T>>::insert(&bag_id, bag);
 
-        Self::deposit_event(RawEvent::DynamicBagCreated(dynamic_bag_id, deletion_prize));
+        Self::deposit_event(RawEvent::DynamicBagCreated(
+            dynamic_bag_id,
+            deletion_prize,
+            storage_buckets,
+            distribution_buckets,
+        ));
 
         Ok(())
     }

+ 1 - 0
runtime-modules/storage/src/tests/mocks.rs

@@ -103,6 +103,7 @@ impl crate::Trait for Test {
     type MaxNumberOfPendingInvitationsPerDistributionBucket =
         MaxNumberOfPendingInvitationsPerDistributionBucket;
     type MaxDataObjectSize = MaxDataObjectSize;
+    type ContentId = u64;
 
     fn ensure_storage_working_group_leader_origin(origin: Self::Origin) -> DispatchResult {
         let account_id = ensure_signed(origin)?;

+ 10 - 6
runtime-modules/storage/src/tests/mod.rs

@@ -3098,19 +3098,16 @@ fn create_dynamic_bag_succeeded() {
             .with_deletion_prize(deletion_prize.clone())
             .call_and_assert(Ok(()));
 
-        EventFixture::assert_last_crate_event(RawEvent::DynamicBagCreated(
-            dynamic_bag_id.clone(),
-            Some(deletion_prize),
-        ));
-
         let bag = Storage::dynamic_bag(&dynamic_bag_id);
+
         // Check that IDs are within possible range.
         assert!(bag
             .stored_by
             .iter()
             .all(|id| { *id < Storage::next_storage_bucket_id() }));
 
-        let creation_policy = Storage::get_dynamic_bag_creation_policy(dynamic_bag_id.into());
+        let creation_policy =
+            Storage::get_dynamic_bag_creation_policy(dynamic_bag_id.clone().into());
         assert_eq!(
             bag.stored_by.len(),
             creation_policy.number_of_storage_buckets as usize
@@ -3127,6 +3124,13 @@ fn create_dynamic_bag_succeeded() {
             Balances::usable_balance(&<StorageTreasury<Test>>::module_account_id()),
             deletion_prize_value
         );
+
+        EventFixture::assert_last_crate_event(RawEvent::DynamicBagCreated(
+            dynamic_bag_id,
+            Some(deletion_prize),
+            BTreeSet::from_iter(bag.stored_by),
+            BTreeSet::from_iter(bag.distributed_by),
+        ));
     });
 }
 

+ 1 - 34
runtime/src/lib.rs

@@ -75,7 +75,6 @@ pub use pallet_staking::StakerStatus;
 pub use proposals_codex::ProposalsConfigParameters;
 pub use working_group;
 
-use common::storage::{ContentParameters, StorageObjectOwner};
 pub use content;
 pub use content::MaxNumber;
 
@@ -443,38 +442,6 @@ impl content::Trait for Runtime {
     type SeriesId = SeriesId;
     type ChannelOwnershipTransferRequestId = ChannelOwnershipTransferRequestId;
     type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
-    type StorageSystem = (); // TODO: Add storage integration
-}
-
-// TODO: Remove after the integration with the Content pallet.
-impl common::storage::StorageSystem<Runtime> for () {
-    fn atomically_add_content(
-        _: StorageObjectOwner<MemberId, ChannelId, DAOId>,
-        _: Vec<ContentParameters<ContentId, DataObjectTypeId>>,
-    ) -> sp_runtime::DispatchResult {
-        todo!()
-    }
-
-    fn can_add_content(
-        _: StorageObjectOwner<MemberId, ChannelId, DAOId>,
-        _: Vec<ContentParameters<ContentId, DataObjectTypeId>>,
-    ) -> sp_runtime::DispatchResult {
-        todo!()
-    }
-
-    fn atomically_remove_content(
-        _: &StorageObjectOwner<MemberId, ChannelId, DAOId>,
-        _: &[ContentId],
-    ) -> sp_runtime::DispatchResult {
-        todo!()
-    }
-
-    fn can_remove_content(
-        _: &StorageObjectOwner<MemberId, ChannelId, DAOId>,
-        _: &[ContentId],
-    ) -> sp_runtime::DispatchResult {
-        todo!()
-    }
 }
 
 impl hiring::Trait for Runtime {
@@ -531,7 +498,6 @@ impl common::MembershipTypes for Runtime {
 
 impl common::StorageOwnership for Runtime {
     type ChannelId = ChannelId;
-    type DAOId = DAOId;
     type ContentId = ContentId;
     type DataObjectTypeId = DataObjectTypeId;
 }
@@ -723,6 +689,7 @@ impl storage::Trait for Runtime {
     type MaxNumberOfPendingInvitationsPerDistributionBucket =
         MaxNumberOfPendingInvitationsPerDistributionBucket;
     type MaxDataObjectSize = MaxDataObjectSize;
+    type ContentId = ContentId;
 
     fn ensure_storage_working_group_leader_origin(origin: Self::Origin) -> DispatchResult {
         StorageWorkingGroup::ensure_origin_is_active_leader(origin)

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
types/augment-codec/all.ts


+ 16 - 0
types/augment-codec/augment-api-errors.ts

@@ -90,6 +90,14 @@ declare module '@polkadot/api/types/errors' {
        * A Channel or Video Category does not exist.
        **/
       CategoryDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Channel Contains Assets
+       **/
+      ChannelContainsAssets: AugmentedError<ApiType>;
+      /**
+       * Channel Contains Video
+       **/
+      ChannelContainsVideos: AugmentedError<ApiType>;
       /**
        * Channel does not exist
        **/
@@ -126,6 +134,10 @@ declare module '@polkadot/api/types/errors' {
        * Feature Not Implemented
        **/
       FeatureNotImplemented: AugmentedError<ApiType>;
+      /**
+       * Channel assets feasibility
+       **/
+      InvalidAssetsProvided: AugmentedError<ApiType>;
       /**
        * Lead authentication failed
        **/
@@ -134,6 +146,10 @@ declare module '@polkadot/api/types/errors' {
        * Member authentication failed
        **/
       MemberAuthFailed: AugmentedError<ApiType>;
+      /**
+       * No assets to be removed have been specified
+       **/
+      NoAssetsSpecified: AugmentedError<ApiType>;
       /**
        * Video does not exist
        **/

+ 7 - 6
types/augment-codec/augment-api-events.ts

@@ -2,7 +2,7 @@
 /* eslint-disable */
 
 import type { BTreeMap, BTreeSet, Bytes, Option, Vec, bool, u32, u64 } from '@polkadot/types';
-import type { ApplicationId, ApplicationIdToWorkerIdMap, BagId, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, Cid, ContentActor, ContentId, CuratorGroupId, CuratorId, DataObjectId, DistributionBucketFamilyId, DistributionBucketId, DynamicBagDeletionPrizeRecord, DynamicBagId, DynamicBagType, EntryMethod, IsCensored, MemberId, MintBalanceOf, MintId, NewAsset, OpeningId, PersonCreationParameters, PersonId, PersonUpdateParameters, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, PostId, ProposalId, ProposalStatus, RationaleText, Series, SeriesId, SeriesParameters, StorageBucketId, ThreadId, UploadParameters, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, Voucher, WorkerId } from './all';
+import type { ApplicationId, ApplicationIdToWorkerIdMap, BagId, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, Cid, ContentActor, CuratorGroupId, CuratorId, DataObjectId, DistributionBucketFamilyId, DistributionBucketId, DynamicBagDeletionPrizeRecord, DynamicBagId, DynamicBagType, EntryMethod, IsCensored, MemberId, MintBalanceOf, MintId, NewAssets, OpeningId, PersonCreationParameters, PersonId, PersonUpdateParameters, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, PostId, ProposalId, ProposalStatus, RationaleText, Series, SeriesId, SeriesParameters, StorageBucketId, ThreadId, UploadParameters, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, Voucher, WorkerId } from './all';
 import type { BalanceStatus } from '@polkadot/types/interfaces/balances';
 import type { AuthorityId } from '@polkadot/types/interfaces/consensus';
 import type { AuthorityList } from '@polkadot/types/interfaces/grandpa';
@@ -53,12 +53,13 @@ declare module '@polkadot/api/types/events' {
       Unreserved: AugmentedEvent<ApiType, [AccountId, Balance]>;
     };
     content: {
-      ChannelAssetsRemoved: AugmentedEvent<ApiType, [ContentActor, ChannelId, Vec<ContentId>]>;
+      ChannelAssetsRemoved: AugmentedEvent<ApiType, [ContentActor, ChannelId, BTreeSet<DataObjectId>, Channel]>;
       ChannelCategoryCreated: AugmentedEvent<ApiType, [ChannelCategoryId, ChannelCategory, ChannelCategoryCreationParameters]>;
       ChannelCategoryDeleted: AugmentedEvent<ApiType, [ContentActor, ChannelCategoryId]>;
       ChannelCategoryUpdated: AugmentedEvent<ApiType, [ContentActor, ChannelCategoryId, ChannelCategoryUpdateParameters]>;
       ChannelCensorshipStatusUpdated: AugmentedEvent<ApiType, [ContentActor, ChannelId, IsCensored, Bytes]>;
       ChannelCreated: AugmentedEvent<ApiType, [ContentActor, ChannelId, Channel, ChannelCreationParameters]>;
+      ChannelDeleted: AugmentedEvent<ApiType, [ContentActor, ChannelId]>;
       ChannelOwnershipTransferred: AugmentedEvent<ApiType, [ContentActor, ChannelOwnershipTransferRequestId]>;
       ChannelOwnershipTransferRequested: AugmentedEvent<ApiType, [ContentActor, ChannelOwnershipTransferRequestId, ChannelOwnershipTransferRequest]>;
       ChannelOwnershipTransferRequestWithdrawn: AugmentedEvent<ApiType, [ContentActor, ChannelOwnershipTransferRequestId]>;
@@ -68,15 +69,15 @@ declare module '@polkadot/api/types/events' {
       CuratorGroupStatusSet: AugmentedEvent<ApiType, [CuratorGroupId, bool]>;
       CuratorRemoved: AugmentedEvent<ApiType, [CuratorGroupId, CuratorId]>;
       FeaturedVideosSet: AugmentedEvent<ApiType, [ContentActor, Vec<VideoId>]>;
-      PersonCreated: AugmentedEvent<ApiType, [ContentActor, PersonId, Vec<NewAsset>, PersonCreationParameters]>;
+      PersonCreated: AugmentedEvent<ApiType, [ContentActor, PersonId, NewAssets, PersonCreationParameters]>;
       PersonDeleted: AugmentedEvent<ApiType, [ContentActor, PersonId]>;
-      PersonUpdated: AugmentedEvent<ApiType, [ContentActor, PersonId, Vec<NewAsset>, PersonUpdateParameters]>;
+      PersonUpdated: AugmentedEvent<ApiType, [ContentActor, PersonId, NewAssets, PersonUpdateParameters]>;
       PlaylistCreated: AugmentedEvent<ApiType, [ContentActor, PlaylistId, PlaylistCreationParameters]>;
       PlaylistDeleted: AugmentedEvent<ApiType, [ContentActor, PlaylistId]>;
       PlaylistUpdated: AugmentedEvent<ApiType, [ContentActor, PlaylistId, PlaylistUpdateParameters]>;
-      SeriesCreated: AugmentedEvent<ApiType, [ContentActor, SeriesId, Vec<NewAsset>, SeriesParameters, Series]>;
+      SeriesCreated: AugmentedEvent<ApiType, [ContentActor, SeriesId, NewAssets, SeriesParameters, Series]>;
       SeriesDeleted: AugmentedEvent<ApiType, [ContentActor, SeriesId]>;
-      SeriesUpdated: AugmentedEvent<ApiType, [ContentActor, SeriesId, Vec<NewAsset>, SeriesParameters, Series]>;
+      SeriesUpdated: AugmentedEvent<ApiType, [ContentActor, SeriesId, NewAssets, SeriesParameters, Series]>;
       VideoCategoryCreated: AugmentedEvent<ApiType, [ContentActor, VideoCategoryId, VideoCategoryCreationParameters]>;
       VideoCategoryDeleted: AugmentedEvent<ApiType, [ContentActor, VideoCategoryId]>;
       VideoCategoryUpdated: AugmentedEvent<ApiType, [ContentActor, VideoCategoryId, VideoCategoryUpdateParameters]>;

+ 1 - 1
types/augment-codec/augment-api-query.ts

@@ -1137,7 +1137,7 @@ declare module '@polkadot/api/types/storage' {
        **/
       uploadingBlocked: AugmentedQuery<ApiType, () => Observable<bool>, []>;
       /**
-       * "Max objects number for a storage bucket voucher" number limit.
+       * "Max objects number for a storage  bucket voucher" number limit.
        **/
       voucherMaxObjectsNumberLimit: AugmentedQuery<ApiType, () => Observable<u64>, []>;
       /**

+ 3 - 2
types/augment-codec/augment-api-tx.ts

@@ -3,7 +3,7 @@
 
 import type { BTreeMap, BTreeSet, Bytes, Compact, Option, Vec, bool, u16, u32, u64 } from '@polkadot/types';
 import type { AnyNumber } from '@polkadot/types/types';
-import type { ActivateOpeningAt, AddOpeningParameters, ApplicationId, ApplicationIdSet, BagId, BalanceOfMint, CategoryId, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, Cid, ContentActor, ContentId, CuratorGroupId, CuratorId, DataObjectId, DistributionBucketFamilyId, DistributionBucketId, DynamicBagDeletionPrize, DynamicBagId, DynamicBagType, ElectionParameters, FillOpeningParameters, MemberId, MemoText, OpeningId, OpeningPolicyCommitment, OpeningType, PaidTermId, PersonActor, PersonCreationParameters, PersonId, PersonUpdateParameters, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, PostId, ProposalId, RewardPolicy, SeriesId, SeriesParameters, StorageBucketId, TerminateRoleParameters, ThreadId, UploadParameters, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, WorkerId, WorkingGroup } from './all';
+import type { ActivateOpeningAt, AddOpeningParameters, ApplicationId, ApplicationIdSet, BagId, BalanceOfMint, CategoryId, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, Cid, ContentActor, CuratorGroupId, CuratorId, DataObjectId, DistributionBucketFamilyId, DistributionBucketId, DynamicBagDeletionPrize, DynamicBagId, DynamicBagType, ElectionParameters, FillOpeningParameters, MemberId, MemoText, OpeningId, OpeningPolicyCommitment, OpeningType, PaidTermId, PersonActor, PersonCreationParameters, PersonId, PersonUpdateParameters, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, PostId, ProposalId, RewardPolicy, SeriesId, SeriesParameters, StorageBucketId, TerminateRoleParameters, ThreadId, UploadParameters, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, WorkerId, WorkingGroup } from './all';
 import type { BabeEquivocationProof } from '@polkadot/types/interfaces/babe';
 import type { Extrinsic, Signature } from '@polkadot/types/interfaces/extrinsics';
 import type { GrandpaEquivocationProof, KeyOwnerProof } from '@polkadot/types/interfaces/grandpa';
@@ -137,6 +137,7 @@ declare module '@polkadot/api/types/submittable' {
       createSeries: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, params: SeriesParameters | { assets?: any; seasons?: any; meta?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, SeriesParameters]>;
       createVideo: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, params: VideoCreationParameters | { assets?: any; meta?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, VideoCreationParameters]>;
       createVideoCategory: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, params: VideoCategoryCreationParameters | { meta?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, VideoCategoryCreationParameters]>;
+      deleteChannel: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId]>;
       deleteChannelCategory: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, categoryId: ChannelCategoryId | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelCategoryId]>;
       deletePerson: AugmentedSubmittable<(actor: PersonActor | { Member: any } | { Curator: any } | string | Uint8Array, person: PersonId | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [PersonActor, PersonId]>;
       deletePlaylist: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, playlist: PlaylistId | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, PlaylistId]>;
@@ -146,7 +147,7 @@ declare module '@polkadot/api/types/submittable' {
       /**
        * Remove assets of a channel from storage
        **/
-      removeChannelAssets: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, assets: Vec<ContentId> | (ContentId | AnyNumber | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, Vec<ContentId>]>;
+      removeChannelAssets: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, assets: BTreeSet<DataObjectId>) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, BTreeSet<DataObjectId>]>;
       /**
        * Remove curator from a given curator group
        **/

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
types/augment-codec/augment-types.ts


+ 24 - 20
types/augment/all/defs.json

@@ -764,25 +764,29 @@
             "Lead": "Null"
         }
     },
-    "NewAsset": {
+    "CreationUploadParameters": {
+        "object_creation_list": "Vec<DataObjectCreationParameters>",
+        "expected_data_size_fee": "u128"
+    },
+    "AssetUrls": "Vec<Url>",
+    "NewAssets": {
         "_enum": {
-            "Upload": "u64",
-            "Urls": "Vec<Url>"
+            "Upload": "CreationUploadParameters",
+            "Urls": "Vec<AssetUrls>"
         }
     },
     "Channel": {
         "owner": "ChannelOwner",
-        "videos": "Vec<VideoId>",
-        "playlists": "Vec<PlaylistId>",
-        "series": "Vec<SeriesId>",
+        "num_videos": "u64",
         "is_censored": "bool",
-        "reward_account": "Option<GenericAccountId>"
+        "reward_account": "Option<GenericAccountId>",
+        "deletion_prize_source_account_id": "GenericAccountId",
+        "num_assets": "u64"
     },
     "ChannelOwner": {
         "_enum": {
             "Member": "MemberId",
-            "Curators": "CuratorGroupId",
-            "Dao": "DAOId"
+            "Curators": "CuratorGroupId"
         }
     },
     "ChannelCategoryId": "u64",
@@ -794,12 +798,12 @@
         "new_meta": "Bytes"
     },
     "ChannelCreationParameters": {
-        "assets": "Vec<NewAsset>",
+        "assets": "NewAssets",
         "meta": "Bytes",
         "reward_account": "Option<GenericAccountId>"
     },
     "ChannelUpdateParameters": {
-        "assets": "Option<Vec<NewAsset>>",
+        "assets": "Option<NewAssets>",
         "new_meta": "Option<Bytes>",
         "reward_account": "Option<Option<GenericAccountId>>"
     },
@@ -813,7 +817,8 @@
     "Video": {
         "in_channel": "ChannelId",
         "in_series": "Option<SeriesId>",
-        "is_censored": "bool"
+        "is_censored": "bool",
+        "maybe_data_objects_id_set": "Option<Vec<DataObjectId>>"
     },
     "VideoId": "u64",
     "VideoCategoryId": "u64",
@@ -825,11 +830,11 @@
         "new_meta": "Bytes"
     },
     "VideoCreationParameters": {
-        "assets": "Vec<NewAsset>",
+        "assets": "NewAssets",
         "meta": "Bytes"
     },
     "VideoUpdateParameters": {
-        "assets": "Option<Vec<NewAsset>>",
+        "assets": "Option<NewAssets>",
         "new_meta": "Option<Bytes>"
     },
     "Person": {
@@ -849,11 +854,11 @@
         }
     },
     "PersonCreationParameters": {
-        "assets": "Vec<NewAsset>",
+        "assets": "NewAssets",
         "meta": "Bytes"
     },
     "PersonUpdateParameters": {
-        "assets": "Option<Vec<NewAsset>>",
+        "assets": "Option<NewAssets>",
         "meta": "Option<Bytes>"
     },
     "Playlist": {
@@ -875,12 +880,12 @@
         "episodes": "Vec<VideoId>"
     },
     "SeriesParameters": {
-        "assets": "Option<Vec<NewAsset>>",
+        "assets": "Option<NewAssets>",
         "seasons": "Option<Vec<Option<SeasonParameters>>>",
         "meta": "Option<Bytes>"
     },
     "SeasonParameters": {
-        "assets": "Option<Vec<NewAsset>>",
+        "assets": "Option<NewAssets>",
         "episodes": "Option<Vec<Option<EpisodeParemters>>>",
         "meta": "Option<Bytes>"
     },
@@ -891,6 +896,5 @@
         }
     },
     "MaxNumber": "u32",
-    "IsCensored": "bool",
-    "ContentId": "u64"
+    "IsCensored": "bool"
 }

+ 25 - 20
types/augment/all/types.ts

@@ -139,6 +139,9 @@ export interface Approved extends Enum {
   readonly asExecutionFailed: ExecutionFailed;
 }
 
+/** @name AssetUrls */
+export interface AssetUrls extends Vec<Url> {}
+
 /** @name Backer */
 export interface Backer extends Struct {
   readonly member: GenericAccountId;
@@ -202,11 +205,11 @@ export interface CategoryId extends u64 {}
 /** @name Channel */
 export interface Channel extends Struct {
   readonly owner: ChannelOwner;
-  readonly videos: Vec<VideoId>;
-  readonly playlists: Vec<PlaylistId>;
-  readonly series: Vec<SeriesId>;
+  readonly num_videos: u64;
   readonly is_censored: bool;
   readonly reward_account: Option<GenericAccountId>;
+  readonly deletion_prize_source_account_id: GenericAccountId;
+  readonly num_assets: u64;
 }
 
 /** @name ChannelCategory */
@@ -230,7 +233,7 @@ export interface ChannelContentType extends Null {}
 
 /** @name ChannelCreationParameters */
 export interface ChannelCreationParameters extends Struct {
-  readonly assets: Vec<NewAsset>;
+  readonly assets: NewAssets;
   readonly meta: Bytes;
   readonly reward_account: Option<GenericAccountId>;
 }
@@ -247,8 +250,6 @@ export interface ChannelOwner extends Enum {
   readonly asMember: MemberId;
   readonly isCurators: boolean;
   readonly asCurators: CuratorGroupId;
-  readonly isDao: boolean;
-  readonly asDao: DAOId;
 }
 
 /** @name ChannelOwnershipTransferRequest */
@@ -267,7 +268,7 @@ export interface ChannelPublicationStatus extends Null {}
 
 /** @name ChannelUpdateParameters */
 export interface ChannelUpdateParameters extends Struct {
-  readonly assets: Option<Vec<NewAsset>>;
+  readonly assets: Option<NewAssets>;
   readonly new_meta: Option<Bytes>;
   readonly reward_account: Option<Option<GenericAccountId>>;
 }
@@ -308,15 +309,18 @@ export interface ContentActor extends Enum {
   readonly isLead: boolean;
 }
 
-/** @name ContentId */
-export interface ContentId extends u64 {}
-
 /** @name ContentIdSet */
 export interface ContentIdSet extends BTreeSet<Cid> {}
 
 /** @name CreateEntityOperation */
 export interface CreateEntityOperation extends Null {}
 
+/** @name CreationUploadParameters */
+export interface CreationUploadParameters extends Struct {
+  readonly object_creation_list: Vec<DataObjectCreationParameters>;
+  readonly expected_data_size_fee: u128;
+}
+
 /** @name Credential */
 export interface Credential extends Null {}
 
@@ -654,12 +658,12 @@ export interface ModerationAction extends Struct {
   readonly rationale: Text;
 }
 
-/** @name NewAsset */
-export interface NewAsset extends Enum {
+/** @name NewAssets */
+export interface NewAssets extends Enum {
   readonly isUpload: boolean;
-  readonly asUpload: u64;
+  readonly asUpload: CreationUploadParameters;
   readonly isUrls: boolean;
-  readonly asUrls: Vec<Url>;
+  readonly asUrls: Vec<AssetUrls>;
 }
 
 /** @name NextAdjustment */
@@ -781,7 +785,7 @@ export interface PersonController extends Enum {
 
 /** @name PersonCreationParameters */
 export interface PersonCreationParameters extends Struct {
-  readonly assets: Vec<NewAsset>;
+  readonly assets: NewAssets;
   readonly meta: Bytes;
 }
 
@@ -790,7 +794,7 @@ export interface PersonId extends u64 {}
 
 /** @name PersonUpdateParameters */
 export interface PersonUpdateParameters extends Struct {
-  readonly assets: Option<Vec<NewAsset>>;
+  readonly assets: Option<NewAssets>;
   readonly meta: Option<Bytes>;
 }
 
@@ -1079,7 +1083,7 @@ export interface Season extends Struct {
 
 /** @name SeasonParameters */
 export interface SeasonParameters extends Struct {
-  readonly assets: Option<Vec<NewAsset>>;
+  readonly assets: Option<NewAssets>;
   readonly episodes: Option<Vec<Option<EpisodeParemters>>>;
   readonly meta: Option<Bytes>;
 }
@@ -1105,7 +1109,7 @@ export interface SeriesId extends u64 {}
 
 /** @name SeriesParameters */
 export interface SeriesParameters extends Struct {
-  readonly assets: Option<Vec<NewAsset>>;
+  readonly assets: Option<NewAssets>;
   readonly seasons: Option<Vec<Option<SeasonParameters>>>;
   readonly meta: Option<Bytes>;
 }
@@ -1329,6 +1333,7 @@ export interface Video extends Struct {
   readonly in_channel: ChannelId;
   readonly in_series: Option<SeriesId>;
   readonly is_censored: bool;
+  readonly maybe_data_objects_id_set: Option<Vec<DataObjectId>>;
 }
 
 /** @name VideoCategory */
@@ -1349,7 +1354,7 @@ export interface VideoCategoryUpdateParameters extends Struct {
 
 /** @name VideoCreationParameters */
 export interface VideoCreationParameters extends Struct {
-  readonly assets: Vec<NewAsset>;
+  readonly assets: NewAssets;
   readonly meta: Bytes;
 }
 
@@ -1358,7 +1363,7 @@ export interface VideoId extends u64 {}
 
 /** @name VideoUpdateParameters */
 export interface VideoUpdateParameters extends Struct {
-  readonly assets: Option<Vec<NewAsset>>;
+  readonly assets: Option<NewAssets>;
   readonly new_meta: Option<Bytes>;
 }
 

+ 16 - 0
types/augment/augment-api-errors.ts

@@ -90,6 +90,14 @@ declare module '@polkadot/api/types/errors' {
        * A Channel or Video Category does not exist.
        **/
       CategoryDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Channel Contains Assets
+       **/
+      ChannelContainsAssets: AugmentedError<ApiType>;
+      /**
+       * Channel Contains Video
+       **/
+      ChannelContainsVideos: AugmentedError<ApiType>;
       /**
        * Channel does not exist
        **/
@@ -126,6 +134,10 @@ declare module '@polkadot/api/types/errors' {
        * Feature Not Implemented
        **/
       FeatureNotImplemented: AugmentedError<ApiType>;
+      /**
+       * Channel assets feasibility
+       **/
+      InvalidAssetsProvided: AugmentedError<ApiType>;
       /**
        * Lead authentication failed
        **/
@@ -134,6 +146,10 @@ declare module '@polkadot/api/types/errors' {
        * Member authentication failed
        **/
       MemberAuthFailed: AugmentedError<ApiType>;
+      /**
+       * No assets to be removed have been specified
+       **/
+      NoAssetsSpecified: AugmentedError<ApiType>;
       /**
        * Video does not exist
        **/

+ 7 - 6
types/augment/augment-api-events.ts

@@ -2,7 +2,7 @@
 /* eslint-disable */
 
 import type { BTreeMap, BTreeSet, Bytes, Option, Vec, bool, u32, u64 } from '@polkadot/types';
-import type { ApplicationId, ApplicationIdToWorkerIdMap, BagId, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, Cid, ContentActor, ContentId, CuratorGroupId, CuratorId, DataObjectId, DistributionBucketFamilyId, DistributionBucketId, DynamicBagDeletionPrizeRecord, DynamicBagId, DynamicBagType, EntryMethod, IsCensored, MemberId, MintBalanceOf, MintId, NewAsset, OpeningId, PersonCreationParameters, PersonId, PersonUpdateParameters, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, PostId, ProposalId, ProposalStatus, RationaleText, Series, SeriesId, SeriesParameters, StorageBucketId, ThreadId, UploadParameters, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, Voucher, WorkerId } from './all';
+import type { ApplicationId, ApplicationIdToWorkerIdMap, BagId, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, Cid, ContentActor, CuratorGroupId, CuratorId, DataObjectId, DistributionBucketFamilyId, DistributionBucketId, DynamicBagDeletionPrizeRecord, DynamicBagId, DynamicBagType, EntryMethod, IsCensored, MemberId, MintBalanceOf, MintId, NewAssets, OpeningId, PersonCreationParameters, PersonId, PersonUpdateParameters, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, PostId, ProposalId, ProposalStatus, RationaleText, Series, SeriesId, SeriesParameters, StorageBucketId, ThreadId, UploadParameters, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, Voucher, WorkerId } from './all';
 import type { BalanceStatus } from '@polkadot/types/interfaces/balances';
 import type { AuthorityId } from '@polkadot/types/interfaces/consensus';
 import type { AuthorityList } from '@polkadot/types/interfaces/grandpa';
@@ -53,12 +53,13 @@ declare module '@polkadot/api/types/events' {
       Unreserved: AugmentedEvent<ApiType, [AccountId, Balance]>;
     };
     content: {
-      ChannelAssetsRemoved: AugmentedEvent<ApiType, [ContentActor, ChannelId, Vec<ContentId>]>;
+      ChannelAssetsRemoved: AugmentedEvent<ApiType, [ContentActor, ChannelId, BTreeSet<DataObjectId>, Channel]>;
       ChannelCategoryCreated: AugmentedEvent<ApiType, [ChannelCategoryId, ChannelCategory, ChannelCategoryCreationParameters]>;
       ChannelCategoryDeleted: AugmentedEvent<ApiType, [ContentActor, ChannelCategoryId]>;
       ChannelCategoryUpdated: AugmentedEvent<ApiType, [ContentActor, ChannelCategoryId, ChannelCategoryUpdateParameters]>;
       ChannelCensorshipStatusUpdated: AugmentedEvent<ApiType, [ContentActor, ChannelId, IsCensored, Bytes]>;
       ChannelCreated: AugmentedEvent<ApiType, [ContentActor, ChannelId, Channel, ChannelCreationParameters]>;
+      ChannelDeleted: AugmentedEvent<ApiType, [ContentActor, ChannelId]>;
       ChannelOwnershipTransferred: AugmentedEvent<ApiType, [ContentActor, ChannelOwnershipTransferRequestId]>;
       ChannelOwnershipTransferRequested: AugmentedEvent<ApiType, [ContentActor, ChannelOwnershipTransferRequestId, ChannelOwnershipTransferRequest]>;
       ChannelOwnershipTransferRequestWithdrawn: AugmentedEvent<ApiType, [ContentActor, ChannelOwnershipTransferRequestId]>;
@@ -68,15 +69,15 @@ declare module '@polkadot/api/types/events' {
       CuratorGroupStatusSet: AugmentedEvent<ApiType, [CuratorGroupId, bool]>;
       CuratorRemoved: AugmentedEvent<ApiType, [CuratorGroupId, CuratorId]>;
       FeaturedVideosSet: AugmentedEvent<ApiType, [ContentActor, Vec<VideoId>]>;
-      PersonCreated: AugmentedEvent<ApiType, [ContentActor, PersonId, Vec<NewAsset>, PersonCreationParameters]>;
+      PersonCreated: AugmentedEvent<ApiType, [ContentActor, PersonId, NewAssets, PersonCreationParameters]>;
       PersonDeleted: AugmentedEvent<ApiType, [ContentActor, PersonId]>;
-      PersonUpdated: AugmentedEvent<ApiType, [ContentActor, PersonId, Vec<NewAsset>, PersonUpdateParameters]>;
+      PersonUpdated: AugmentedEvent<ApiType, [ContentActor, PersonId, NewAssets, PersonUpdateParameters]>;
       PlaylistCreated: AugmentedEvent<ApiType, [ContentActor, PlaylistId, PlaylistCreationParameters]>;
       PlaylistDeleted: AugmentedEvent<ApiType, [ContentActor, PlaylistId]>;
       PlaylistUpdated: AugmentedEvent<ApiType, [ContentActor, PlaylistId, PlaylistUpdateParameters]>;
-      SeriesCreated: AugmentedEvent<ApiType, [ContentActor, SeriesId, Vec<NewAsset>, SeriesParameters, Series]>;
+      SeriesCreated: AugmentedEvent<ApiType, [ContentActor, SeriesId, NewAssets, SeriesParameters, Series]>;
       SeriesDeleted: AugmentedEvent<ApiType, [ContentActor, SeriesId]>;
-      SeriesUpdated: AugmentedEvent<ApiType, [ContentActor, SeriesId, Vec<NewAsset>, SeriesParameters, Series]>;
+      SeriesUpdated: AugmentedEvent<ApiType, [ContentActor, SeriesId, NewAssets, SeriesParameters, Series]>;
       VideoCategoryCreated: AugmentedEvent<ApiType, [ContentActor, VideoCategoryId, VideoCategoryCreationParameters]>;
       VideoCategoryDeleted: AugmentedEvent<ApiType, [ContentActor, VideoCategoryId]>;
       VideoCategoryUpdated: AugmentedEvent<ApiType, [ContentActor, VideoCategoryId, VideoCategoryUpdateParameters]>;

+ 1 - 1
types/augment/augment-api-query.ts

@@ -1137,7 +1137,7 @@ declare module '@polkadot/api/types/storage' {
        **/
       uploadingBlocked: AugmentedQuery<ApiType, () => Observable<bool>, []>;
       /**
-       * "Max objects number for a storage bucket voucher" number limit.
+       * "Max objects number for a storage  bucket voucher" number limit.
        **/
       voucherMaxObjectsNumberLimit: AugmentedQuery<ApiType, () => Observable<u64>, []>;
       /**

+ 3 - 2
types/augment/augment-api-tx.ts

@@ -3,7 +3,7 @@
 
 import type { BTreeMap, BTreeSet, Bytes, Compact, Option, Vec, bool, u16, u32, u64 } from '@polkadot/types';
 import type { AnyNumber } from '@polkadot/types/types';
-import type { ActivateOpeningAt, AddOpeningParameters, ApplicationId, ApplicationIdSet, BagId, BalanceOfMint, CategoryId, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, Cid, ContentActor, ContentId, CuratorGroupId, CuratorId, DataObjectId, DistributionBucketFamilyId, DistributionBucketId, DynamicBagDeletionPrize, DynamicBagId, DynamicBagType, ElectionParameters, FillOpeningParameters, MemberId, MemoText, OpeningId, OpeningPolicyCommitment, OpeningType, PaidTermId, PersonActor, PersonCreationParameters, PersonId, PersonUpdateParameters, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, PostId, ProposalId, RewardPolicy, SeriesId, SeriesParameters, StorageBucketId, TerminateRoleParameters, ThreadId, UploadParameters, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, WorkerId, WorkingGroup } from './all';
+import type { ActivateOpeningAt, AddOpeningParameters, ApplicationId, ApplicationIdSet, BagId, BalanceOfMint, CategoryId, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, Cid, ContentActor, CuratorGroupId, CuratorId, DataObjectId, DistributionBucketFamilyId, DistributionBucketId, DynamicBagDeletionPrize, DynamicBagId, DynamicBagType, ElectionParameters, FillOpeningParameters, MemberId, MemoText, OpeningId, OpeningPolicyCommitment, OpeningType, PaidTermId, PersonActor, PersonCreationParameters, PersonId, PersonUpdateParameters, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, PostId, ProposalId, RewardPolicy, SeriesId, SeriesParameters, StorageBucketId, TerminateRoleParameters, ThreadId, UploadParameters, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, WorkerId, WorkingGroup } from './all';
 import type { BabeEquivocationProof } from '@polkadot/types/interfaces/babe';
 import type { Extrinsic, Signature } from '@polkadot/types/interfaces/extrinsics';
 import type { GrandpaEquivocationProof, KeyOwnerProof } from '@polkadot/types/interfaces/grandpa';
@@ -137,6 +137,7 @@ declare module '@polkadot/api/types/submittable' {
       createSeries: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, params: SeriesParameters | { assets?: any; seasons?: any; meta?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, SeriesParameters]>;
       createVideo: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, params: VideoCreationParameters | { assets?: any; meta?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, VideoCreationParameters]>;
       createVideoCategory: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, params: VideoCategoryCreationParameters | { meta?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, VideoCategoryCreationParameters]>;
+      deleteChannel: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId]>;
       deleteChannelCategory: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, categoryId: ChannelCategoryId | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelCategoryId]>;
       deletePerson: AugmentedSubmittable<(actor: PersonActor | { Member: any } | { Curator: any } | string | Uint8Array, person: PersonId | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [PersonActor, PersonId]>;
       deletePlaylist: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, playlist: PlaylistId | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, PlaylistId]>;
@@ -146,7 +147,7 @@ declare module '@polkadot/api/types/submittable' {
       /**
        * Remove assets of a channel from storage
        **/
-      removeChannelAssets: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, assets: Vec<ContentId> | (ContentId | AnyNumber | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, Vec<ContentId>]>;
+      removeChannelAssets: AugmentedSubmittable<(actor: ContentActor | { Curator: any } | { Member: any } | { Lead: any } | string | Uint8Array, channelId: ChannelId | AnyNumber | Uint8Array, assets: BTreeSet<DataObjectId>) => SubmittableExtrinsic<ApiType>, [ContentActor, ChannelId, BTreeSet<DataObjectId>]>;
       /**
        * Remove curator from a given curator group
        **/

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
types/augment/augment-types.ts


+ 3 - 0
types/src/common.ts

@@ -106,7 +106,10 @@ export class InputValidationLengthConstraint
   }
 }
 
+// Reserved keys are not part of the exported definition const, since they are not intented to be used
 export const WorkingGroupDef = {
+  // _Reserved0
+  // _Reserved1
   Storage: Null,
   Content: Null,
   Operations: Null,

+ 25 - 22
types/src/content/index.ts

@@ -1,8 +1,9 @@
 import { Vec, Option, Tuple } from '@polkadot/types'
 import { bool, u64, u32, u128, Null, Bytes } from '@polkadot/types/primitive'
 import { MemberId } from '../members'
-import { JoyStructDecorated, JoyEnum, ChannelId, JoyBTreeSet, DAOId, Url } from '../common'
+import { JoyStructDecorated, JoyEnum, ChannelId, JoyBTreeSet, Url } from '../common'
 import { GenericAccountId as AccountId } from '@polkadot/types/generic/AccountId'
+import { DataObjectId, DataObjectCreationParameters } from '../storage'
 
 export class CuratorId extends u64 {}
 export class CuratorGroupId extends u64 {}
@@ -16,15 +17,16 @@ export class ChannelOwnershipTransferRequestId extends u64 {}
 export class MaxNumber extends u32 {}
 export class IsCensored extends bool {}
 
-// TODO: Remove after the storage-content integration.
-export class ContentId extends u64 {}
+export class AssetUrls extends Vec.with(Url) {}
 
-// TODO: Remove after the storage-content integration.
-export class ContentParameters extends u64 {}
+export class CreationUploadParameters extends JoyStructDecorated({
+  object_creation_list: Vec.with(DataObjectCreationParameters),
+  expected_data_size_fee: u128,
+}) {}
 
-export class NewAsset extends JoyEnum({
-  Upload: ContentParameters,
-  Urls: Vec.with(Url),
+export class NewAssets extends JoyEnum({
+  Upload: CreationUploadParameters,
+  Urls: Vec.with(AssetUrls),
 }) {}
 
 export class CuratorGroup extends JoyStructDecorated({
@@ -41,26 +43,25 @@ export class ContentActor extends JoyEnum({
 export class ChannelOwner extends JoyEnum({
   Member: MemberId,
   Curators: CuratorGroupId,
-  Dao: DAOId,
 }) {}
 
 export class Channel extends JoyStructDecorated({
   owner: ChannelOwner,
-  videos: Vec.with(VideoId),
-  playlists: Vec.with(PlaylistId),
-  series: Vec.with(SeriesId),
+  num_videos: u64,
   is_censored: bool,
   reward_account: Option.with(AccountId),
+  deletion_prize_source_account_id: AccountId,
+  num_assets: u64,
 }) {}
 
 export class ChannelCreationParameters extends JoyStructDecorated({
-  assets: Vec.with(NewAsset),
+  assets: NewAssets,
   meta: Bytes,
   reward_account: Option.with(AccountId),
 }) {}
 
 export class ChannelUpdateParameters extends JoyStructDecorated({
-  assets: Option.with(Vec.with(NewAsset)),
+  assets: Option.with(NewAssets),
   new_meta: Option.with(Bytes),
   reward_account: Option.with(Option.with(AccountId)),
 }) {}
@@ -100,15 +101,16 @@ export class Video extends JoyStructDecorated({
   in_channel: ChannelId,
   in_series: Option.with(SeriesId),
   is_censored: bool,
+  maybe_data_objects_id_set: Option.with(JoyBTreeSet(DataObjectId)),
 }) {}
 
 export class VideoCreationParameters extends JoyStructDecorated({
-  assets: Vec.with(NewAsset),
+  assets: NewAssets,
   meta: Bytes,
 }) {}
 
 export class VideoUpdateParameters extends JoyStructDecorated({
-  assets: Option.with(Vec.with(NewAsset)),
+  assets: Option.with(NewAssets),
   new_meta: Option.with(Bytes),
 }) {}
 
@@ -134,7 +136,7 @@ export class Season extends JoyStructDecorated({
 }) {}
 
 export class SeasonParameters extends JoyStructDecorated({
-  assets: Option.with(Vec.with(NewAsset)),
+  assets: Option.with(NewAssets),
   episodes: Option.with(Vec.with(Option.with(EpisodeParemters))),
   meta: Option.with(Bytes),
 }) {}
@@ -145,7 +147,7 @@ export class Series extends JoyStructDecorated({
 }) {}
 
 export class SeriesParameters extends JoyStructDecorated({
-  assets: Option.with(Vec.with(NewAsset)),
+  assets: Option.with(NewAssets),
   seasons: Option.with(Vec.with(Option.with(SeasonParameters))),
   meta: Option.with(Bytes),
 }) {}
@@ -160,12 +162,12 @@ export class Person extends JoyStructDecorated({
 }) {}
 
 export class PersonCreationParameters extends JoyStructDecorated({
-  assets: Vec.with(NewAsset),
+  assets: NewAssets,
   meta: Bytes,
 }) {}
 
 export class PersonUpdateParameters extends JoyStructDecorated({
-  assets: Option.with(Vec.with(NewAsset)),
+  assets: Option.with(NewAssets),
   meta: Option.with(Bytes),
 }) {}
 
@@ -179,7 +181,9 @@ export const contentTypes = {
   CuratorGroupId,
   CuratorGroup,
   ContentActor,
-  NewAsset,
+  CreationUploadParameters,
+  AssetUrls,
+  NewAssets,
   Channel,
   ChannelOwner,
   ChannelCategoryId,
@@ -216,7 +220,6 @@ export const contentTypes = {
   EpisodeParemters,
   MaxNumber,
   IsCensored,
-  ContentId, // TODO: Remove after the content integration
 }
 
 export default contentTypes

+ 1 - 0
types/src/scripts/generateRegistryJson.ts

@@ -11,6 +11,7 @@ const OUTPUT_PATH = path.join(__dirname, '../../augment/all/defs.json')
 function normalizeDef(registry: Registry, defOrConstructor: any, typeName: string): RegistryTypes[string] {
   if (typeof defOrConstructor === 'string') {
     // Replace unhandled BTreeSet with Vec
+    // FIXME: Remove after updating @polkadot/api!
     defOrConstructor = defOrConstructor.replace('BTreeSet<', 'Vec<')
     // Workaround for "Unhandled type VecFixed"
     defOrConstructor = defOrConstructor.replace('[u8;32]', 'Hash')

Некоторые файлы не были показаны из-за большого количества измененных файлов