Browse Source

Merge branch 'iznik' into types-augment

Leszek Wiesner 4 years ago
parent
commit
a622e6ac93
100 changed files with 2999 additions and 1995 deletions
  1. 7 12
      .travis.yml
  2. 385 231
      Cargo.lock
  3. 6 0
      README.md
  4. 93 138
      node/Cargo.toml
  5. 3 20
      node/bin/main.rs
  6. 64 19
      node/build.rs
  7. 139 50
      node/src/chain_spec.rs
  8. 45 125
      node/src/cli.rs
  9. 100 0
      node/src/command.rs
  10. 4 0
      node/src/lib.rs
  11. 1 13
      node/src/members_config.rs
  12. 10 0
      node/src/node_executor.rs
  13. 188 0
      node/src/node_rpc.rs
  14. 532 225
      node/src/service.rs
  15. 5 1
      package.json
  16. 0 3
      pioneer/.eslintignore
  17. 8 2
      pioneer/.eslintrc.js
  18. 3 1
      pioneer/packages/apps-config/src/api/spec/index.ts
  19. 3 0
      pioneer/packages/apps-config/src/api/spec/joystream-node.ts
  20. 5 0
      pioneer/packages/apps-config/src/settings/endpoints.ts
  21. 3 1
      pioneer/packages/apps-config/src/ui/logos/index.ts
  22. 1 0
      pioneer/packages/apps-config/src/ui/logos/nodes/joystream-node.svg
  23. 21 41
      pioneer/packages/apps-routing/src/index.ts
  24. 15 0
      pioneer/packages/apps-routing/src/joy-members.ts
  25. 27 0
      pioneer/packages/apps-routing/src/joy-pages.ts
  26. BIN
      pioneer/packages/apps/public/favicon.ico
  27. 11 0
      pioneer/packages/apps/public/index.html
  28. 14 1
      pioneer/packages/apps/src/Apps.tsx
  29. 1 1
      pioneer/packages/apps/src/Content/NotFound.tsx
  30. 2 6
      pioneer/packages/apps/src/Content/index.tsx
  31. 56 0
      pioneer/packages/apps/src/JoyTopBar/TopBar.tsx
  32. 1 1
      pioneer/packages/apps/src/SideBar/ChainInfo.tsx
  33. 3 24
      pioneer/packages/apps/src/SideBar/index.tsx
  34. 20 13
      pioneer/packages/apps/src/index.tsx
  35. 3 11
      pioneer/packages/apps/webpack.base.config.js
  36. 2 1
      pioneer/packages/apps/webpack.config.js
  37. 0 0
      pioneer/packages/joy-members/.skip-build
  38. 3 3
      pioneer/packages/joy-members/package.json
  39. 47 29
      pioneer/packages/joy-members/src/Dashboard.tsx
  40. 27 23
      pioneer/packages/joy-members/src/Details.tsx
  41. 3 1
      pioneer/packages/joy-members/src/DetailsByHandle.tsx
  42. 53 45
      pioneer/packages/joy-members/src/EditForm.tsx
  43. 11 6
      pioneer/packages/joy-members/src/List.tsx
  44. 8 8
      pioneer/packages/joy-members/src/MemberPreview.tsx
  45. 0 58
      pioneer/packages/joy-members/src/index.css
  46. 16 11
      pioneer/packages/joy-members/src/index.tsx
  47. 62 0
      pioneer/packages/joy-members/src/style.ts
  48. 3 2
      pioneer/packages/joy-members/src/utils.ts
  49. 0 0
      pioneer/packages/joy-pages/.skip-build
  50. 3 3
      pioneer/packages/joy-pages/package.json
  51. 4 2
      pioneer/packages/joy-pages/src/index.tsx
  52. 0 14
      pioneer/packages/joy-utils-old/src/functions/misc.ts
  53. 0 1
      pioneer/packages/joy-utils-old/src/react/components/index.tsx
  54. 0 1
      pioneer/packages/joy-utils-old/src/react/context/index.tsx
  55. 0 0
      pioneer/packages/joy-utils/README.md
  56. 3 3
      pioneer/packages/joy-utils/package.json
  57. 0 0
      pioneer/packages/joy-utils/src/functions/date.ts
  58. 2 2
      pioneer/packages/joy-utils/src/functions/format.ts
  59. 167 0
      pioneer/packages/joy-utils/src/functions/misc.ts
  60. 1 1
      pioneer/packages/joy-utils/src/react/components/FlexCenter.tsx
  61. 4 1
      pioneer/packages/joy-utils/src/react/components/MutedText.tsx
  62. 3 0
      pioneer/packages/joy-utils/src/react/components/Section.tsx
  63. 46 50
      pioneer/packages/joy-utils/src/react/components/TxButton.tsx
  64. 5 5
      pioneer/packages/joy-utils/src/react/components/forms.tsx
  65. 4 0
      pioneer/packages/joy-utils/src/react/components/index.tsx
  66. 30 10
      pioneer/packages/joy-utils/src/react/context/account.tsx
  67. 2 0
      pioneer/packages/joy-utils/src/react/context/index.tsx
  68. 2 6
      pioneer/packages/joy-utils/src/react/context/membership.tsx
  69. 0 0
      pioneer/packages/joy-utils/src/react/helpers/index.ts
  70. 124 0
      pioneer/packages/joy-utils/src/react/hocs/accounts.tsx
  71. 104 0
      pioneer/packages/joy-utils/src/react/hocs/guards.tsx
  72. 2 0
      pioneer/packages/joy-utils/src/react/hooks/index.ts
  73. 6 0
      pioneer/packages/joy-utils/src/react/hooks/useMyAccount.tsx
  74. 6 0
      pioneer/packages/joy-utils/src/react/hooks/useMyMembership.tsx
  75. 0 28
      pioneer/packages/old-apps/apps-routing/src/joy-pages.ts
  76. 0 17
      pioneer/packages/old-apps/apps/src/TopBar.css
  77. 0 47
      pioneer/packages/old-apps/apps/src/TopBar.tsx
  78. 0 11
      pioneer/packages/old-apps/react-components/src/styles/old-theme.ts-unused
  79. 1 1
      pioneer/packages/page-staking/src/Targets/Summary.tsx
  80. 8 0
      pioneer/packages/react-components/src/InputAddress/index.tsx
  81. 5 2
      pioneer/packages/react-components/src/Tabs/Tab.tsx
  82. 1 0
      pioneer/packages/react-components/src/Tabs/types.ts
  83. 3 1
      pioneer/packages/react-components/src/styles/index.ts
  84. 16 8
      pioneer/packages/react-components/src/styles/joystream.ts
  85. 1 1
      pioneer/packages/react-components/src/styles/theme.ts
  86. 7 9
      pioneer/tsconfig.json
  87. 15 43
      runtime-modules/common/Cargo.toml
  88. 2 2
      runtime-modules/common/src/currency.rs
  89. 2 2
      runtime-modules/common/src/lib.rs
  90. 37 120
      runtime-modules/content-working-group/Cargo.toml
  91. 2 60
      runtime-modules/content-working-group/src/genesis.rs
  92. 103 88
      runtime-modules/content-working-group/src/lib.rs
  93. 37 32
      runtime-modules/content-working-group/src/mock.rs
  94. 0 6
      runtime-modules/content-working-group/src/mod.rs
  95. 79 62
      runtime-modules/content-working-group/src/tests.rs
  96. 19 41
      runtime-modules/forum/Cargo.toml
  97. 59 66
      runtime-modules/forum/src/lib.rs
  98. 39 33
      runtime-modules/forum/src/mock.rs
  99. 1 1
      runtime-modules/forum/src/tests.rs
  100. 30 89
      runtime-modules/governance/Cargo.toml

+ 7 - 12
.travis.yml

@@ -7,15 +7,9 @@ language: rust
 # sometimes break the build. When cache is enabled do not use the produced WASM build.
 # This also means the binary should not be used to produce the final chainspec file (because the same
 # one is embedded in the binary)
-cache: cargo
+# cache: cargo
 
-rust:
-  - stable
-
-matrix:
-  include:
-    - os: linux
-      env: TARGET=x86_64-unknown-linux-gnu
+rust: stable
 
 # Skip Rust build in a pull request if no rust project files were modified
 before_install:
@@ -41,7 +35,8 @@ before_script:
 
 script:
   # we set release as build type for all steps to benefit from already compiled packages in prior steps
-  - BUILD_DUMMY_WASM_BINARY=1 cargo clippy --release --target=${TARGET} -- -D warnings
-  - BUILD_DUMMY_WASM_BINARY=1 cargo test --release --verbose --all --target=${TARGET}
-  - TRIGGER_WASM_BUILD=1 WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release --target=${TARGET} -p joystream-node
-  - ls -l ./target/${TARGET}/release/wbuild/joystream-node-runtime/
+  - BUILD_DUMMY_WASM_BINARY=1 cargo clippy --release -- -D warnings
+  - BUILD_DUMMY_WASM_BINARY=1 cargo test --release --verbose --all
+  - TRIGGER_WASM_BUILD=1 WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release -p joystream-node
+  - ls -l ./target/release/wbuild/joystream-node-runtime/
+  - ./target/release/joystream-node --version

File diff suppressed because it is too large
+ 385 - 231
Cargo.lock


+ 6 - 0
README.md

@@ -123,6 +123,12 @@ During a rebase/merge you may want to skip all hooks, you can use `HUSKY_SKIP_HO
 HUSKY_SKIP_HOOKS=1 git rebase ...
 ```
 
+## RLS Extension in VScode or Atom Editors
+
+If you use RLS extension in your IDE, start your editor with the `BUILD_DUMMY_WASM_BINARY=1` environment set to workaround a build issue that occurs in the IDE only.
+
+`BUILD_DUMMY_WASM_BINARY=1 code ./joystream`
+
 ## Authors
 
 See the list of [contributors](https://github.com/Joystream/joystream/graphs/contributors) who participated in this project.

+ 93 - 138
node/Cargo.toml

@@ -1,9 +1,9 @@
 [package]
-authors = ['Joystream']
+authors = ['Joystream contributors']
 build = 'build.rs'
 edition = '2018'
 name = 'joystream-node'
-version = '2.7.0'
+version = '3.0.0'
 default-run = "joystream-node"
 
 [[bin]]
@@ -14,143 +14,98 @@ path = 'bin/main.rs'
 crate-type = ["cdylib", "rlib"]
 
 [dependencies]
-hex-literal = '0.2.1'
-derive_more = '0.14.0'
-exit-future = '0.1.4'
-futures = '0.1.29'
-log = '0.4.8'
-parking_lot = '0.9.0'
-tokio = '0.1.22'
-jsonrpc-core = '13.2.0'
-rand = '0.7.2'
-structopt = '=0.3.5'
+# third-party dependencies
+serde = { version = "1.0.102", features = ["derive"] }
+futures = { version = "0.3.1", features = ["compat"] }
+jsonrpc-core = "14.2.0"
+structopt = { version = "0.3.8", optional = true}
 serde_json = '1.0'
-serde = '1.0'
-hex = '0.4'
-# https://users.rust-lang.org/t/failure-derive-compilation-error/39062
-# quote = '<=1.0.2'
 
-[dependencies.node-runtime]
-package = 'joystream-node-runtime'
-path = '../runtime'
-
-[dependencies.substrate-basic-authorship]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-basic-authorship'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.babe]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-consensus-babe'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.babe-primitives]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-consensus-babe-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.codec]
-package = 'parity-scale-codec'
-version = '1.0.0'
-
-[dependencies.ctrlc]
-features = ['termination']
-version = '3.0'
-
-[dependencies.inherents]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-inherents'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.network]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-network'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.primitives]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.sr-io]
-git = 'https://github.com/paritytech/substrate.git'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.substrate-cli]
-git = 'https://github.com/paritytech/substrate.git'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.substrate-client]
-git = 'https://github.com/paritytech/substrate.git'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.substrate-executor]
-git = 'https://github.com/paritytech/substrate.git'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.substrate-service]
-git = 'https://github.com/paritytech/substrate.git'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.transaction-pool]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-transaction-pool'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.substrate-telemetry]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-telemetry'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.grandpa]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-finality-grandpa'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.grandpa-primitives]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-finality-grandpa-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.im-online]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-im-online'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.substrate-rpc]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-rpc'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.authority-discovery]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-authority-discovery'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.client-db]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-client-db'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.runtime-primitives]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.offchain]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-offchain'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.libp2p]
-version = '0.13.2'
-default-features = false
+# primitives
+sp-authority-discovery = { package = 'sp-authority-discovery', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-consensus-babe = { package = 'sp-consensus-babe', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-finality-grandpa = { package = 'sp-finality-grandpa', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-core = { package = 'sp-core', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-runtime = { package = 'sp-runtime', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-finality-tracker = { package = 'sp-finality-tracker', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-inherents = { package = 'sp-inherents', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-consensus = { package = 'sp-consensus', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-transaction-pool = { package = 'sp-transaction-pool', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-api = { package = 'sp-api', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-blockchain = { package = 'sp-blockchain', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-block-builder = { package = 'sp-block-builder', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+
+# client dependencies
+sc-client-api = { package = 'sc-client-api', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-chain-spec = { package = 'sc-chain-spec', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-consensus = { package = 'sc-consensus', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-transaction-pool = { package = 'sc-transaction-pool', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-network = { package = 'sc-network', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-consensus-babe = { package = 'sc-consensus-babe', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sc-finality-grandpa = { package = 'sc-finality-grandpa', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-basic-authorship = { package = 'sc-basic-authorship', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-service = { package = 'sc-service', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-authority-discovery = { package = 'sc-authority-discovery', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-consensus-epochs = { package = 'sc-consensus-epochs', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-keystore = { package = 'sc-keystore', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-consensus-babe-rpc = { package = 'sc-consensus-babe-rpc', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-finality-grandpa-rpc = { package = 'sc-finality-grandpa-rpc', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-rpc-api = { package = 'sc-rpc-api', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-executor = { package = 'sc-executor', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+
+# frame dependencies
+pallet-im-online = { package = 'pallet-im-online', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+pallet-transaction-payment-rpc = { package = 'pallet-transaction-payment-rpc', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+substrate-frame-rpc-system = { package = 'substrate-frame-rpc-system', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+frame-benchmarking = { package = 'frame-benchmarking', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+
+# node-specific dependencies
+node-runtime = { package= "joystream-node-runtime", path = "../runtime" }
+
+# CLI-specific dependencies
+sc-cli = { package = 'sc-cli', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true }
+frame-benchmarking-cli = { package = 'frame-benchmarking-cli', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true }
+node-inspect = { package = 'node-inspect', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true }
+
+# WASM-specific dependencies
+wasm-bindgen = { version = "0.2.57", optional = true }
+wasm-bindgen-futures = { version = "0.4.7", optional = true }
+browser-utils = { package = 'substrate-browser-utils', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true}
+
+[dev-dependencies]
+tempfile = "3.1.0"
+codec = { package = "parity-scale-codec", version = "1.3.1" }
+sp-timestamp = { package = 'sp-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-keyring = { package = 'sp-keyring', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-consensus-babe = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', features = ["test-helpers"]}
+# sc-service-test = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+frame-system = { package = 'frame-system', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+pallet-transaction-payment = { package = 'pallet-transaction-payment', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+pallet-grandpa = { package = 'pallet-grandpa', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 
 [build-dependencies]
-vergen = '3'
+structopt = { version = "0.3.8", optional = true }
+node-inspect = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true}
+sc-cli = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true}
+frame-benchmarking-cli = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true }
+substrate-build-script-utils = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true }
+
+[features]
+default = [ "cli" ]
+browser = [
+	"browser-utils",
+	"wasm-bindgen",
+	"wasm-bindgen-futures",
+]
+cli = [
+	"node-inspect",
+	"sc-cli",
+	"frame-benchmarking-cli",
+	"sc-service/db",
+	"structopt",
+	"substrate-build-script-utils",
+]
+runtime-benchmarks = [
+	"node-runtime/runtime-benchmarks",
+	"frame-benchmarking-cli",
+]

+ 3 - 20
node/bin/main.rs

@@ -14,27 +14,10 @@
 // You should have received a copy of the GNU General Public License
 // along with Joystream node.  If not, see <http://www.gnu.org/licenses/>.
 
-//! Substrate Node Template CLI library.
+//! Joystream Node.
 
 #![warn(missing_docs)]
-#![warn(unused_extern_crates)]
 
-use joystream_node::cli;
-pub use substrate_cli::{error, IntoExit, VersionInfo};
-
-fn main() {
-    let version = VersionInfo {
-        name: "Joystream Node",
-        commit: env!("VERGEN_SHA_SHORT"),
-        version: env!("CARGO_PKG_VERSION"),
-        executable_name: "joystream-node",
-        author: "Joystream",
-        description: "Joystream substrate node",
-        support_url: "https://www.joystream.org/",
-    };
-
-    if let Err(e) = cli::run(::std::env::args(), cli::Exit, version) {
-        eprintln!("Fatal error: {}\n\n{:?}", e, e);
-        std::process::exit(1)
-    }
+fn main() -> sc_cli::Result<()> {
+    joystream_node::command::run()
 }

+ 64 - 19
node/build.rs

@@ -1,27 +1,72 @@
-use std::{env, path::PathBuf};
+// This file is part of Substrate.
 
-use vergen::{generate_cargo_keys, ConstantsFlags};
+// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
 
-const ERROR_MSG: &str = "Failed to generate metadata files";
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 fn main() {
-    generate_cargo_keys(ConstantsFlags::SHA_SHORT).expect(ERROR_MSG);
-
-    let mut manifest_dir = PathBuf::from(
-        env::var("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` is always set by cargo."),
-    );
-
-    while manifest_dir.parent().is_some() {
-        if manifest_dir.join(".git/HEAD").exists() {
-            println!(
-                "cargo:rerun-if-changed={}",
-                manifest_dir.join(".git/HEAD").display()
-            );
-            return;
-        }
+    #[cfg(feature = "cli")]
+    cli::main();
+}
+
+#[cfg(feature = "cli")]
+mod cli {
+    include!("src/cli.rs");
+
+    use sc_cli::structopt::clap::Shell;
+    use std::{env, fs, path::Path};
+    use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed};
+
+    pub fn main() {
+        build_shell_completion();
+        generate_cargo_keys();
 
-        manifest_dir.pop();
+        rerun_if_git_head_changed();
     }
 
-    println!("cargo:warning=Could not find `.git/HEAD` from manifest dir!");
+    /// Build shell completion scripts for all known shells
+    /// Full list in https://github.com/kbknapp/clap-rs/blob/e9d0562a1dc5dfe731ed7c767e6cee0af08f0cf9/src/app/parser.rs#L123
+    fn build_shell_completion() {
+        for shell in &[
+            Shell::Bash,
+            Shell::Fish,
+            Shell::Zsh,
+            Shell::Elvish,
+            Shell::PowerShell,
+        ] {
+            build_completion(shell);
+        }
+    }
+
+    /// Build the shell auto-completion for a given Shell
+    fn build_completion(shell: &Shell) {
+        let outdir = match env::var_os("OUT_DIR") {
+            None => return,
+            Some(dir) => dir,
+        };
+        let path = Path::new(&outdir)
+            .parent()
+            .unwrap()
+            .parent()
+            .unwrap()
+            .parent()
+            .unwrap()
+            .join("completion-scripts");
+
+        fs::create_dir(&path).ok();
+
+        Cli::clap().gen_completions("joystream-node", *shell, &path);
+    }
 }

+ 139 - 50
node/src/chain_spec.rs

@@ -19,30 +19,33 @@
 // Example:  voting_period: 1 * DAY
 #![allow(clippy::identity_op)]
 
+use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
+use serde_json as json;
+use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
+use sp_consensus_babe::AuthorityId as BabeId;
+use sp_core::{sr25519, Pair, Public};
+use sp_finality_grandpa::AuthorityId as GrandpaId;
+use sp_runtime::traits::{IdentifyAccount, Verify};
+use sp_runtime::Perbill;
+
 use node_runtime::{
     versioned_store::InputValidationLengthConstraint as VsInputValidation,
     AuthorityDiscoveryConfig, BabeConfig, Balance, BalancesConfig, ContentWorkingGroupConfig,
     CouncilConfig, CouncilElectionConfig, DataObjectStorageRegistryConfig,
-    DataObjectTypeRegistryConfig, ElectionParameters, GrandpaConfig, ImOnlineConfig, IndicesConfig,
-    MembersConfig, MigrationConfig, Perbill, ProposalsCodexConfig, SessionConfig, SessionKeys,
-    Signature, StakerStatus, StakingConfig, StorageWorkingGroupConfig, SudoConfig, SystemConfig,
-    VersionedStoreConfig, DAYS, WASM_BINARY,
+    DataObjectTypeRegistryConfig, ElectionParameters, GrandpaConfig, ImOnlineConfig, MembersConfig,
+    ProposalsCodexConfig, SessionConfig, SessionKeys, Signature, StakerStatus, StakingConfig,
+    StorageWorkingGroupConfig, SudoConfig, SystemConfig, VersionedStoreConfig, DAYS, WASM_BINARY,
 };
-pub use node_runtime::{AccountId, GenesisConfig};
-use primitives::{sr25519, Pair, Public};
-use runtime_primitives::traits::{IdentifyAccount, Verify};
 
-use babe_primitives::AuthorityId as BabeId;
-use grandpa_primitives::AuthorityId as GrandpaId;
-use im_online::sr25519::AuthorityId as ImOnlineId;
-use serde_json as json;
+pub use node_runtime::{AccountId, GenesisConfig};
 
 type AccountPublic = <Signature as Verify>::Signer;
 
 /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
-pub type ChainSpec = substrate_service::ChainSpec<GenesisConfig>;
+pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>;
 
 use node_runtime::common::constraints::InputValidationLengthConstraint;
+use sc_chain_spec::ChainType;
 
 /// The chain specification option. This is expected to come in from the CLI and
 /// is little more than one of a number of alternatives which can easily be converted
@@ -73,21 +76,35 @@ where
 /// Helper function to generate stash, controller and session key from seed
 pub fn get_authority_keys_from_seed(
     seed: &str,
-) -> (AccountId, AccountId, GrandpaId, BabeId, ImOnlineId) {
+) -> (
+    AccountId,
+    AccountId,
+    GrandpaId,
+    BabeId,
+    ImOnlineId,
+    AuthorityDiscoveryId,
+) {
     (
         get_account_id_from_seed::<sr25519::Public>(&format!("{}//stash", seed)),
         get_account_id_from_seed::<sr25519::Public>(seed),
         get_from_seed::<GrandpaId>(seed),
         get_from_seed::<BabeId>(seed),
         get_from_seed::<ImOnlineId>(seed),
+        get_from_seed::<AuthorityDiscoveryId>(seed),
     )
 }
 
-fn session_keys(grandpa: GrandpaId, babe: BabeId, im_online: ImOnlineId) -> SessionKeys {
+fn session_keys(
+    grandpa: GrandpaId,
+    babe: BabeId,
+    im_online: ImOnlineId,
+    authority_discovery: AuthorityDiscoveryId,
+) -> SessionKeys {
     SessionKeys {
         grandpa,
         babe,
         im_online,
+        authority_discovery,
     }
 }
 
@@ -98,6 +115,7 @@ impl Alternative {
             Alternative::Development => ChainSpec::from_genesis(
                 "Development",
                 "dev",
+                ChainType::Development,
                 || {
                     testnet_genesis(
                         vec![get_authority_keys_from_seed("Alice")],
@@ -111,7 +129,7 @@ impl Alternative {
                         crate::proposals_config::development(),
                     )
                 },
-                vec![],
+                Vec::new(),
                 None,
                 None,
                 Some(chain_spec_properties()),
@@ -120,6 +138,7 @@ impl Alternative {
             Alternative::LocalTestnet => ChainSpec::from_genesis(
                 "Local Testnet",
                 "local_testnet",
+                ChainType::Local,
                 || {
                     testnet_genesis(
                         vec![
@@ -144,7 +163,7 @@ impl Alternative {
                         crate::proposals_config::development(),
                     )
                 },
-                vec![],
+                Vec::new(),
                 None,
                 None,
                 Some(chain_spec_properties()),
@@ -152,14 +171,6 @@ impl Alternative {
             ),
         })
     }
-
-    pub(crate) fn from(s: &str) -> Option<Self> {
-        match s {
-            "dev" => Some(Alternative::Development),
-            "local" => Some(Alternative::LocalTestnet),
-            _ => None,
-        }
-    }
 }
 
 fn new_vs_validation(min: u16, max_min_diff: u16) -> VsInputValidation {
@@ -180,7 +191,14 @@ pub fn chain_spec_properties() -> json::map::Map<String, json::Value> {
 }
 
 pub fn testnet_genesis(
-    initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId)>,
+    initial_authorities: Vec<(
+        AccountId,
+        AccountId,
+        GrandpaId,
+        BabeId,
+        ImOnlineId,
+        AuthorityDiscoveryId,
+    )>,
     root_key: AccountId,
     endowed_accounts: Vec<AccountId>,
     cpcp: node_runtime::ProposalsConfigParameters,
@@ -197,29 +215,15 @@ pub fn testnet_genesis(
             code: WASM_BINARY.to_vec(),
             changes_trie_config: Default::default(),
         }),
-        balances: Some(BalancesConfig {
+        pallet_balances: Some(BalancesConfig {
             balances: endowed_accounts
                 .iter()
                 .cloned()
                 .map(|k| (k, ENDOWMENT))
                 .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH)))
                 .collect(),
-            vesting: vec![],
         }),
-        indices: Some(IndicesConfig { ids: vec![] }),
-        session: Some(SessionConfig {
-            keys: initial_authorities
-                .iter()
-                .map(|x| {
-                    (
-                        x.0.clone(),
-                        session_keys(x.2.clone(), x.3.clone(), x.4.clone()),
-                    )
-                })
-                .collect::<Vec<_>>(),
-        }),
-        staking: Some(StakingConfig {
-            current_era: 0,
+        pallet_staking: Some(StakingConfig {
             validator_count: 20,
             minimum_validator_count: 1,
             stakers: initial_authorities
@@ -230,15 +234,29 @@ pub fn testnet_genesis(
             slash_reward_fraction: Perbill::from_percent(10),
             ..Default::default()
         }),
-        sudo: Some(SudoConfig { key: root_key }),
-        babe: Some(BabeConfig {
+        pallet_sudo: Some(SudoConfig {
+            key: root_key.clone(),
+        }),
+        pallet_babe: Some(BabeConfig {
             authorities: vec![],
         }),
-        im_online: Some(ImOnlineConfig { keys: vec![] }),
-        authority_discovery: Some(AuthorityDiscoveryConfig { keys: vec![] }),
-        grandpa: Some(GrandpaConfig {
+        pallet_im_online: Some(ImOnlineConfig { keys: vec![] }),
+        pallet_authority_discovery: Some(AuthorityDiscoveryConfig { keys: vec![] }),
+        pallet_grandpa: Some(GrandpaConfig {
             authorities: vec![],
         }),
+        pallet_session: Some(SessionConfig {
+            keys: initial_authorities
+                .iter()
+                .map(|x| {
+                    (
+                        x.0.clone(),
+                        x.0.clone(),
+                        session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone()),
+                    )
+                })
+                .collect::<Vec<_>>(),
+        }),
         council: Some(CouncilConfig {
             active_council: vec![],
             term_ends_at: 1,
@@ -260,9 +278,7 @@ pub fn testnet_genesis(
             default_paid_membership_fee: 100u128,
             members: vec![],
         }),
-        forum: Some(crate::forum_config::from_serialized::create(
-            endowed_accounts[0].clone(),
-        )),
+        forum: Some(crate::forum_config::from_serialized::create(root_key)),
         data_object_type_registry: Some(DataObjectTypeRegistryConfig {
             first_data_object_type_id: 1,
         }),
@@ -310,7 +326,6 @@ pub fn testnet_genesis(
             channel_banner_constraint: InputValidationLengthConstraint::new(5, 1024),
             channel_title_constraint: InputValidationLengthConstraint::new(5, 1024),
         }),
-        migration: Some(MigrationConfig {}),
         proposals_codex: Some(ProposalsCodexConfig {
             set_validator_count_proposal_voting_period: cpcp
                 .set_validator_count_proposal_voting_period,
@@ -367,3 +382,77 @@ pub fn testnet_genesis(
         }),
     }
 }
+
+// Tests are commented out until we find a solution to why
+// building dependencies for the tests are taking so long on Travis CI
+
+// #[cfg(test)]
+// pub(crate) mod tests {
+//     use super::*;
+//     use crate::service::{new_full, new_light};
+//     use sc_service_test;
+
+//     fn local_testnet_genesis_instant_single() -> GenesisConfig {
+//         testnet_genesis(
+//             vec![get_authority_keys_from_seed("Alice")],
+//             get_account_id_from_seed::<sr25519::Public>("Alice"),
+//             vec![get_authority_keys_from_seed("Alice").0],
+//             crate::proposals_config::development(),
+//         )
+//     }
+
+//     /// Local testnet config (single validator - Alice)
+//     pub fn integration_test_config_with_single_authority() -> ChainSpec {
+//         ChainSpec::from_genesis(
+//             "Integration Test",
+//             "test",
+//             ChainType::Development,
+//             local_testnet_genesis_instant_single,
+//             vec![],
+//             None,
+//             None,
+//             None,
+//             Default::default(),
+//         )
+//     }
+
+//     fn local_testnet_genesis() -> GenesisConfig {
+//         testnet_genesis(
+//             vec![
+//                 get_authority_keys_from_seed("Alice"),
+//                 get_authority_keys_from_seed("Bob"),
+//             ],
+//             get_account_id_from_seed::<sr25519::Public>("Alice"),
+//             vec![
+//                 get_authority_keys_from_seed("Alice").0,
+//                 get_authority_keys_from_seed("Bob").0,
+//             ],
+//             crate::proposals_config::development(),
+//         )
+//     }
+
+//     /// Local testnet config (multivalidator Alice + Bob)
+//     pub fn integration_test_config_with_two_authorities() -> ChainSpec {
+//         ChainSpec::from_genesis(
+//             "Integration Test",
+//             "test",
+//             ChainType::Development,
+//             local_testnet_genesis,
+//             vec![],
+//             None,
+//             None,
+//             None,
+//             Default::default(),
+//         )
+//     }
+
+//     #[test]
+//     #[ignore]
+//     fn test_connectivity() {
+//         sc_service_test::connectivity(
+//             integration_test_config_with_two_authorities(),
+//             |config| new_full(config),
+//             |config| new_light(config),
+//         );
+//     }
+// }

+ 45 - 125
node/src/cli.rs

@@ -1,128 +1,48 @@
-use crate::chain_spec;
-use crate::new_full_start;
-use crate::service;
-use futures::{future, sync::oneshot, Future};
-use log::info;
-use std::cell::RefCell;
-pub use substrate_cli::{error, IntoExit, VersionInfo};
-use substrate_cli::{informant, parse_and_prepare, NoCustom, ParseAndPrepare};
-use substrate_service::{AbstractService, Configuration, Roles as ServiceRoles};
-use tokio::runtime::Runtime;
-
-/// Parse command line arguments into service configuration.
-pub fn run<I, T, E>(args: I, exit: E, version: VersionInfo) -> error::Result<()>
-where
-    I: IntoIterator<Item = T>,
-    T: Into<std::ffi::OsString> + Clone,
-    E: IntoExit,
-{
-    type Config<T> = Configuration<(), T>;
-    match parse_and_prepare::<NoCustom, NoCustom, _>(&version, "joystream-node", args) {
-        ParseAndPrepare::Run(cmd) => cmd.run(
-            load_spec,
-            exit,
-            |exit, _cli_args, _custom_args, config: Config<_>| {
-                info!("{}", version.name);
-                info!("  version {}", config.full_version());
-                info!("  by {}, 2019", version.author);
-                info!("Chain specification: {}", config.chain_spec.name());
-                info!("Node name: {}", config.name);
-                info!("Roles: {:?}", config.roles);
-                let runtime = Runtime::new().map_err(|e| format!("{:?}", e))?;
-                match config.roles {
-                    ServiceRoles::LIGHT => run_until_exit(
-                        runtime,
-                        service::new_light(config).map_err(|e| format!("{:?}", e))?,
-                        exit,
-                    ),
-                    _ => run_until_exit(
-                        runtime,
-                        service::new_full(config).map_err(|e| format!("{:?}", e))?,
-                        exit,
-                    ),
-                }
-                .map_err(|e| format!("{:?}", e))
-            },
-        ),
-        ParseAndPrepare::BuildSpec(cmd) => cmd.run::<NoCustom, _, _, _>(load_spec),
-        ParseAndPrepare::ExportBlocks(cmd) => cmd.run_with_builder(
-            |config: Config<_>| Ok(new_full_start!(config).0),
-            load_spec,
-            exit,
-        ),
-        ParseAndPrepare::ImportBlocks(cmd) => cmd.run_with_builder(
-            |config: Config<_>| Ok(new_full_start!(config).0),
-            load_spec,
-            exit,
-        ),
-        ParseAndPrepare::PurgeChain(cmd) => cmd.run(load_spec),
-        ParseAndPrepare::RevertChain(cmd) => {
-            cmd.run_with_builder(|config: Config<_>| Ok(new_full_start!(config).0), load_spec)
-        }
-        ParseAndPrepare::CustomCommand(_) => Ok(()),
-    }?;
-
-    Ok(())
-}
-
-fn load_spec(id: &str) -> Result<Option<chain_spec::ChainSpec>, String> {
-    Ok(match chain_spec::Alternative::from(id) {
-        Some(spec) => Some(spec.load()?),
-        None => None,
-    })
-}
-
-fn run_until_exit<T, E>(mut runtime: Runtime, service: T, e: E) -> error::Result<()>
-where
-    T: AbstractService,
-    E: IntoExit,
-{
-    let (exit_send, exit) = exit_future::signal();
-
-    let informant = informant::build(&service);
-    runtime.executor().spawn(exit.until(informant).map(|_| ()));
-
-    // we eagerly drop the service so that the internal exit future is fired,
-    // but we need to keep holding a reference to the global telemetry guard
-    let _telemetry = service.telemetry();
-
-    let service_res = {
-        let exit = e
-            .into_exit()
-            .map_err(|_| error::Error::Other("Exit future failed.".into()));
-        let service = service.map_err(error::Error::Service);
-        let select = service.select(exit).map(|_| ()).map_err(|(err, _)| err);
-        runtime.block_on(select)
-    };
-
-    exit_send.fire();
-
-    // TODO [andre]: timeout this future #1318
-    let _ = runtime.shutdown_on_idle().wait();
-
-    service_res
+// Copyright 2019 Joystream Contributors
+// This file is part of Joystream node.
+
+// Joystream node is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Joystream node is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Joystream node.  If not, see <http://www.gnu.org/licenses/>.
+
+use sc_cli::RunCmd;
+use structopt::StructOpt;
+
+/// An overarching CLI command definition.
+#[derive(Debug, StructOpt)]
+pub struct Cli {
+    /// Possible subcommand with parameters.
+    #[structopt(subcommand)]
+    pub subcommand: Option<Subcommand>,
+    #[allow(missing_docs)]
+    #[structopt(flatten)]
+    pub run: RunCmd,
 }
 
-// handles ctrl-c
-pub struct Exit;
-impl IntoExit for Exit {
-    type Exit = future::MapErr<oneshot::Receiver<()>, fn(oneshot::Canceled) -> ()>;
-    fn into_exit(self) -> Self::Exit {
-        // can't use signal directly here because CtrlC takes only `Fn`.
-        let (exit_send, exit) = oneshot::channel();
-
-        let exit_send_cell = RefCell::new(Some(exit_send));
-        ctrlc::set_handler(move || {
-            let exit_send = exit_send_cell
-                .try_borrow_mut()
-                .expect("signal handler not reentrant; qed")
-                .take();
-            if let Some(exit_send) = exit_send {
-                exit_send.send(()).expect("Error sending exit notification");
-            }
-        })
-        .expect("Error setting Ctrl-C handler");
-
-        exit.map_err(drop)
-    }
+/// Possible subcommands of the main binary.
+#[derive(Debug, StructOpt)]
+pub enum Subcommand {
+    /// A set of base subcommands handled by `sc_cli`.
+    #[structopt(flatten)]
+    Base(sc_cli::Subcommand),
+
+    /// The custom inspect subcommmand for decoding blocks and extrinsics.
+    #[structopt(
+        name = "inspect",
+        about = "Decode given block or extrinsic using current native runtime."
+    )]
+    Inspect(node_inspect::cli::InspectCmd),
+
+    /// The custom benchmark subcommmand benchmarking runtime pallets.
+    #[structopt(name = "benchmark", about = "Benchmark runtime pallets.")]
+    Benchmark(frame_benchmarking_cli::BenchmarkCmd),
 }

+ 100 - 0
node/src/command.rs

@@ -0,0 +1,100 @@
+// Copyright 2019 Joystream Contributors
+// This file is part of Joystream node.
+
+// Joystream node is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Joystream node is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Joystream node.  If not, see <http://www.gnu.org/licenses/>.
+
+use crate::cli::{Cli, Subcommand};
+use crate::node_executor;
+use crate::node_rpc;
+use crate::{chain_spec, service};
+
+use node_executor::Executor;
+use node_runtime::{opaque::Block, RuntimeApi};
+use sc_cli::{Result, SubstrateCli};
+use sc_finality_grandpa::{self as grandpa};
+
+impl SubstrateCli for Cli {
+    fn impl_name() -> &'static str {
+        "Joystream Node"
+    }
+
+    fn support_url() -> &'static str {
+        "https://www.joystream.org/"
+    }
+
+    fn copyright_start_year() -> i32 {
+        2019
+    }
+
+    fn executable_name() -> &'static str {
+        "joystream-node"
+    }
+
+    fn impl_version() -> &'static str {
+        env!("SUBSTRATE_CLI_IMPL_VERSION")
+    }
+
+    fn description() -> &'static str {
+        env!("CARGO_PKG_DESCRIPTION")
+    }
+
+    fn author() -> &'static str {
+        env!("CARGO_PKG_AUTHORS")
+    }
+
+    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
+        Ok(match id {
+            "dev" => Box::new(chain_spec::Alternative::Development.load()?),
+            "local" => Box::new(chain_spec::Alternative::LocalTestnet.load()?),
+            path => Box::new(chain_spec::ChainSpec::from_json_file(
+                std::path::PathBuf::from(path),
+            )?),
+        })
+    }
+}
+
+/// Parse command line arguments into service configuration.
+pub fn run() -> Result<()> {
+    let cli = Cli::from_args();
+
+    match &cli.subcommand {
+        None => {
+            let runner = cli.create_runner(&cli.run)?;
+            runner.run_node(service::new_light, service::new_full, node_runtime::VERSION)
+        }
+        Some(Subcommand::Inspect(cmd)) => {
+            let runner = cli.create_runner(cmd)?;
+
+            runner.sync_run(|config| cmd.run::<Block, RuntimeApi, Executor>(config))
+        }
+        Some(Subcommand::Benchmark(cmd)) => {
+            if cfg!(feature = "runtime-benchmarks") {
+                let runner = cli.create_runner(cmd)?;
+
+                runner.sync_run(|config| cmd.run::<Block, Executor>(config))
+            } else {
+                println!(
+                    "Benchmarking wasn't enabled when building the node. \
+				You can enable it with `--features runtime-benchmarks`."
+                );
+                Ok(())
+            }
+        }
+        Some(Subcommand::Base(subcommand)) => {
+            let runner = cli.create_runner(subcommand)?;
+
+            runner.run_subcommand(subcommand, |config| Ok(new_full_start!(config).0))
+        }
+    }
+}

+ 4 - 0
node/src/lib.rs

@@ -3,4 +3,8 @@ pub mod cli;
 pub mod forum_config;
 pub mod members_config;
 pub mod proposals_config;
+#[macro_use]
 pub mod service;
+pub mod command;
+pub mod node_executor;
+pub mod node_rpc;

+ 1 - 13
node/src/members_config.rs

@@ -1,7 +1,7 @@
 use serde::Deserialize;
 use serde_json::Result;
 
-use primitives::crypto::{AccountId32, Ss58Codec};
+use sp_core::crypto::{AccountId32, Ss58Codec};
 
 #[derive(Deserialize)]
 struct Member {
@@ -12,18 +12,6 @@ struct Member {
     about: String,
 }
 
-// fn test_load_members() -> Result<Vec<Member>> {
-//     let data = r#"
-//         [{
-//             "address": "5Gn9n7SDJ7VgHqHQWYzkSA4vX6DCmS5TFWdHxikTXp9b4L32",
-//             "handle": "mokhtar",
-//             "avatar_uri": "http://mokhtar.net/avatar.png",
-//             "about": "Mokhtar"
-//         }]"#;
-
-//     serde_json::from_str(data)
-// }
-
 fn parse_members_json() -> Result<Vec<Member>> {
     let data = include_str!("../res/acropolis_members.json");
     serde_json::from_str(data)

+ 10 - 0
node/src/node_executor.rs

@@ -0,0 +1,10 @@
+use sc_executor::native_executor_instance;
+
+// Declare an instance of the native executor named `Executor`. Include the wasm binary as the
+// equivalent wasm code.
+native_executor_instance!(
+    pub Executor,
+    node_runtime::api::dispatch,
+    node_runtime::native_version,
+    frame_benchmarking::benchmarking::HostFunctions,
+);

+ 188 - 0
node/src/node_rpc.rs

@@ -0,0 +1,188 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! A collection of node-specific RPC methods.
+//!
+//! Since `substrate` core functionality makes no assumptions
+//! about the modules used inside the runtime, so do
+//! RPC methods defined in `sc-rpc` crate.
+//! It means that `client/rpc` can't have any methods that
+//! need some strong assumptions about the particular runtime.
+//!
+//! The RPCs available in this crate however can make some assumptions
+//! about how the runtime is constructed and what FRAME pallets
+//! are part of it. Therefore all node-runtime-specific RPCs can
+//! be placed here or imported from corresponding FRAME RPC definitions.
+
+#![warn(missing_docs)]
+
+use std::sync::Arc;
+
+use node_runtime::UncheckedExtrinsic;
+use node_runtime::{opaque::Block, AccountId, Balance, BlockNumber, Hash, Index};
+use sc_consensus_babe::{Config, Epoch};
+use sc_consensus_babe_rpc::BabeRpcHandler;
+use sc_consensus_epochs::SharedEpochChanges;
+use sc_finality_grandpa::{SharedAuthoritySet, SharedVoterState};
+use sc_finality_grandpa_rpc::GrandpaRpcHandler;
+use sc_keystore::KeyStorePtr;
+use sc_rpc_api::DenyUnsafe;
+use sp_api::ProvideRuntimeApi;
+use sp_block_builder::BlockBuilder;
+use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
+use sp_consensus::SelectChain;
+use sp_consensus_babe::BabeApi;
+use sp_transaction_pool::TransactionPool;
+
+/// Light client extra dependencies.
+pub struct LightDeps<C, F, P> {
+    /// The client instance to use.
+    pub client: Arc<C>,
+    /// Transaction pool instance.
+    pub pool: Arc<P>,
+    /// Remote access to the blockchain (async).
+    pub remote_blockchain: Arc<dyn sc_client_api::light::RemoteBlockchain<Block>>,
+    /// Fetcher instance.
+    pub fetcher: Arc<F>,
+}
+
+/// Extra dependencies for BABE.
+pub struct BabeDeps {
+    /// BABE protocol config.
+    pub babe_config: Config,
+    /// BABE pending epoch changes.
+    pub shared_epoch_changes: SharedEpochChanges<Block, Epoch>,
+    /// The keystore that manages the keys of the node.
+    pub keystore: KeyStorePtr,
+}
+
+/// Extra dependencies for GRANDPA
+pub struct GrandpaDeps {
+    /// Voting round info.
+    pub shared_voter_state: SharedVoterState,
+    /// Authority set info.
+    pub shared_authority_set: SharedAuthoritySet<Hash, BlockNumber>,
+}
+
+/// Full client dependencies.
+pub struct FullDeps<C, P, SC> {
+    /// The client instance to use.
+    pub client: Arc<C>,
+    /// Transaction pool instance.
+    pub pool: Arc<P>,
+    /// The SelectChain Strategy
+    pub select_chain: SC,
+    /// Whether to deny unsafe calls
+    pub deny_unsafe: DenyUnsafe,
+    /// BABE specific dependencies.
+    pub babe: BabeDeps,
+    /// GRANDPA specific dependencies.
+    pub grandpa: GrandpaDeps,
+}
+
+/// Instantiate all Full RPC extensions.
+pub fn create_full<C, P, M, SC>(deps: FullDeps<C, P, SC>) -> jsonrpc_core::IoHandler<M>
+where
+    C: ProvideRuntimeApi<Block>,
+    C: HeaderBackend<Block> + HeaderMetadata<Block, Error = BlockChainError> + 'static,
+    C: Send + Sync + 'static,
+    C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>,
+    C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<
+        Block,
+        Balance,
+        UncheckedExtrinsic,
+    >,
+    C::Api: BabeApi<Block>,
+    C::Api: BlockBuilder<Block>,
+    P: TransactionPool + 'static,
+    M: jsonrpc_core::Metadata + Default,
+    SC: SelectChain<Block> + 'static,
+{
+    use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi};
+    use substrate_frame_rpc_system::{FullSystem, SystemApi};
+
+    let mut io = jsonrpc_core::IoHandler::default();
+    let FullDeps {
+        client,
+        pool,
+        select_chain,
+        deny_unsafe,
+        babe,
+        grandpa,
+    } = deps;
+    let BabeDeps {
+        keystore,
+        babe_config,
+        shared_epoch_changes,
+    } = babe;
+    let GrandpaDeps {
+        shared_voter_state,
+        shared_authority_set,
+    } = grandpa;
+
+    io.extend_with(SystemApi::to_delegate(FullSystem::new(
+        client.clone(),
+        pool,
+        deny_unsafe,
+    )));
+    // Making synchronous calls in light client freezes the browser currently,
+    // more context: https://github.com/paritytech/substrate/pull/3480
+    // These RPCs should use an asynchronous caller instead.
+    io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(
+        client.clone(),
+    )));
+    io.extend_with(sc_consensus_babe_rpc::BabeApi::to_delegate(
+        BabeRpcHandler::new(
+            client,
+            shared_epoch_changes,
+            keystore,
+            babe_config,
+            select_chain,
+            deny_unsafe,
+        ),
+    ));
+    io.extend_with(sc_finality_grandpa_rpc::GrandpaApi::to_delegate(
+        GrandpaRpcHandler::new(shared_authority_set, shared_voter_state),
+    ));
+
+    io
+}
+
+/// Instantiate all Light RPC extensions.
+pub fn create_light<C, P, M, F>(deps: LightDeps<C, F, P>) -> jsonrpc_core::IoHandler<M>
+where
+    C: sp_blockchain::HeaderBackend<Block>,
+    C: Send + Sync + 'static,
+    F: sc_client_api::light::Fetcher<Block> + 'static,
+    P: TransactionPool + 'static,
+    M: jsonrpc_core::Metadata + Default,
+{
+    use substrate_frame_rpc_system::{LightSystem, SystemApi};
+
+    let LightDeps {
+        client,
+        pool,
+        remote_blockchain,
+        fetcher,
+    } = deps;
+    let mut io = jsonrpc_core::IoHandler::default();
+    io.extend_with(SystemApi::<Hash, AccountId, Index>::to_delegate(
+        LightSystem::new(client, remote_blockchain, fetcher, pool),
+    ));
+
+    io
+}

+ 532 - 225
node/src/service.rs

@@ -16,107 +16,141 @@
 
 #![warn(unused_extern_crates)]
 
-// Clippy linter warning.
-#![allow(clippy::type_complexity)] // disable it because this is foreign code and can be changed any time
-
-// Clippy linter warning.
-#![allow(clippy::redundant_closure_call)] // disable it because of the substrate lib design
-
-//! Service and ServiceFactory implementation. Specialized wrapper over substrate service.
-
-use client_db::Backend;
-use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider};
-use inherents::InherentDataProviders;
-use network::{construct_simple_protocol, NetworkService};
-use node_runtime::{self, opaque::Block, GenesisConfig, RuntimeApi};
-use offchain::OffchainWorkers;
-use primitives::Blake2Hasher;
-use runtime_primitives::traits::Block as BlockT;
-use std::sync::Arc;
-use substrate_client::{Client, LocalCallExecutor, LongestChain};
-pub use substrate_executor::{native_executor_instance, NativeExecutor};
-use substrate_service::{
-    error::Error as ServiceError, AbstractService, Configuration, NetworkStatus, Service,
-    ServiceBuilder,
-};
-use transaction_pool::{self, txpool::Pool as TransactionPool};
+// Substrate implementation issue.
+#![allow(clippy::redundant_closure_call)]
 
-construct_simple_protocol! {
-    /// Demo protocol attachment for substrate.
-    pub struct NodeProtocol where Block = Block { }
-}
+//! Service implementation. Specialized wrapper over substrate service.
 
-// Declare an instance of the native executor named `Executor`. Include the wasm binary as the
-// equivalent wasm code.
-native_executor_instance!(
-    pub Executor,
-    node_runtime::api::dispatch,
-    node_runtime::native_version
-);
+use node_runtime::opaque::Block;
+use node_runtime::RuntimeApi;
+use sc_consensus::LongestChain;
+use sc_finality_grandpa::{
+    self as grandpa, FinalityProofProvider as GrandpaFinalityProofProvider, StorageAndProofProvider,
+};
+use sc_service::{
+    config::Configuration, error::Error as ServiceError, AbstractService, ServiceBuilder,
+};
+use sp_inherents::InherentDataProviders;
+use std::sync::Arc;
+
+use crate::node_executor;
+use crate::node_rpc;
 
 /// Starts a `ServiceBuilder` for a full service.
 ///
 /// Use this macro if you don't actually need the full service, but just the builder in order to
 /// be able to perform chain operations.
-#[macro_export]
 macro_rules! new_full_start {
     ($config:expr) => {{
-        // type RpcExtension = jsonrpc_core::IoHandler<substrate_rpc::Metadata>;
+        use std::sync::Arc;
+
         let mut import_setup = None;
-        let inherent_data_providers = inherents::InherentDataProviders::new();
+        let mut rpc_setup = None;
+        let inherent_data_providers = sp_inherents::InherentDataProviders::new();
 
-        let builder = substrate_service::ServiceBuilder::new_full::<
-            node_runtime::opaque::Block,
-            node_runtime::RuntimeApi,
-            crate::service::Executor,
+        let builder = sc_service::ServiceBuilder::new_full::<
+            Block,
+            RuntimeApi,
+            node_executor::Executor,
         >($config)?
-        .with_select_chain(|_config, backend| {
-            Ok(substrate_client::LongestChain::new(backend.clone()))
-        })?
-        .with_transaction_pool(|config, client| {
-            Ok(transaction_pool::txpool::Pool::new(
-                config,
-                transaction_pool::FullChainApi::new(client),
+        .with_select_chain(|_config, backend| Ok(sc_consensus::LongestChain::new(backend.clone())))?
+        .with_transaction_pool(|builder| {
+            let pool_api = sc_transaction_pool::FullChainApi::new(builder.client().clone());
+            let config = builder.config();
+
+            Ok(sc_transaction_pool::BasicPool::new(
+                config.transaction_pool.clone(),
+                std::sync::Arc::new(pool_api),
+                builder.prometheus_registry(),
             ))
         })?
-        .with_import_queue(|_config, client, mut select_chain, _transaction_pool| {
-            let select_chain = select_chain
-                .take()
-                .ok_or_else(|| substrate_service::Error::SelectChainRequired)?;
-            let (grandpa_block_import, grandpa_link) =
-                grandpa::block_import::<_, _, _, node_runtime::RuntimeApi, _>(
+        .with_import_queue(
+            |_config,
+             client,
+             mut select_chain,
+             _transaction_pool,
+             spawn_task_handle,
+             prometheus_registry| {
+                let select_chain = select_chain
+                    .take()
+                    .ok_or_else(|| sc_service::Error::SelectChainRequired)?;
+                let (grandpa_block_import, grandpa_link) = grandpa::block_import(
                     client.clone(),
-                    &*client,
+                    &(client.clone() as Arc<_>),
                     select_chain,
                 )?;
-            let justification_import = grandpa_block_import.clone();
-
-            let (block_import, babe_link) = babe::block_import(
-                babe::Config::get_or_compute(&*client)?,
-                grandpa_block_import,
-                client.clone(),
-                client.clone(),
-            )?;
-
-            let import_queue = babe::import_queue(
-                babe_link.clone(),
-                block_import.clone(),
-                Some(Box::new(justification_import)),
-                None,
-                client.clone(),
-                client,
-                inherent_data_providers.clone(),
-            )?;
-
-            import_setup = Some((block_import, grandpa_link, babe_link));
-            Ok(import_queue)
+                let justification_import = grandpa_block_import.clone();
+
+                let (block_import, babe_link) = sc_consensus_babe::block_import(
+                    sc_consensus_babe::Config::get_or_compute(&*client)?,
+                    grandpa_block_import,
+                    client.clone(),
+                )?;
+
+                let import_queue = sc_consensus_babe::import_queue(
+                    babe_link.clone(),
+                    block_import.clone(),
+                    Some(Box::new(justification_import)),
+                    None,
+                    client,
+                    inherent_data_providers.clone(),
+                    spawn_task_handle,
+                    prometheus_registry,
+                )?;
+
+                import_setup = Some((block_import, grandpa_link, babe_link));
+                Ok(import_queue)
+            },
+        )?
+        .with_rpc_extensions_builder(|builder| {
+            let grandpa_link = import_setup
+                .as_ref()
+                .map(|s| &s.1)
+                .expect("GRANDPA LinkHalf is present for full services or set up failed; qed.");
+
+            let shared_authority_set = grandpa_link.shared_authority_set().clone();
+            let shared_voter_state = grandpa::SharedVoterState::empty();
+
+            rpc_setup = Some((shared_voter_state.clone()));
+
+            let babe_link = import_setup
+                .as_ref()
+                .map(|s| &s.2)
+                .expect("BabeLink is present for full services or set up failed; qed.");
+
+            let babe_config = babe_link.config().clone();
+            let shared_epoch_changes = babe_link.epoch_changes().clone();
+
+            let client = builder.client().clone();
+            let pool = builder.pool().clone();
+            let select_chain = builder
+                .select_chain()
+                .cloned()
+                .expect("SelectChain is present for full services or set up failed; qed.");
+            let keystore = builder.keystore().clone();
+
+            Ok(move |deny_unsafe| {
+                let deps = node_rpc::FullDeps {
+                    client: client.clone(),
+                    pool: pool.clone(),
+                    select_chain: select_chain.clone(),
+                    deny_unsafe,
+                    babe: node_rpc::BabeDeps {
+                        babe_config: babe_config.clone(),
+                        shared_epoch_changes: shared_epoch_changes.clone(),
+                        keystore: keystore.clone(),
+                    },
+                    grandpa: node_rpc::GrandpaDeps {
+                        shared_voter_state: shared_voter_state.clone(),
+                        shared_authority_set: shared_authority_set.clone(),
+                    },
+                };
+
+                node_rpc::create_full(deps)
+            })
         })?;
-        // We don't have any custom rpc commands...
-        // .with_rpc_extensions(|client, pool| -> RpcExtension {
-        // 	node_rpc::create(client, pool)
-        // })?;
 
-        (builder, import_setup, inherent_data_providers)
+        (builder, import_setup, inherent_data_providers, rpc_setup)
     }};
 }
 
@@ -126,58 +160,57 @@ macro_rules! new_full_start {
 /// concrete types instead.
 macro_rules! new_full {
 	($config:expr, $with_startup_data: expr) => {{
-		use futures::sync::mpsc;
-		use network::DhtEvent;
+		use futures::prelude::*;
+		use sc_network::Event;
+		use sc_client_api::ExecutorProvider;
+		use sp_core::traits::BareCryptoStorePtr;
 
 		let (
-			is_authority,
+			role,
 			force_authoring,
 			name,
-			disable_grandpa
+			disable_grandpa,
 		) = (
-			$config.roles.is_authority(),
+			$config.role.clone(),
 			$config.force_authoring,
-			$config.name.clone(),
-			$config.disable_grandpa
+			$config.network.node_name.clone(),
+			$config.disable_grandpa,
 		);
 
-        // sentry nodes announce themselves as authorities to the network
-		// and should run the same protocols authorities do, but it should
-		// never actively participate in any consensus process.
-        let participates_in_consensus = is_authority && !$config.sentry_mode;
-
-		let (builder, mut import_setup, inherent_data_providers) = new_full_start!($config);
-
-		// Dht event channel from the network to the authority discovery module. Use bounded channel to ensure
-		// back-pressure. Authority discovery is triggering one event per authority within the current authority set.
-		// This estimates the authority set size to be somewhere below 10 000 thereby setting the channel buffer size to
-		// 10 000.
-		let (dht_event_tx, _dht_event_rx) =
-			mpsc::channel::<DhtEvent>(10_000);
+		let (builder, mut import_setup, inherent_data_providers, mut rpc_setup) =
+			new_full_start!($config);
 
-		let service = builder.with_network_protocol(|_| Ok(crate::service::NodeProtocol::new()))?
-			.with_finality_proof_provider(|client, backend|
-				Ok(Arc::new(grandpa::FinalityProofProvider::new(backend, client)) as _)
-			)?
-			.with_dht_event_tx(dht_event_tx)?
-			.build()?;
+		let service = builder
+			.with_finality_proof_provider(|client, backend| {
+				// GenesisAuthoritySetProvider is implemented for StorageAndProofProvider
+				let provider = client as Arc<dyn grandpa::StorageAndProofProvider<_, _>>;
+				Ok(Arc::new(grandpa::FinalityProofProvider::new(backend, provider)) as _)
+			})?
+			.build_full()?;
 
 		let (block_import, grandpa_link, babe_link) = import_setup.take()
-				.expect("Link Half and Block Import are present for Full Services or setup failed before. qed");
+			.expect("Link Half and Block Import are present for Full Services or setup failed before. qed");
+
+		let shared_voter_state = rpc_setup.take()
+			.expect("The SharedVoterState is present for Full Services or setup failed before. qed");
 
 		($with_startup_data)(&block_import, &babe_link);
 
-		if participates_in_consensus {
-			let proposer = substrate_basic_authorship::ProposerFactory {
-				client: service.client(),
-				transaction_pool: service.transaction_pool(),
-			};
+		if let sc_service::config::Role::Authority { .. } = &role {
+			let proposer = sc_basic_authorship::ProposerFactory::new(
+				service.client(),
+				service.transaction_pool(),
+				service.prometheus_registry().as_ref(),
+			);
 
 			let client = service.client();
 			let select_chain = service.select_chain()
-				.ok_or(substrate_service::Error::SelectChainRequired)?;
+				.ok_or(sc_service::Error::SelectChainRequired)?;
+
+			let can_author_with =
+				sp_consensus::CanAuthorWithNativeVersion::new(client.executor().clone());
 
-			let babe_config = babe::BabeParams {
+			let babe_config = sc_consensus_babe::BabeParams {
 				keystore: service.keystore(),
 				client,
 				select_chain,
@@ -187,62 +220,95 @@ macro_rules! new_full {
 				inherent_data_providers: inherent_data_providers.clone(),
 				force_authoring,
 				babe_link,
+				can_author_with,
 			};
 
-			let babe = babe::start_babe(babe_config)?;
-			service.spawn_essential_task(babe);
-        }
+			let babe = sc_consensus_babe::start_babe(babe_config)?;
+			service.spawn_essential_task_handle().spawn_blocking("babe-proposer", babe);
+		}
+
+		// Spawn authority discovery module.
+		if matches!(role, sc_service::config::Role::Authority{..} | sc_service::config::Role::Sentry {..}) {
+			let (sentries, authority_discovery_role) = match role {
+				sc_service::config::Role::Authority { ref sentry_nodes } => (
+					sentry_nodes.clone(),
+					sc_authority_discovery::Role::Authority (
+						service.keystore(),
+					),
+				),
+				sc_service::config::Role::Sentry {..} => (
+					vec![],
+					sc_authority_discovery::Role::Sentry,
+				),
+				_ => unreachable!("Due to outer matches! constraint; qed.")
+			};
+
+			let network = service.network();
+			let dht_event_stream = network.event_stream("authority-discovery").filter_map(|e| async move { match e {
+				Event::Dht(e) => Some(e),
+				_ => None,
+			}}).boxed();
+			let authority_discovery = sc_authority_discovery::AuthorityDiscovery::new(
+				service.client(),
+				network,
+				sentries,
+				dht_event_stream,
+				authority_discovery_role,
+				service.prometheus_registry(),
+			);
+
+			service.spawn_task_handle().spawn("authority-discovery", authority_discovery);
+		}
 
-        // if the node isn't actively participating in consensus then it doesn't
+		// if the node isn't actively participating in consensus then it doesn't
 		// need a keystore, regardless of which protocol we use below.
-		let keystore = if participates_in_consensus {
-			Some(service.keystore())
+		let keystore = if role.is_authority() {
+			Some(service.keystore() as BareCryptoStorePtr)
 		} else {
 			None
-        };
-
-        let config = grandpa::Config {
-            // FIXME #1578 make this available through chainspec
-            gossip_duration: std::time::Duration::from_millis(333),
-            justification_period: 512,
-            name: Some(name),
-            observer_enabled: true,
-            keystore,
-            is_authority,
-        };
-
-		match (is_authority, disable_grandpa) {
-			(false, false) => {
-				// start the lightweight GRANDPA observer
-				service.spawn_task(Box::new(grandpa::run_grandpa_observer(
-					config,
-					grandpa_link,
-					service.network(),
-					service.on_exit(),
-				)?));
-			},
-			(true, false) => {
-				// start the full GRANDPA voter
-				let grandpa_config = grandpa::GrandpaParams {
-					config,
-					link: grandpa_link,
-					network: service.network(),
-					inherent_data_providers: inherent_data_providers.clone(),
-					on_exit: service.on_exit(),
-					telemetry_on_connect: Some(service.telemetry_on_connect_stream()),
-					voting_rule: grandpa::VotingRulesBuilder::default().build(),
-                };
-                // the GRANDPA voter task is considered infallible, i.e.
-				// if it fails we take down the service with it.
-				service.spawn_essential_task(grandpa::run_grandpa_voter(grandpa_config)?);
-			},
-			(_, true) => {
-				grandpa::setup_disabled_grandpa(
-					service.client(),
-					&inherent_data_providers,
-					service.network(),
-				)?;
-			},
+		};
+
+		let config = grandpa::Config {
+			// FIXME #1578 make this available through chainspec
+			gossip_duration: std::time::Duration::from_millis(333),
+			justification_period: 512,
+			name: Some(name),
+			observer_enabled: false,
+			keystore,
+			is_authority: role.is_network_authority(),
+		};
+
+		let enable_grandpa = !disable_grandpa;
+		if enable_grandpa {
+			// start the full GRANDPA voter
+			// NOTE: non-authorities could run the GRANDPA observer protocol, but at
+			// this point the full voter should provide better guarantees of block
+			// and vote data availability than the observer. The observer has not
+			// been tested extensively yet and having most nodes in a network run it
+			// could lead to finality stalls.
+			let grandpa_config = grandpa::GrandpaParams {
+				config,
+				link: grandpa_link,
+				network: service.network(),
+				inherent_data_providers: inherent_data_providers.clone(),
+				telemetry_on_connect: Some(service.telemetry_on_connect_stream()),
+				voting_rule: grandpa::VotingRulesBuilder::default().build(),
+				prometheus_registry: service.prometheus_registry(),
+				shared_voter_state,
+			};
+
+			// the GRANDPA voter task is considered infallible, i.e.
+			// if it fails we take down the service with it.
+			service.spawn_essential_task_handle().spawn_blocking(
+				"grandpa-voter",
+				grandpa::run_grandpa_voter(grandpa_config)?
+			);
+		} else {
+			grandpa::setup_disabled_grandpa(
+				service.client(),
+				&inherent_data_providers,
+				service.network(),
+			)?;
 		}
 
 		Ok((service, inherent_data_providers))
@@ -252,70 +318,49 @@ macro_rules! new_full {
 	}}
 }
 
-#[allow(dead_code)]
-type ConcreteBlock = node_runtime::opaque::Block;
-#[allow(dead_code)]
-type ConcreteClient = Client<
-    Backend<ConcreteBlock>,
-    LocalCallExecutor<Backend<ConcreteBlock>, NativeExecutor<Executor>>,
-    ConcreteBlock,
-    node_runtime::RuntimeApi,
->;
-#[allow(dead_code)]
-type ConcreteBackend = Backend<ConcreteBlock>;
-
-/// A specialized configuration object for setting up the node..
-pub type NodeConfiguration<C> =
-    Configuration<C, GenesisConfig /*, crate::chain_spec::Extensions*/>;
-
 /// Builds a new service for a full client.
-pub fn new_full<C: Send + Default + 'static>(config: NodeConfiguration<C>)
--> Result<
-	Service<
-		ConcreteBlock,
-		ConcreteClient,
-		LongestChain<ConcreteBackend, ConcreteBlock>,
-		NetworkStatus<ConcreteBlock>,
-		NetworkService<ConcreteBlock, crate::service::NodeProtocol, <ConcreteBlock as BlockT>::Hash>,
-		TransactionPool<transaction_pool::FullChainApi<ConcreteClient, ConcreteBlock>>,
-		OffchainWorkers<
-			ConcreteClient,
-			<ConcreteBackend as substrate_client::backend::Backend<Block, Blake2Hasher>>::OffchainStorage,
-			ConcreteBlock,
-		>
-	>,
-	ServiceError,
->
-{
+pub fn new_full(config: Configuration) -> Result<impl AbstractService, ServiceError> {
     new_full!(config).map(|(service, _)| service)
 }
 
 /// Builds a new service for a light client.
-pub fn new_light<C: Send + Default + 'static>(
-    config: NodeConfiguration<C>,
-) -> Result<impl AbstractService, ServiceError> {
-    // type RpcExtension = jsonrpc_core::IoHandler<substrate_rpc::Metadata>;
+pub fn new_light(config: Configuration) -> Result<impl AbstractService, ServiceError> {
     let inherent_data_providers = InherentDataProviders::new();
 
-    let service = ServiceBuilder::new_light::<Block, RuntimeApi, Executor>(config)?
+    let service = ServiceBuilder::new_light::<Block, RuntimeApi, node_executor::Executor>(config)?
         .with_select_chain(|_config, backend| Ok(LongestChain::new(backend.clone())))?
-        .with_transaction_pool(|config, client| {
-            Ok(TransactionPool::new(
-                config,
-                transaction_pool::FullChainApi::new(client),
-            ))
+        .with_transaction_pool(|builder| {
+            let fetcher = builder
+                .fetcher()
+                .ok_or_else(|| "Trying to start light transaction pool without active fetcher")?;
+            let pool_api =
+                sc_transaction_pool::LightChainApi::new(builder.client().clone(), fetcher);
+            let pool = sc_transaction_pool::BasicPool::with_revalidation_type(
+                builder.config().transaction_pool.clone(),
+                Arc::new(pool_api),
+                builder.prometheus_registry(),
+                sc_transaction_pool::RevalidationType::Light,
+            );
+            Ok(pool)
         })?
         .with_import_queue_and_fprb(
-            |_config, client, backend, fetcher, _select_chain, _tx_pool| {
+            |_config,
+             client,
+             backend,
+             fetcher,
+             _select_chain,
+             _tx_pool,
+             spawn_task_handle,
+             registry| {
                 let fetch_checker = fetcher
                     .map(|fetcher| fetcher.checker().clone())
                     .ok_or_else(|| {
                         "Trying to start light import queue without active fetch checker"
                     })?;
-                let grandpa_block_import = grandpa::light_block_import::<_, _, _, RuntimeApi>(
+                let grandpa_block_import = grandpa::light_block_import(
                     client.clone(),
                     backend,
-                    &*client,
+                    &(client.clone() as Arc<_>),
                     Arc::new(fetch_checker),
                 )?;
 
@@ -323,35 +368,297 @@ pub fn new_light<C: Send + Default + 'static>(
                 let finality_proof_request_builder =
                     finality_proof_import.create_finality_proof_request_builder();
 
-                let (babe_block_import, babe_link) = babe::block_import(
-                    babe::Config::get_or_compute(&*client)?,
+                let (babe_block_import, babe_link) = sc_consensus_babe::block_import(
+                    sc_consensus_babe::Config::get_or_compute(&*client)?,
                     grandpa_block_import,
                     client.clone(),
-                    client.clone(),
                 )?;
 
-                let import_queue = babe::import_queue(
+                let import_queue = sc_consensus_babe::import_queue(
                     babe_link,
                     babe_block_import,
                     None,
                     Some(Box::new(finality_proof_import)),
-                    client.clone(),
                     client,
                     inherent_data_providers.clone(),
+                    spawn_task_handle,
+                    registry,
                 )?;
 
                 Ok((import_queue, finality_proof_request_builder))
             },
         )?
-        .with_network_protocol(|_| Ok(NodeProtocol::new()))?
         .with_finality_proof_provider(|client, backend| {
-            Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, client)) as _)
+            // GenesisAuthoritySetProvider is implemented for StorageAndProofProvider
+            let provider = client as Arc<dyn StorageAndProofProvider<_, _>>;
+            Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, provider)) as _)
         })?
-        // We don't have any custom rpc extensions
-        // .with_rpc_extensions(|client, pool| -> RpcExtension {
-        // 	node_rpc::create(client, pool)
-        // })?
-        .build()?;
+        .with_rpc_extensions(|builder| {
+            let fetcher = builder
+                .fetcher()
+                .ok_or_else(|| "Trying to start node RPC without active fetcher")?;
+            let remote_blockchain = builder
+                .remote_backend()
+                .ok_or_else(|| "Trying to start node RPC without active remote blockchain")?;
+
+            let light_deps = node_rpc::LightDeps {
+                remote_blockchain,
+                fetcher,
+                client: builder.client().clone(),
+                pool: builder.pool(),
+            };
+
+            Ok(node_rpc::create_light(light_deps))
+        })?
+        .build_light()?;
 
     Ok(service)
 }
+
+// Tests are commented out until we find a solution to why
+// building dependencies for the tests are taking so long on Travis CI
+
+// #[cfg(test)]
+// mod tests {
+//     use crate::node_executor;
+//     use crate::node_rpc;
+//     use crate::service::{new_full, new_light};
+//     use codec::{Decode, Encode};
+//     use node_runtime::RuntimeApi;
+//     use node_runtime::{currency::CENTS, SLOT_DURATION};
+//     use node_runtime::{opaque::Block, AccountId, DigestItem, Signature};
+//     use node_runtime::{BalancesCall, Call, UncheckedExtrinsic};
+//     use sc_consensus_babe::{BabeIntermediate, CompatibleDigestItem, INTERMEDIATE_KEY};
+//     use sc_consensus_epochs::descendent_query;
+//     use sc_finality_grandpa::{self as grandpa};
+//     use sc_service::AbstractService;
+//     use sp_consensus::{
+//         BlockImport, BlockImportParams, BlockOrigin, Environment, ForkChoiceStrategy, Proposer,
+//         RecordProof,
+//     };
+//     use sp_core::{crypto::Pair as CryptoPair, H256};
+//     use sp_finality_tracker;
+//     use sp_keyring::AccountKeyring;
+//     use sp_runtime::traits::IdentifyAccount;
+//     use sp_runtime::{
+//         generic::{BlockId, Digest, Era, SignedPayload},
+//         traits::Verify,
+//         traits::{Block as BlockT, Header as HeaderT},
+//         OpaqueExtrinsic,
+//     };
+//     use sp_timestamp;
+//     use sp_transaction_pool::{ChainEvent, MaintainedTransactionPool};
+//     use std::{any::Any, borrow::Cow, sync::Arc};
+
+//     type AccountPublic = <Signature as Verify>::Signer;
+
+//     // Long running test. Run it locally only after the node changes.
+//     #[test]
+//     // It is "ignored", but the node-cli ignored tests are running on the CI.
+//     // This can be run locally with `cargo test --release -p node-cli test_sync -- --ignored`.
+//     #[ignore]
+//     fn test_sync() {
+//         let keystore_path = tempfile::tempdir().expect("Creates keystore path");
+//         let keystore =
+//             sc_keystore::Store::open(keystore_path.path(), None).expect("Creates keystore");
+//         let alice = keystore
+//             .write()
+//             .insert_ephemeral_from_seed::<sc_consensus_babe::AuthorityPair>("//Alice")
+//             .expect("Creates authority pair");
+
+//         let chain_spec = crate::chain_spec::tests::integration_test_config_with_single_authority();
+
+//         // For the block factory
+//         let mut slot_num = 1u64;
+
+//         // For the extrinsics factory
+//         let bob = Arc::new(AccountKeyring::Bob.pair());
+//         let charlie = Arc::new(AccountKeyring::Charlie.pair());
+//         let mut index = 0;
+
+//         sc_service_test::sync(
+//             chain_spec,
+//             |config| {
+//                 let mut setup_handles = None;
+//                 new_full!(
+//                     config,
+//                     |block_import: &sc_consensus_babe::BabeBlockImport<Block, _, _>,
+//                      babe_link: &sc_consensus_babe::BabeLink<Block>| {
+//                         setup_handles = Some((block_import.clone(), babe_link.clone()));
+//                     }
+//                 )
+//                 .map(move |(node, x)| (node, (x, setup_handles.unwrap())))
+//             },
+//             |config| new_light(config),
+//             |service, &mut (ref inherent_data_providers, (ref mut block_import, ref babe_link))| {
+//                 let mut inherent_data = inherent_data_providers
+//                     .create_inherent_data()
+//                     .expect("Creates inherent data.");
+//                 inherent_data.replace_data(sp_finality_tracker::INHERENT_IDENTIFIER, &1u64);
+
+//                 let parent_id = BlockId::number(service.client().chain_info().best_number);
+//                 let parent_header = service.client().header(&parent_id).unwrap().unwrap();
+//                 let parent_hash = parent_header.hash();
+//                 let parent_number = *parent_header.number();
+
+//                 futures::executor::block_on(service.transaction_pool().maintain(
+//                     ChainEvent::NewBlock {
+//                         is_new_best: true,
+//                         hash: parent_header.hash(),
+//                         tree_route: None,
+//                         header: parent_header.clone(),
+//                     },
+//                 ));
+
+//                 let mut proposer_factory = sc_basic_authorship::ProposerFactory::new(
+//                     service.client(),
+//                     service.transaction_pool(),
+//                     None,
+//                 );
+
+//                 let epoch_descriptor = babe_link
+//                     .epoch_changes()
+//                     .lock()
+//                     .epoch_descriptor_for_child_of(
+//                         descendent_query(&*service.client()),
+//                         &parent_hash,
+//                         parent_number,
+//                         slot_num,
+//                     )
+//                     .unwrap()
+//                     .unwrap();
+
+//                 let mut digest = Digest::<H256>::default();
+
+//                 // even though there's only one authority some slots might be empty,
+//                 // so we must keep trying the next slots until we can claim one.
+//                 let babe_pre_digest = loop {
+//                     inherent_data.replace_data(
+//                         sp_timestamp::INHERENT_IDENTIFIER,
+//                         &(slot_num * SLOT_DURATION),
+//                     );
+//                     if let Some(babe_pre_digest) = sc_consensus_babe::test_helpers::claim_slot(
+//                         slot_num,
+//                         &parent_header,
+//                         &*service.client(),
+//                         &keystore,
+//                         &babe_link,
+//                     ) {
+//                         break babe_pre_digest;
+//                     }
+
+//                     slot_num += 1;
+//                 };
+
+//                 digest.push(<DigestItem as CompatibleDigestItem>::babe_pre_digest(
+//                     babe_pre_digest,
+//                 ));
+
+//                 let new_block = futures::executor::block_on(async move {
+//                     let proposer = proposer_factory.init(&parent_header).await;
+//                     proposer
+//                         .unwrap()
+//                         .propose(
+//                             inherent_data,
+//                             digest,
+//                             std::time::Duration::from_secs(1),
+//                             RecordProof::Yes,
+//                         )
+//                         .await
+//                 })
+//                 .expect("Error making test block")
+//                 .block;
+
+//                 let (new_header, new_body) = new_block.deconstruct();
+//                 let pre_hash = new_header.hash();
+//                 // sign the pre-sealed hash of the block and then
+//                 // add it to a digest item.
+//                 let to_sign = pre_hash.encode();
+//                 let signature = alice.sign(&to_sign[..]);
+//                 let item = <DigestItem as CompatibleDigestItem>::babe_seal(signature.into());
+//                 slot_num += 1;
+
+//                 let mut params = BlockImportParams::new(BlockOrigin::File, new_header);
+//                 params.post_digests.push(item);
+//                 params.body = Some(new_body);
+//                 params.intermediates.insert(
+//                     Cow::from(INTERMEDIATE_KEY),
+//                     Box::new(BabeIntermediate::<Block> { epoch_descriptor }) as Box<dyn Any>,
+//                 );
+//                 params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
+
+//                 block_import
+//                     .import_block(params, Default::default())
+//                     .expect("error importing test block");
+//             },
+//             |service, _| {
+//                 let amount = 5 * CENTS;
+//                 let to: AccountId = AccountPublic::from(bob.public()).into_account().into();
+//                 let from: AccountId = AccountPublic::from(charlie.public()).into_account().into();
+//                 let genesis_hash = service.client().block_hash(0).unwrap().unwrap();
+//                 let best_block_id = BlockId::number(service.client().chain_info().best_number);
+//                 let (spec_version, transaction_version) = {
+//                     let version = service.client().runtime_version_at(&best_block_id).unwrap();
+//                     (version.spec_version, version.transaction_version)
+//                 };
+//                 let signer = charlie.clone();
+
+//                 let function = Call::Balances(BalancesCall::transfer(to.into(), amount));
+
+//                 let check_spec_version = frame_system::CheckSpecVersion::new();
+//                 let check_tx_version = frame_system::CheckTxVersion::new();
+//                 let check_genesis = frame_system::CheckGenesis::new();
+//                 let check_era = frame_system::CheckEra::from(Era::Immortal);
+//                 let check_nonce = frame_system::CheckNonce::from(index);
+//                 let check_weight = frame_system::CheckWeight::new();
+//                 let payment = pallet_transaction_payment::ChargeTransactionPayment::from(0);
+//                 let validate_grandpa_equivocation =
+//                     pallet_grandpa::ValidateEquivocationReport::new();
+//                 let extra = (
+//                     check_spec_version,
+//                     check_tx_version,
+//                     check_genesis,
+//                     check_era,
+//                     check_nonce,
+//                     check_weight,
+//                     payment,
+//                     validate_grandpa_equivocation,
+//                 );
+//                 let raw_payload = SignedPayload::from_raw(
+//                     function,
+//                     extra,
+//                     (
+//                         spec_version,
+//                         transaction_version,
+//                         genesis_hash,
+//                         genesis_hash,
+//                         (),
+//                         (),
+//                         (),
+//                         (),
+//                     ),
+//                 );
+//                 let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
+//                 let (function, extra, _) = raw_payload.deconstruct();
+//                 let xt =
+//                     UncheckedExtrinsic::new_signed(function, from.into(), signature.into(), extra)
+//                         .encode();
+//                 let v: Vec<u8> = Decode::decode(&mut xt.as_slice()).unwrap();
+
+//                 index += 1;
+//                 OpaqueExtrinsic(v)
+//             },
+//         );
+//     }
+
+//     #[test]
+//     #[ignore]
+//     fn test_consensus() {
+//         sc_service_test::consensus(
+//             crate::chain_spec::tests::integration_test_config_with_two_authorities(),
+//             |config| new_full(config),
+//             |config| new_light(config),
+//             vec!["//Alice".into(), "//Bob".into()],
+//         )
+//     }
+// }

+ 5 - 1
package.json

@@ -22,7 +22,11 @@
     "pioneer",
     "pioneer/packages/apps*",
     "pioneer/packages/page*",
-    "pioneer/packages/react*"
+    "pioneer/packages/react*",
+    "pioneer/packages/joy-utils",
+    "pioneer/packages/joy-members",
+    "pioneer/packages/joy-pages",
+    "utils/api-examples"
   ],
   "resolutions": {
     "@polkadot/api": "1.26.1",

+ 0 - 3
pioneer/.eslintignore

@@ -2,16 +2,13 @@
 **/coverage/*
 **/node_modules/*
 packages/old-apps/*
-packages/joy-members/*
 packages/joy-election/*
 packages/joy-forum/*
 packages/joy-help/*
 packages/joy-media/*
-packages/joy-pages/*
 packages/joy-proposals/*
 packages/joy-roles/*
 packages/joy-settings/*
-packages/joy-utils/*
 packages/joy-utils-old/*
 .eslintrc.js
 i18next-scanner.config.js

+ 8 - 2
pioneer/.eslintrc.js

@@ -13,14 +13,20 @@ module.exports = {
   rules: {
     ...base.rules,
     '@typescript-eslint/no-explicit-any': 'off',
-    'react/prop-types': 'off',
     'new-cap': 'off',
     '@typescript-eslint/interface-name-prefix': 'off',
     '@typescript-eslint/ban-ts-comment': 'error',
     // why only required in VSCode!?!? is eslint plugin not working like eslint commandline?
     // Or are we having to add this because of new versions of eslint-config-* ?
     'no-console': 'off',
-    'header/header': 'off' // Temporary disable polkadot's rule
+    // Override some extended config rules:
+    'camelcase': 'off',
+    'header/header': 'off',
+    'sort-keys': 'off',
+    'react/jsx-sort-props': 'off',
+    'react/jsx-max-props-per-line': 'off',
+    'sort-destructure-keys/sort-destructure-keys': 'off',
+    '@typescript-eslint/unbound-method': 'warn', // Doesn't work well with our version of Formik, see: https://github.com/formium/formik/issues/2589
   },
   // isolate pioneer from monorepo eslint rules
   root: true

+ 3 - 1
pioneer/packages/apps-config/src/api/spec/index.ts

@@ -10,6 +10,7 @@ import encointerNodeTeeproxy from './encointer-node-teeproxy';
 import kulupu from './kulupu';
 import nodeTemplate from './node-template';
 import stablePoc from './stable-poc';
+import joystreamNode from './joystream-node';
 
 export default {
   acala,
@@ -21,5 +22,6 @@ export default {
   kulupu,
   'node-template': nodeTemplate,
   'stable-poc': stablePoc,
-  stable_poc: stablePoc
+  stable_poc: stablePoc,
+  'joystream-node': joystreamNode
 };

+ 3 - 0
pioneer/packages/apps-config/src/api/spec/joystream-node.ts

@@ -0,0 +1,3 @@
+import { types } from '@joystream/types';
+
+export default types;

+ 5 - 0
pioneer/packages/apps-config/src/settings/endpoints.ts

@@ -29,6 +29,11 @@ function createDev (t: TFunction): LinkOption[] {
 
 function createLive (t: TFunction): LinkOption[] {
   return [
+    {
+      info: 'joystream',
+      text: t<string>('rpc.joystream', 'Joystream (Current Testnet, hosted by Jsgenesis)', { ns: 'apps-config' }),
+      value: 'wss://rome-rpc-endpoint.joystream.org:9944'
+    },
     {
       dnslink: 'polkadot',
       info: 'polkadot',

+ 3 - 1
pioneer/packages/apps-config/src/ui/logos/index.ts

@@ -17,6 +17,7 @@ import nodeNodle from './nodes/nodle.svg';
 import nodePolkadot from './nodes/polkadot-circle.svg';
 import nodePolkadotJs from './nodes/polkadot-js.svg';
 import nodeSubstrate from './nodes/substrate-hexagon.svg';
+import nodeJoystream from './nodes/joystream-node.svg';
 
 // extensions
 import extensionPolkadotJs from './extensions/polkadot-js.svg';
@@ -48,7 +49,8 @@ const nodeLogos: Record<string, any> = [
   ['Nodle Chain Node', nodeNodle],
   ['parity-polkadot', nodePolkadot],
   ['polkadot-js', nodePolkadotJs],
-  ['substrate-node', nodeSubstrate]
+  ['substrate-node', nodeSubstrate],
+  ['joystream-node', nodeJoystream]
 ].reduce((logos, [node, logo]): Record<string, any> => ({
   ...logos,
   [(node as string).toLowerCase().replace(/-/g, ' ')]: logo

+ 1 - 0
pioneer/packages/apps-config/src/ui/logos/nodes/joystream-node.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240"><defs><style>.cls-1{fill:#4038ff;}.cls-2{fill:#fff;}</style></defs><title>Icon-mono-white-1bg-blue</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_14" data-name="Layer 14"><rect class="cls-1" width="240" height="240"/><path class="cls-2" d="M135.28,49.73l12.7,0-.15,59.67a57,57,0,0,1-14.49,37.86,67.76,67.76,0,0,0,1.72-15Z"/><path class="cls-2" d="M94.28,153.78v0a34.19,34.19,0,0,1-26.15,12.61L72,153.73Z"/><path class="cls-2" d="M102,130.94v1.28a34,34,0,0,1-2,11.41l-25-.06,3.83-12.69Z"/><path class="cls-2" d="M158.14,49.78l12.7,0-.09,36.84a57,57,0,0,1-14.49,37.86,67.76,67.76,0,0,0,1.72-15Z"/><path class="cls-2" d="M125.11,49.69l-.21,82.59a57.22,57.22,0,0,1-57.32,57H61.23l3.83-12.69h2.56a44.5,44.5,0,0,0,44.57-44.35l.22-82.58Z"/></g></g></svg>

+ 21 - 41
pioneer/packages/apps-routing/src/index.ts

@@ -9,69 +9,49 @@ import appSettings from '@polkadot/ui-settings';
 // When adding here, also ensure to add to Dummy.tsx
 
 import accounts from './accounts';
-import claims from './claims';
-import contracts from './contracts';
-import council from './council';
-// import dashboard from './dashboard';
-import democracy from './democracy';
 import explorer from './explorer';
 import extrinsics from './extrinsics';
-import genericAsset from './generic-asset';
 import js from './js';
-import parachains from './parachains';
-import poll from './poll';
 import settings from './settings';
-import society from './society';
 import staking from './staking';
 import storage from './storage';
 import sudo from './sudo';
-import techcomm from './techcomm';
 import toolbox from './toolbox';
 import transfer from './transfer';
-import treasury from './treasury';
+// Joy packages
+import members from './joy-members';
+import { terms, privacyPolicy } from './joy-pages';
 
 export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Routes {
   return appSettings.uiMode === 'light'
     ? [
-      // dashboard,
-      explorer(t),
-      accounts(t),
-      claims(t),
-      poll(t),
-      transfer(t),
-      genericAsset(t),
-      null,
+      members(t),
       staking(t),
-      democracy(t),
-      council(t),
-      // TODO Not sure about the inclusion of treasury, parachains & society here
       null,
-      settings(t)
+      transfer(t),
+      accounts(t),
+      settings(t),
+      // Those are hidden
+      terms(t),
+      privacyPolicy(t)
     ]
     : [
-      // dashboard(t),
-      explorer(t),
-      accounts(t),
-      claims(t),
-      poll(t),
-      transfer(t),
-      genericAsset(t),
-      null,
+      members(t),
       staking(t),
-      democracy(t),
-      council(t),
-      treasury(t),
-      techcomm(t),
-      parachains(t),
-      society(t),
       null,
-      contracts(t),
+      transfer(t),
+      accounts(t),
+      settings(t),
+      null,
+      explorer(t),
       storage(t),
       extrinsics(t),
+      js(t),
+      toolbox(t),
       sudo(t),
       null,
-      settings(t),
-      toolbox(t),
-      js(t)
+      // Those are hidden
+      terms(t),
+      privacyPolicy(t)
     ];
 }

+ 15 - 0
pioneer/packages/apps-routing/src/joy-members.ts

@@ -0,0 +1,15 @@
+import { Route } from './types';
+
+import Members from '@polkadot/joy-members/index';
+
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Members,
+    display: {
+      needsApi: ['query.members.nextMemberId']
+    },
+    icon: 'users',
+    name: 'members',
+    text: t<string>('nav.membership', 'Membership', { ns: 'apps-routing' })
+  };
+}

+ 27 - 0
pioneer/packages/apps-routing/src/joy-pages.ts

@@ -0,0 +1,27 @@
+import { Route } from './types';
+
+import { ToS, Privacy } from '@polkadot/joy-pages/index';
+
+export function terms (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: ToS,
+    display: {
+      isHidden: true
+    },
+    text: t<string>('nav.terms', 'Terms of Service', { ns: 'apps-routing' }),
+    icon: 'file',
+    name: 'pages/tos'
+  };
+}
+
+export function privacyPolicy (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Privacy,
+    display: {
+      isHidden: true
+    },
+    text: t<string>('nav.privacy', 'Privacy Policy', { ns: 'apps-routing' }),
+    icon: 'file',
+    name: 'pages/privacy'
+  };
+}

BIN
pioneer/packages/apps/public/favicon.ico


+ 11 - 0
pioneer/packages/apps/public/index.html

@@ -6,6 +6,17 @@
     <link rel="manifest" href="manifest.json">
     <link rel="shortcut icon" href="favicon.ico">
     <title><%= htmlWebpackPlugin.options.PAGE_TITLE %></title>
+    <% if (htmlWebpackPlugin.options.IS_PROD) { %>
+      <!-- Global site tag (gtag.js) - Google Analytics -->
+      <script async src="https://www.googletagmanager.com/gtag/js?id=UA-133429788-6"></script>
+      <script>
+        window.dataLayer = window.dataLayer || [];
+        function gtag(){dataLayer.push(arguments);}
+        gtag('js', new Date());
+
+        gtag('config', 'UA-133429788-6', { 'anonymize_ip': true });
+      </script>
+    <% } %>
     <script type="text/javascript" src="/env-config.js"></script>
   </head>
   <body>

+ 14 - 1
pioneer/packages/apps/src/Apps.tsx

@@ -21,6 +21,9 @@ import SideBar from './SideBar';
 import WarmUp from './WarmUp';
 import { WindowDimensionsCtx } from './WindowDimensions';
 
+/* Joystream-specific */
+import TopBar from './JoyTopBar/TopBar';
+
 interface SidebarState {
   isCollapsed: boolean;
   isMenu: boolean;
@@ -96,7 +99,10 @@ function Apps ({ className = '' }: Props): React.ReactElement<Props> {
             toggleMenu={_toggleMenu}
           />
           <Signer>
-            <Content />
+            <div className='apps--Main'>
+              <TopBar />
+              <Content />
+            </div>
           </Signer>
           <ConnectingOverlay />
           <div id={PORTAL_ID} />
@@ -222,4 +228,11 @@ export default React.memo(styled(Apps)`
       opacity: 1;
     }
   }
+
+  .apps--Main {
+    flex-grow: 1;
+    min-height: 100vh;
+    overflow-x: hidden;
+    overflow-y: auto;
+  }
 `);

+ 1 - 1
pioneer/packages/apps/src/Content/NotFound.tsx

@@ -7,7 +7,7 @@ import { Redirect } from 'react-router';
 
 function NotFound (): React.ReactElement {
   return (
-    <Redirect to='/explorer' />
+    <Redirect to='/staking' />
   );
 }
 

+ 2 - 6
pioneer/packages/apps/src/Content/index.tsx

@@ -78,15 +78,11 @@ function Content ({ className }: Props): React.ReactElement<Props> {
 }
 
 export default React.memo(styled(Content)`
-  background: #f5f4f3;
-  flex-grow: 1;
-  height: 100%;
-  min-height: 100vh;
-  overflow-x: hidden;
-  overflow-y: auto;
+  background: rgba(250, 250, 250);
   padding: 0 1.5rem;
   position: relative;
   width: 100%;
+  height: 100%;
 
   @media(max-width: 768px) {
     padding: 0 0.5rem;

+ 56 - 0
pioneer/packages/apps/src/JoyTopBar/TopBar.tsx

@@ -0,0 +1,56 @@
+import React from 'react';
+import { useMyMembership } from '@polkadot/joy-utils/react/hooks';
+import { InputAddress } from '@polkadot/react-components';
+import { Available } from '@polkadot/react-query';
+import styled from 'styled-components';
+import { useApi } from '@polkadot/react-hooks';
+
+const StyledTopBar = styled.div`
+  padding: 0.75rem;
+  background-color: #3f3f3f;
+  border-bottom: 1px solid #d4d4d5;
+  text-align: right;
+  margin: 0;
+
+  &.NoMyAddress {
+    background-color: #ffeb83;
+    color: #000;
+    text-align: center;
+  }
+
+  .ui--InputAddress {
+    display: inline-block;
+  }
+`;
+
+function JoyTopBar () {
+  const {
+    allAccounts,
+    myAddress
+  } = useMyMembership();
+
+  const { isApiReady } = useApi();
+
+  if (!isApiReady) {
+    return null;
+  }
+
+  const balance = <span className='label'>Balance: </span>;
+  const labelExtra = myAddress
+    ? <Available label={balance} params={myAddress} />
+    : 'No key selected';
+
+  return Object.keys(allAccounts || {}).length ? (
+    <StyledTopBar>
+      <InputAddress
+        defaultValue={myAddress}
+        help='My current key that signs transactions'
+        label='My key'
+        labelExtra={labelExtra}
+        type='account'
+      />
+    </StyledTopBar>
+  ) : null;
+}
+
+export default JoyTopBar;

+ 1 - 1
pioneer/packages/apps/src/SideBar/ChainInfo.tsx

@@ -24,7 +24,7 @@ function ChainInfo ({ className = '', onClick }: Props): React.ReactElement<Prop
 
   return (
     <div
-      className={`apps--SideBar-logo ${className} ui--highlight--border`}
+      className={`apps--SideBar-logo ${className}`}
       onClick={onClick}
     >
       <div className='apps--SideBar-logo-inner'>

+ 3 - 24
pioneer/packages/apps/src/SideBar/index.tsx

@@ -7,7 +7,7 @@ import { Routes } from '@polkadot/apps-routing/types';
 import React, { useCallback, useMemo, useState } from 'react';
 import styled from 'styled-components';
 import createRoutes from '@polkadot/apps-routing';
-import { Button, ChainImg, Icon, Menu, media } from '@polkadot/react-components';
+import { Button, ChainImg, Menu, media } from '@polkadot/react-components';
 
 import { SIDEBAR_MENU_THRESHOLD } from '../constants';
 import NetworkModal from '../modals/Network';
@@ -101,27 +101,6 @@ function SideBar ({ className = '', collapse, handleResize, isCollapsed, isMenuO
                 )
             ))}
             <Menu.Divider hidden />
-            <Menu.Item className='apps--SideBar-Item'>
-              <a
-                className='apps--SideBar-Item-NavLink'
-                href='https://github.com/polkadot-js/apps'
-                rel='noopener noreferrer'
-                target='_blank'
-              >
-                <Icon icon='code-branch' /><span className='text'>{t<string>('nav.github', 'GitHub', { ns: 'apps-routing' })}</span>
-              </a>
-            </Menu.Item>
-            <Menu.Item className='apps--SideBar-Item'>
-              <a
-                className='apps--SideBar-Item-NavLink'
-                href='https://wiki.polkadot.network'
-                rel='noopener noreferrer'
-                target='_blank'
-              >
-                <Icon icon='book' /><span className='text'>{t<string>('nav.wiki', 'Wiki', { ns: 'apps-routing' })}</span>
-              </a>
-            </Menu.Item>
-            <Menu.Divider hidden />
             {!isCollapsed && <NodeInfo />}
           </div>
           <div className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}>
@@ -159,7 +138,7 @@ export default React.memo(styled(SideBar)`
 
   .apps--SideBar {
     align-items: center;
-    background: #4f5255;
+    background: #3f3f3f;
     box-sizing: border-box;
     display: flex;
     flex-flow: column;
@@ -221,7 +200,7 @@ export default React.memo(styled(SideBar)`
     }
 
     .apps--SideBar-collapse {
-      background: #4f5255;
+      background: #3f3f3f;
       bottom: 0;
       left: 0;
       padding: 0.75rem 0 .75rem 0.65rem;

+ 20 - 13
pioneer/packages/apps/src/index.tsx

@@ -20,6 +20,9 @@ import settings from '@polkadot/ui-settings';
 import Apps from './Apps';
 import WindowDimensions from './WindowDimensions';
 
+/* Joystream-specific */
+import { MyMembershipProvider, MyAccountProvider } from '@polkadot/joy-utils/react/context';
+
 const rootId = 'root';
 const rootElement = document.getElementById(rootId);
 const theme = { theme: settings.uiTheme };
@@ -38,19 +41,23 @@ store.each((_, key): void => {
 ReactDOM.render(
   <Suspense fallback='...'>
     <ThemeProvider theme={theme}>
-      <Queue>
-        <Api url={settings.apiUrl}>
-          <BlockAuthors>
-            <Events>
-              <HashRouter>
-                <WindowDimensions>
-                  <Apps />
-                </WindowDimensions>
-              </HashRouter>
-            </Events>
-          </BlockAuthors>
-        </Api>
-      </Queue>
+      <MyAccountProvider>
+        <Queue>
+          <Api url={settings.apiUrl}>
+            <MyMembershipProvider>
+              <BlockAuthors>
+                <Events>
+                  <HashRouter>
+                    <WindowDimensions>
+                      <Apps />
+                    </WindowDimensions>
+                  </HashRouter>
+                </Events>
+              </BlockAuthors>
+            </MyMembershipProvider>
+          </Api>
+        </Queue>
+      </MyAccountProvider>
     </ThemeProvider>
   </Suspense>,
   rootElement

+ 3 - 11
pioneer/packages/apps/webpack.base.config.js

@@ -98,7 +98,7 @@ function createWebpack (ENV, context) {
           ]
         },
         {
-          exclude: [/semantic-ui-css/],
+          // Original config had "exclude: [/semantic-ui-css/]"
           test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
           use: [
             {
@@ -112,7 +112,8 @@ function createWebpack (ENV, context) {
           ]
         },
         {
-          exclude: [/semantic-ui-css/],
+          // Original config had "exclude: [/semantic-ui-css/]", because Semantic UI Icons
+          // are not used in polkadot-js/apps repository, but they are used in ours
           test: [/\.eot$/, /\.ttf$/, /\.svg$/, /\.woff$/, /\.woff2$/],
           use: [
             {
@@ -123,15 +124,6 @@ function createWebpack (ENV, context) {
               }
             }
           ]
-        },
-        {
-          include: [/semantic-ui-css/],
-          test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.eot$/, /\.ttf$/, /\.svg$/, /\.woff$/, /\.woff2$/],
-          use: [
-            {
-              loader: require.resolve('null-loader')
-            }
-          ]
         }
       ]
     },

+ 2 - 1
pioneer/packages/apps/webpack.config.js

@@ -20,7 +20,8 @@ module.exports = merge(
     devtool: process.env.BUILD_ANALYZE ? 'source-map' : false,
     plugins: [
       new HtmlWebpackPlugin({
-        PAGE_TITLE: 'Polkadot/Substrate Portal',
+        IS_PROD: ENV === 'production',
+        PAGE_TITLE: 'Joystream Network Portal',
         inject: true,
         template: path.join(context, `${hasPublic ? 'public/' : ''}index.html`)
       })

+ 0 - 0
pioneer/packages/joy-members/.skip-build


+ 3 - 3
pioneer/packages/joy-members/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@polkadot/joy-utils": "^0.1.1"
   }
 }

+ 47 - 29
pioneer/packages/joy-members/src/Dashboard.tsx

@@ -3,12 +3,12 @@ import BN from 'bn.js';
 
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
-import { Bubble } from '@polkadot/react-components/index';
+import { withCalls } from '@polkadot/react-api/hoc';
+import { Label } from 'semantic-ui-react';
 import { formatNumber } from '@polkadot/util';
 import { bool as Bool } from '@polkadot/types';
 
-import Section from '@polkadot/joy-utils/Section';
+import { Section } from '@polkadot/joy-utils/react/components';
 import translate from './translate';
 import { queryMembershipToProp } from './utils';
 
@@ -27,39 +27,57 @@ class Dashboard extends React.PureComponent<Props> {
   renderGeneral () {
     const p = this.props;
     const { newMembershipsAllowed: isAllowed } = p;
-    let isAllowedColor = '';
+    let isAllowedColor: 'grey' | 'green' | 'red' = 'grey';
+
     if (isAllowed) {
       isAllowedColor = isAllowed.eq(true) ? 'green' : 'red';
     }
-    return <Section title='General'>
-      <Bubble label='New memberships allowed?' className={isAllowedColor}>
-        {isAllowed && (isAllowed.eq(true) ? 'Yes' : 'No')}
-      </Bubble>
-      <Bubble label='Next member ID'>
-        {formatNumber(p.nextMemberId)}
-      </Bubble>
-      <Bubble label='First member ID'>
-        {formatNumber(FIRST_MEMBER_ID)}
-      </Bubble>
-    </Section>;
+
+    return (
+      <Section title='General'>
+        <Label.Group size='large'>
+          <Label color={isAllowedColor}>
+            New memberships allowed?
+            <Label.Detail>{isAllowed && (isAllowed.eq(true) ? 'Yes' : 'No')}</Label.Detail>
+          </Label>
+          <Label color='grey'>
+            Next member ID
+            <Label.Detail>{formatNumber(p.nextMemberId)}</Label.Detail>
+          </Label>
+          <Label color='grey'>
+            First member ID
+            <Label.Detail>{formatNumber(FIRST_MEMBER_ID)}</Label.Detail>
+          </Label>
+        </Label.Group>
+      </Section>
+    );
   }
 
   renderValidation () {
     const p = this.props;
-    return <Section title='Validation'>
-      <Bubble label='Min. length of handle'>
-        {formatNumber(p.minHandleLength)} chars
-      </Bubble>
-      <Bubble label='Max. length of handle'>
-        {formatNumber(p.maxHandleLength)} chars
-      </Bubble>
-      <Bubble label='Max. length of avatar URI'>
-        {formatNumber(p.maxAvatarUriLength)} chars
-      </Bubble>
-      <Bubble label='Max. length of about'>
-        {formatNumber(p.maxAboutTextLength)} chars
-      </Bubble>
-    </Section>;
+
+    return (
+      <Section title='Validation'>
+        <Label.Group color='grey' size='large'>
+          <Label>
+            Min. length of handle
+            <Label.Detail>{formatNumber(p.minHandleLength)} chars</Label.Detail>
+          </Label>
+          <Label>
+            Max. length of handle
+            <Label.Detail>{formatNumber(p.maxHandleLength)} chars</Label.Detail>
+          </Label>
+          <Label>
+            Max. length of avatar URI
+            <Label.Detail>{formatNumber(p.maxAvatarUriLength)} chars</Label.Detail>
+          </Label>
+          <Label>
+            Max. length of about
+            <Label.Detail>{formatNumber(p.maxAboutTextLength)} chars</Label.Detail>
+          </Label>
+        </Label.Group>
+      </Section>
+    );
   }
 
   render () {

+ 27 - 23
pioneer/packages/joy-members/src/Details.tsx

@@ -5,18 +5,18 @@ import ReactMarkdown from 'react-markdown';
 import { IdentityIcon } from '@polkadot/react-components';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { Option } from '@polkadot/types';
 import BalanceDisplay from '@polkadot/react-components/Balance';
-import AddressMini from '@polkadot/react-components/AddressMiniJoy';
+import AddressMini from '@polkadot/react-components/AddressMini';
 import { formatNumber } from '@polkadot/util';
 
 import translate from './translate';
 import { MemberId, Membership, EntryMethod, Paid, Screening, Genesis, SubscriptionId } from '@joystream/types/members';
 import { queryMembershipToProp } from './utils';
 import { Seat } from '@joystream/types/council';
-import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/index';
-import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
+import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/react/hocs/accounts';
 
 type Props = ApiProps & I18nProps & MyAccountProps & {
   preview?: boolean;
@@ -28,6 +28,7 @@ type Props = ApiProps & I18nProps & MyAccountProps & {
 class Component extends React.PureComponent<Props> {
   render () {
     const { membership } = this.props;
+
     return membership && !membership.handle.isEmpty
       ? this.renderProfile(membership)
       : (
@@ -54,34 +55,34 @@ class Component extends React.PureComponent<Props> {
     const hasAvatar = avatar_uri && nonEmptyStr(avatar_uri.toString());
     const isMyProfile = myAddress && (myAddress === root_account.toString() || myAddress === controller_account.toString());
     const isCouncilor: boolean = (
-      (activeCouncil.find(x => root_account.eq(x.member)) !== undefined) ||
-      (activeCouncil.find(x => controller_account.eq(x.member)) !== undefined)
+      (activeCouncil.find((x) => root_account.eq(x.member)) !== undefined) ||
+      (activeCouncil.find((x) => controller_account.eq(x.member)) !== undefined)
     );
 
     return (
       <>
-      <div className={`item ProfileDetails ${isMyProfile && 'MyProfile'}`}>
-        {hasAvatar
-          ? <img className='ui avatar image' src={avatar_uri.toString()} />
-          : <IdentityIcon className='image' value={root_account} size={40} />
-        }
-        <div className='content'>
-          <div className='header'>
-            <Link to={`/members/${handle.toString()}`} className='handle'>{handle.toString()}</Link>
-            {isMyProfile && <Link to={'/members/edit'} className='ui tiny button'>Edit my profile</Link>}
-          </div>
-          <div className='description'>
-            {isCouncilor &&
-              <b className='muted text' style={{ color: '#607d8b' }}>
+        <div className={`item ProfileDetails${isMyProfile ? ' MyProfile' : ''}`}>
+          {hasAvatar
+            ? <img className='ui avatar image' src={avatar_uri.toString()} />
+            : <IdentityIcon className='image' value={root_account} size={40} />
+          }
+          <div className='content'>
+            <div className='header'>
+              <Link to={`/members/${handle.toString()}`} className='handle'>{handle.toString()}</Link>
+              {isMyProfile && <Link to={'/members/edit'} className='ui tiny button'>Edit my profile</Link>}
+            </div>
+            <div className='description'>
+              {isCouncilor &&
+              <b className='muted text' style={{ color: '#607d8b', display: 'block' }}>
                 <i className='university icon'></i>
                 Council member
               </b>}
-            <BalanceDisplay label='Balance(root): ' params={root_account} />
-            <div>MemberId: {this.props.memberId.toString()}</div>
+              <BalanceDisplay label='Balance(root): ' params={root_account} />
+              <div>MemberId: {this.props.memberId.toString()}</div>
+            </div>
           </div>
         </div>
-      </div>
-      {!preview && this.renderDetails(membership, isCouncilor)}
+        {!preview && this.renderDetails(membership, isCouncilor)}
       </>
     );
   }
@@ -147,11 +148,14 @@ class Component extends React.PureComponent<Props> {
 
   private renderEntryMethod (entry: EntryMethod) {
     const etype = entry.type;
+
     if (etype === Paid.name) {
       const paid = entry.value as Paid;
+
       return <div>Paid, terms ID: {paid.toNumber()}</div>;
     } else if (etype === Screening.name) {
       const accountId = entry.value as Screening;
+
       return <div>Screened by <AddressMini value={accountId} isShort={false} isPadded={false} withBalance /></div>;
     } else if (etype === Genesis.name) {
       return <div>Created at Genesis</div>;

+ 3 - 1
pioneer/packages/joy-members/src/DetailsByHandle.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { stringToU8a, u8aToHex } from '@polkadot/util';
 
 import translate from './translate';
@@ -16,6 +16,7 @@ type DetailsByHandleProps = {
 
 function DetailsByHandleInner (p: DetailsByHandleProps) {
   const { memberIdByHandle: memberId } = p;
+
   return memberId !== undefined // here we can't make distinction value existing and loading
     ? <div className='ui massive relaxed middle aligned list FullProfile'>
       <Details memberId={memberId} />
@@ -39,6 +40,7 @@ class Component extends React.PureComponent<Props> {
   render () {
     const { match: { params: { handle } } } = this.props;
     const handleHex = u8aToHex(stringToU8a(handle));
+
     return (
       <DetailsByHandle handle={handleHex} />
     );

+ 53 - 45
pioneer/packages/joy-members/src/EditForm.tsx

@@ -1,19 +1,18 @@
 import BN from 'bn.js';
-import React from 'react';
+import React, { useContext } from 'react';
 import { Link } from 'react-router-dom';
 import { Form, Field, withFormik, FormikProps } from 'formik';
 import * as Yup from 'yup';
 
 import { Vec } from '@polkadot/types';
-import Section from '@polkadot/joy-utils/Section';
-import TxButton from '@polkadot/joy-utils/TxButton';
-import * as JoyForms from '@polkadot/joy-utils/forms';
+import { Section, TxButton } from '@polkadot/joy-utils/react/components';
+import * as JoyForms from '@polkadot/joy-utils/react/components/forms';
 import { SubmittableResult } from '@polkadot/api';
 import { MemberId, Membership, PaidTermId, PaidMembershipTerms } from '@joystream/types/members';
 import { OptionText } from '@joystream/types/common';
-import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
+import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/react/hocs/accounts';
 import { queryMembershipToProp } from './utils';
-import { withCalls } from '@polkadot/react-api/index';
+import { withCalls, ApiContext } from '@polkadot/react-api/index';
 import { Button, Message } from 'semantic-ui-react';
 import { formatBalance } from '@polkadot/util';
 import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types';
@@ -79,12 +78,15 @@ const InnerForm = (props: FormProps) => {
     memberId
   } = props;
 
+  const { api } = useContext(ApiContext);
+
   const onSubmit = (sendTx: () => void) => {
     if (isValid) sendTx();
   };
 
   const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
     setSubmitting(false);
+
     if (txResult == null) {
       // Tx cancelled.
 
@@ -102,7 +104,9 @@ const InnerForm = (props: FormProps) => {
 
   // TODO extract to forms.tsx
   const fieldToTextOption = (field: FieldName): OptionText => {
-    return isFieldChanged(field) ? OptionText.some(values[field]) : OptionText.none();
+    return isFieldChanged(field)
+      ? api.createType('Option<Text>', values[field])
+      : api.createType('Option<Text>', null);
   };
 
   const buildTxParams = () => {
@@ -126,29 +130,29 @@ const InnerForm = (props: FormProps) => {
   // TODO show warning that you don't have enough balance to buy a membership
 
   return (
-    <Section title="My Membership Profile">
-      <Form className="ui form JoyForm">
+    <Section title='My Membership Profile'>
+      <Form className='ui form JoyForm'>
         <LabelledText
-          name="handle"
-          label="Handle/nickname"
+          name='handle'
+          label='Handle/nickname'
           placeholder={'You can use a-z, 0-9 and underscores.'}
           style={{ maxWidth: '30rem' }}
           {...props}
         />
         <LabelledText
-          name="avatar"
-          label="Avatar URL"
-          placeholder="Paste here an URL of your avatar image."
+          name='avatar'
+          label='Avatar URL'
+          placeholder='Paste here an URL of your avatar image.'
           {...props}
         />
-        <LabelledField name="about" label="About" {...props}>
+        <LabelledField name='about' label='About' {...props}>
           <Field
-            component="textarea"
-            id="about"
-            name="about"
+            component='textarea'
+            id='about'
+            name='about'
             disabled={isSubmitting}
             rows={3}
-            placeholder="Write here anything you would like to share about yourself with Joystream community."
+            placeholder='Write here anything you would like to share about yourself with Joystream community.'
           />
         </LabelledField>
         {!profile && paidTerms && (
@@ -165,24 +169,26 @@ const InnerForm = (props: FormProps) => {
           </Message>
         )}
         <LabelledField invisibleLabel {...props}>
-          <TxButton
-            type="submit"
-            size="large"
-            label={profile ? 'Update my profile' : 'Register'}
-            isDisabled={!dirty || isSubmitting}
-            params={buildTxParams()}
-            tx={profile ? 'members.updateMembership' : 'members.buyMembership'}
-            onClick={onSubmit}
-            txFailedCb={onTxFailed}
-            txSuccessCb={onTxSuccess}
-          />
-          <Button
-            type="button"
-            size="large"
-            disabled={!dirty || isSubmitting}
-            onClick={() => resetForm()}
-            content="Reset form"
-          />
+          <div style={{ display: 'flex' }}>
+            <TxButton
+              type='submit'
+              size='large'
+              label={profile ? 'Update my profile' : 'Register'}
+              isDisabled={!dirty || isSubmitting}
+              params={buildTxParams()}
+              tx={profile ? 'members.updateMembership' : 'members.buyMembership'}
+              onClick={onSubmit}
+              txFailedCb={onTxFailed}
+              txSuccessCb={onTxSuccess}
+            />
+            <Button
+              type='button'
+              size='large'
+              disabled={!dirty || isSubmitting}
+              onClick={() => resetForm()}
+              content='Reset form'
+            />
+          </div>
         </LabelledField>
       </Form>
     </Section>
@@ -191,8 +197,9 @@ const InnerForm = (props: FormProps) => {
 
 const EditForm = withFormik<OuterProps, FormValues>({
   // Transform outer props into form values
-  mapPropsToValues: props => {
+  mapPropsToValues: (props) => {
     const { profile: p } = props;
+
     return {
       handle: p ? p.handle.toString() : '',
       avatar: p ? p.avatar_uri.toString() : '',
@@ -202,7 +209,7 @@ const EditForm = withFormik<OuterProps, FormValues>({
 
   validationSchema: buildSchema,
 
-  handleSubmit: values => {
+  handleSubmit: (values) => {
     // do submitting things
   }
 })(InnerForm);
@@ -220,6 +227,7 @@ type WithMembershipDataProps = {
 
 function WithMembershipDataInner (p: WithMembershipDataProps) {
   const triedToFindProfile = !p.memberId || p.membership;
+
   if (
     triedToFindProfile &&
     p.paidTerms &&
@@ -228,7 +236,9 @@ function WithMembershipDataInner (p: WithMembershipDataProps) {
     p.maxAvatarUriLength &&
     p.maxAboutTextLength
   ) {
-    const membership = p.membership && !p.membership.handle.isEmpty ? p.membership : undefined;
+    const membership = (p.memberId && p.membership && !p.membership.handle.isEmpty)
+      ? p.membership
+      : undefined;
 
     if (!membership && p.paidTerms.isEmpty) {
       console.error('Could not find active paid membership terms');
@@ -267,10 +277,10 @@ type WithMembershipDataWrapperProps = MyAccountProps & {
 function WithMembershipDataWrapperInner (p: WithMembershipDataWrapperProps) {
   if (p.allAccounts && !Object.keys(p.allAccounts).length) {
     return (
-      <Message warning className="JoyMainStatus">
+      <Message warning className='JoyMainStatus'>
         <Message.Header>Please create a key to get started.</Message.Header>
         <div style={{ marginTop: '1rem' }}>
-          <Link to={'/accounts'} className="ui button orange">
+          <Link to={'/accounts'} className='ui button orange'>
             Create key
           </Link>
         </div>
@@ -280,9 +290,7 @@ function WithMembershipDataWrapperInner (p: WithMembershipDataWrapperProps) {
 
   if (p.memberIdsByRootAccountId && p.memberIdsByControllerAccountId && p.paidTermsIds) {
     if (p.paidTermsIds.length) {
-      // let member_ids = p.memberIdsByRootAccountId.slice(); // u8a.subarray is not a function!!
-      p.memberIdsByRootAccountId.concat(p.memberIdsByControllerAccountId);
-      const memberId = p.memberIdsByRootAccountId.length ? p.memberIdsByRootAccountId[0] : undefined;
+      const [memberId] = p.memberIdsByRootAccountId.toArray().concat(p.memberIdsByControllerAccountId.toArray());
 
       return <WithMembershipData memberId={memberId} paidTermsId={p.paidTermsIds[0]} />;
     } else {

+ 11 - 6
pioneer/packages/joy-members/src/List.tsx

@@ -4,13 +4,14 @@ import React from 'react';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 
-import Section from '@polkadot/joy-utils/Section';
+import { Section } from '@polkadot/joy-utils/react/components';
 import translate from './translate';
 import Details from './Details';
 import { MemberId } from '@joystream/types/members';
 import { RouteComponentProps, Redirect } from 'react-router-dom';
 import { Pagination, Icon, PaginationProps } from 'semantic-ui-react';
 import styled from 'styled-components';
+import { withApi } from '@polkadot/react-api';
 
 const StyledPagination = styled(Pagination)`
   border-bottom: 1px solid #ddd !important;
@@ -22,7 +23,7 @@ type Props = ApiProps & I18nProps & RouteComponentProps & {
   match: { params: { page?: string } };
 };
 
-type State = {};
+type State = Record<any, never>;
 
 const MEMBERS_PER_PAGE = 20;
 
@@ -31,7 +32,8 @@ class Component extends React.PureComponent<Props, State> {
 
   onPageChange = (e: React.MouseEvent, data: PaginationProps) => {
     const { history } = this.props;
-    history.push(`/members/list/${data.activePage}`);
+
+    history.push(`/members/list/${data.activePage || 1}`);
   }
 
   renderPagination (currentPage: number, pagesCount: number) {
@@ -55,7 +57,8 @@ class Component extends React.PureComponent<Props, State> {
     const {
       firstMemberId,
       membersCreated,
-      match: { params: { page } }
+      match: { params: { page } },
+      api
     } = this.props;
 
     const membersCount = membersCreated.toNumber();
@@ -67,11 +70,13 @@ class Component extends React.PureComponent<Props, State> {
     }
 
     const ids: MemberId[] = [];
+
     if (membersCount > 0) {
       const firstId = firstMemberId.toNumber() + (currentPage - 1) * MEMBERS_PER_PAGE;
       const lastId = Math.min(firstId + MEMBERS_PER_PAGE, membersCount) - 1;
+
       for (let i = firstId; i <= lastId; i++) {
-        ids.push(new MemberId(i));
+        ids.push(api.createType('MemberId', i));
       }
     }
 
@@ -95,4 +100,4 @@ class Component extends React.PureComponent<Props, State> {
   }
 }
 
-export default translate(Component);
+export default translate(withApi(Component));

+ 8 - 8
pioneer/packages/joy-members/src/MemberPreview.tsx

@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
 
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls, withMulti } from '@polkadot/react-api/with';
+import { withCalls, withMulti } from '@polkadot/react-api/hoc';
 import { Vec } from '@polkadot/types';
 import { AccountId } from '@polkadot/types/interfaces';
 import IdentityIcon from '@polkadot/react-components/IdentityIcon';
@@ -12,9 +12,8 @@ import translate from './translate';
 import { MemberId, Membership } from '@joystream/types/members';
 import { queryMembershipToProp } from './utils';
 import { Seat } from '@joystream/types/council';
-import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/index';
-import { FlexCenter } from '@polkadot/joy-utils/FlexCenter';
-import { MutedSpan } from '@polkadot/joy-utils/MutedText';
+import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { FlexCenter, MutedSpan } from '@polkadot/joy-utils/react/components';
 
 const AvatarSizePx = 36;
 const InlineAvatarSizePx = 24;
@@ -33,6 +32,7 @@ type MemberPreviewProps = ApiProps & I18nProps & {
 class InnerMemberPreview extends React.PureComponent<MemberPreviewProps> {
   render () {
     const { membership } = this.props;
+
     return membership && !membership.handle.isEmpty
       ? this.renderProfile(membership)
       : null;
@@ -43,19 +43,19 @@ class InnerMemberPreview extends React.PureComponent<MemberPreviewProps> {
     const { handle, avatar_uri } = membership;
 
     const hasAvatar = avatar_uri && nonEmptyStr(avatar_uri.toString());
-    const isCouncilor: boolean = accountId !== undefined && activeCouncil.find(x => accountId.eq(x.member)) !== undefined;
+    const isCouncilor: boolean = accountId !== undefined && activeCouncil.find((x) => accountId.eq(x.member)) !== undefined;
 
     const avatarSize = inline ? InlineAvatarSizePx : AvatarSizePx;
 
-    return <div className={`JoyMemberPreview ${className}`} style={style}>
+    return <div className={`JoyMemberPreview ${className || ''}`} style={style}>
       <FlexCenter>
         {prefixLabel &&
           <MutedSpan className='PrefixLabel'>{prefixLabel}</MutedSpan>
         }
         {hasAvatar ? (
-          <img className="Avatar" src={avatar_uri.toString()} width={avatarSize} height={avatarSize} />
+          <img className='Avatar' src={avatar_uri.toString()} width={avatarSize} height={avatarSize} />
         ) : (
-          <IdentityIcon className="Avatar" value={accountId} size={avatarSize} />
+          <IdentityIcon className='Avatar' value={accountId} size={avatarSize} />
         )
         }
         <div className='Content'>

+ 0 - 58
pioneer/packages/joy-members/src/index.css

@@ -1,58 +0,0 @@
-.ProfilePreviews,
-.FullProfile {
-  .item {
-    .image {
-      padding: 0 !important;
-    }
-    .description {
-      font-size: 1rem;
-    }
-  }
-}
-.ProfilePreviews {
-  &.ui.list>.item:first-child {
-    padding-top: .75rem;
-  }
-  &.ui.list>.item:last-child {
-    padding-bottom: .75rem;
-  }
-  .MyProfile {
-    background-color: #FFF8E1;
-  }
-}
-.ProfileDetails {
-  padding-left: 1rem !important;
-  .handle {
-    margin-right: 1rem;
-    .button {
-      padding: .5rem .75rem;
-    }
-  }
-}
-.ProfileDetailsTable {
-  font-size: 1rem !important;
-  tr td:first-child {
-    width: 1%;
-    white-space: nowrap;
-  }
-}
-
-.JoyMemberPreview {
-  margin-right: .5rem;
-  .PrefixLabel {
-    margin-right: .5rem;
-  }
-  .Avatar {
-    margin-right: .5rem;
-    border-radius: 100%;
-  }
-  .Content {
-    .Username {
-      font-weight: bold;
-    }
-    .Details {
-      font-weight: 100;
-      opacity: .75;
-    }
-  }
-}

+ 16 - 11
pioneer/packages/joy-members/src/index.tsx

@@ -5,10 +5,9 @@ import { Route, Switch } from 'react-router';
 
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
-import { withCalls, withMulti } from '@polkadot/react-api/with';
-import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
-
-import './index.css';
+import { withCalls, withMulti } from '@polkadot/react-api/hoc';
+import Tabs from '@polkadot/react-components/Tabs';
+import { TabItem } from '@polkadot/react-components/Tabs/types';
 
 import { queryMembershipToProp } from './utils';
 import translate from './translate';
@@ -16,10 +15,15 @@ import Dashboard from './Dashboard';
 import List from './List';
 import DetailsByHandle from './DetailsByHandle';
 import EditForm from './EditForm';
-import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
+import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts';
 import { FIRST_MEMBER_ID } from './constants';
 import { RouteComponentProps } from 'react-router-dom';
 
+import styled from 'styled-components';
+import style from './style';
+
+const MembersMain = styled.main`${style}`;
+
 // define out internal types
 type Props = AppProps & ApiProps & I18nProps & MyAccountProps & {
   nextMemberId?: BN;
@@ -32,8 +36,8 @@ class App extends React.PureComponent<Props> {
     return [
       {
         name: 'list',
-        text: t('All members') + ` (${memberCount})`,
-        forcedExact: false
+        text: t('All members') + ` (${memberCount?.toString() || '-'})`,
+        forceMatchParams: true
       },
       {
         name: 'edit',
@@ -48,6 +52,7 @@ class App extends React.PureComponent<Props> {
 
   private renderList (routeProps: RouteComponentProps) {
     const { nextMemberId, ...otherProps } = this.props;
+
     return nextMemberId
       ? <List firstMemberId={FIRST_MEMBER_ID} membersCreated={nextMemberId} {...otherProps} {...routeProps}/>
       : <em>Loading...</em>;
@@ -58,18 +63,18 @@ class App extends React.PureComponent<Props> {
     const tabs = this.buildTabs();
 
     return (
-      <main className='members--App'>
+      <MembersMain className='members--App'>
         <header>
           <Tabs basePath={basePath} items={tabs} />
         </header>
         <Switch>
           <Route path={`${basePath}/edit`} component={EditForm} />
           <Route path={`${basePath}/dashboard`} component={Dashboard} />
-          <Route path={`${basePath}/list/:page([0-9]+)?`} render={ props => this.renderList(props) } />
+          <Route path={`${basePath}/list/:page([0-9]+)?`} render={ (props) => this.renderList(props) } />
           <Route exact={true} path={`${basePath}/:handle`} component={DetailsByHandle} />
-          <Route render={ props => this.renderList(props) } />
+          <Route render={ (props) => this.renderList(props) } />
         </Switch>
-      </main>
+      </MembersMain>
     );
   }
 }

+ 62 - 0
pioneer/packages/joy-members/src/style.ts

@@ -0,0 +1,62 @@
+import { css } from 'styled-components';
+
+export default css`
+  .ProfilePreviews,
+  .FullProfile {
+    .item {
+      .image {
+        padding: 0 !important;
+      }
+      .description {
+        font-size: 1rem;
+      }
+    }
+  }
+  .ProfilePreviews {
+    &.ui.list>.item:first-child {
+      padding-top: .75rem;
+    }
+    &.ui.list>.item:last-child {
+      padding-bottom: .75rem;
+    }
+    .MyProfile {
+      background-color: #FFF8E1;
+    }
+  }
+  .ProfileDetails {
+    padding-left: 1rem !important;
+    .handle {
+      margin-right: 1rem;
+      .button {
+        padding: .5rem .75rem;
+      }
+    }
+  }
+  .ProfileDetailsTable {
+    font-size: 1rem !important;
+    tr td:first-child {
+      width: 1%;
+      white-space: nowrap;
+    }
+  }
+
+  .JoyMemberPreview {
+    margin-right: .5rem;
+    .PrefixLabel {
+      margin-right: .5rem;
+    }
+    .Avatar {
+      margin-right: .5rem;
+      border-radius: 100%;
+    }
+    .Content {
+      .Username {
+        font-weight: bold;
+      }
+      .Details {
+        font-weight: 100;
+        opacity: .75;
+      }
+    }
+  }
+`;

+ 3 - 2
pioneer/packages/joy-members/src/utils.ts

@@ -1,5 +1,6 @@
-import { queryToProp } from '@polkadot/joy-utils/index';
-import { Options as QueryOptions } from '@polkadot/react-api/with/types';
+// TODO: Move to joy-utils?
+import { queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { Options as QueryOptions } from '@polkadot/react-api/hoc/types';
 
 export const queryMembershipToProp = (storageItem: string, paramNameOrOpts?: string | QueryOptions) => {
   return queryToProp(`query.members.${storageItem}`, paramNameOrOpts);

+ 0 - 0
pioneer/packages/joy-pages/.skip-build


+ 3 - 3
pioneer/packages/joy-pages/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@polkadot/joy-utils": "^0.1.1"
   }
 }

+ 4 - 2
pioneer/packages/joy-pages/src/index.tsx

@@ -4,9 +4,11 @@ import Page from './Page';
 import ToS_md from './md/ToS.md';
 
 import Privacy_md from './md/Privacy.md';
+
 export function ToS () {
-  return <Page md={ToS_md} />;
+  return <Page md={ToS_md as string} />;
 }
+
 export function Privacy () {
-  return <Page md={Privacy_md} />;
+  return <Page md={Privacy_md as string} />;
 }

+ 0 - 14
pioneer/packages/joy-utils-old/src/functions/misc.ts

@@ -1,14 +0,0 @@
-import { Bytes } from '@polkadot/types/primitive';
-
-export function includeKeys<T extends { [k: string]: any }> (obj: T, ...allowedKeys: string[]) {
-  return Object.keys(obj).filter(objKey => {
-    return allowedKeys.reduce(
-      (hasAllowed: boolean, allowedKey: string) => hasAllowed || objKey.includes(allowedKey),
-      false
-    );
-  });
-}
-
-export function bytesToString (bytes: Bytes) {
-  return Buffer.from(bytes.toString().substr(2), 'hex').toString();
-}

+ 0 - 1
pioneer/packages/joy-utils-old/src/react/components/index.tsx

@@ -1 +0,0 @@
-export { default as PromiseComponent } from './PromiseComponent';

+ 0 - 1
pioneer/packages/joy-utils-old/src/react/context/index.tsx

@@ -1 +0,0 @@
-export { TransportContext, TransportProvider } from './transport';

+ 0 - 0
pioneer/packages/joy-utils-old/README.md → pioneer/packages/joy-utils/README.md


+ 3 - 3
pioneer/packages/joy-utils-old/package.json → pioneer/packages/joy-utils/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@types/query-string": "^6.2.0",
     "@types/uuid": "^3.4.4",
     "@types/yup": "^0.26.10",

+ 0 - 0
pioneer/packages/joy-utils-old/src/functions/date.ts → pioneer/packages/joy-utils/src/functions/date.ts


+ 2 - 2
pioneer/packages/joy-utils-old/src/functions/format.ts → pioneer/packages/joy-utils/src/functions/format.ts

@@ -16,7 +16,7 @@ export const formatReward = (
     : next_payment_at_block;
 
   return (
-    `${formatBalance(amount)}${interval.isSome ? ` / ${interval.unwrap()} block(s)` : ''}` +
-    ((showNextPaymentBlock && nextPaymentBlock) ? ` (Next payment: #${nextPaymentBlock})` : '')
+    `${formatBalance(amount)}${interval.isSome ? ` / ${interval.unwrap().toString()} block(s)` : ''}` +
+    ((showNextPaymentBlock && nextPaymentBlock) ? ` (Next payment: #${nextPaymentBlock.toString()})` : '')
   );
 };

+ 167 - 0
pioneer/packages/joy-utils/src/functions/misc.ts

@@ -0,0 +1,167 @@
+import { Bytes } from '@polkadot/types/primitive';
+import BN from 'bn.js';
+import keyring from '@polkadot/ui-keyring';
+import { ElectionStake, Backer } from '@joystream/types/council';
+import { Options as QueryOptions } from '@polkadot/react-api/hoc/types';
+import queryString from 'query-string';
+import { SubmittableResult } from '@polkadot/api';
+import { Codec } from '@polkadot/types/types';
+
+export const ZERO = new BN(0);
+
+export function bnToStr (bn?: BN, dflt = ''): string {
+  return bn ? bn.toString() : dflt;
+}
+
+export const notDefined = (x: any): boolean =>
+  x === null || typeof x === 'undefined';
+
+export const isDefined = (x: any): boolean =>
+  !notDefined(x);
+
+export const isDef = isDefined;
+
+export const notDef = notDefined;
+
+export const isObj = (x: any): boolean =>
+  x !== null && typeof x === 'object';
+
+export const isStr = (x: any): boolean =>
+  typeof x === 'string';
+
+export const isNum = (x: any): boolean =>
+  typeof x === 'number';
+
+export const isEmptyStr = (x: any): boolean =>
+  notDefined(x) || (isStr(x) && (x as string).trim().length === 0);
+
+export const nonEmptyStr = (x?: any) =>
+  isStr(x) && (x as string).trim().length > 0;
+
+export const parseNumStr = (num: string): number | undefined => {
+  try {
+    return parseInt(num, undefined);
+  } catch (err) {
+    return undefined;
+  }
+};
+
+export const nonEmptyArr = (x: any): boolean =>
+  Array.isArray(x) && x.length > 0;
+
+export const isEmptyArr = (x: any): boolean =>
+  !nonEmptyArr(x);
+
+export function findNameByAddress (address: string): string | undefined {
+  let keyring_address;
+
+  try {
+    keyring_address = keyring.getAccount(address);
+  } catch (error) {
+    try {
+      keyring_address = keyring.getAddress(address);
+    } catch (error) {
+      // do nothing
+    }
+  }
+
+  return keyring_address ? keyring_address.meta.name : undefined;
+}
+
+export function isKnownAddress (address: string): boolean {
+  return isDefined(findNameByAddress(address));
+}
+
+export function calcTotalStake (stakes: ElectionStake | ElectionStake[] | undefined): BN {
+  if (typeof stakes === 'undefined') {
+    return ZERO;
+  }
+
+  const total = (stake: ElectionStake) => stake.new.add(stake.transferred);
+
+  try {
+    if (Array.isArray(stakes)) {
+      return stakes.reduce((accum, stake) => {
+        return accum.add(total(stake));
+      }, ZERO);
+    } else {
+      return total(stakes);
+    }
+  } catch (err) {
+    console.log('Failed to calculate a total stake', stakes, err);
+
+    return ZERO;
+  }
+}
+
+export function calcBackersStake (backers: Backer[]): BN {
+  return backers.map((b) => b.stake).reduce((accum, stake) => {
+    return accum.add(stake);
+  }, ZERO);
+}
+
+/** Example of apiQuery: 'query.councilElection.round' */
+export function queryToProp (
+  apiQuery: string,
+  paramNameOrOpts?: string | QueryOptions
+): [string, QueryOptions] {
+  let paramName: string | undefined;
+  let propName: string | undefined;
+
+  if (typeof paramNameOrOpts === 'string') {
+    paramName = paramNameOrOpts;
+  } else if (paramNameOrOpts) {
+    paramName = paramNameOrOpts.paramName;
+    propName = paramNameOrOpts.propName;
+  }
+
+  // If prop name is still undefined, derive it from the name of storage item:
+  if (!propName) {
+    propName = apiQuery.split('.').slice(-1)[0];
+  }
+
+  return [apiQuery, { paramName, propName }];
+}
+
+export function getUrlParam (location: Location, paramName: string, deflt: string | null = null): string | null {
+  const params = queryString.parse(location.search);
+
+  return params[paramName] ? params[paramName] as string : deflt;
+}
+
+export function filterSubstrateEventsAndExtractData (txResult: SubmittableResult, eventName: string): Codec[][] {
+  const res: Codec[][] = [];
+
+  txResult.events.forEach((event) => {
+    const { event: { method, data } } = event;
+
+    if (method === eventName) {
+      res.push(data.toArray());
+    }
+  });
+
+  return res;
+}
+
+export function findFirstParamOfSubstrateEvent<T extends Codec> (txResult: SubmittableResult, eventName: string): T | undefined {
+  const data = filterSubstrateEventsAndExtractData(txResult, eventName);
+
+  if (data && data.length) {
+    return data[0][0] as T;
+  }
+
+  return undefined;
+}
+
+export function includeKeys<T extends { [k: string]: any }> (obj: T, ...allowedKeys: string[]) {
+  return Object.keys(obj).filter((objKey) => {
+    return allowedKeys.reduce(
+      (hasAllowed: boolean, allowedKey: string) => hasAllowed || objKey.includes(allowedKey),
+      false
+    );
+  });
+}
+
+export function bytesToString (bytes: Bytes) {
+  return Buffer.from(bytes.toString().substr(2), 'hex').toString();
+}

+ 1 - 1
pioneer/packages/joy-utils-old/src/FlexCenter.tsx → pioneer/packages/joy-utils/src/react/components/FlexCenter.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
 
-export function FlexCenter (props: React.PropsWithChildren<{}>) {
+export function FlexCenter (props: React.PropsWithChildren<unknown>) {
   return <div className='FlexCenter'>{props.children}</div>;
 }

+ 4 - 1
pioneer/packages/joy-utils-old/src/MutedText.tsx → pioneer/packages/joy-utils/src/react/components/MutedText.tsx

@@ -8,15 +8,18 @@ type Props = React.PropsWithChildren<{
 
 function getClassNames (props: Props): string {
   const { smaller = false, className } = props;
-  return `grey text ${smaller ? 'smaller' : ''} ${className}`;
+
+  return `grey text ${smaller ? 'smaller' : ''} ${className || ''}`;
 }
 
 export const MutedSpan = (props: Props) => {
   const { style, children } = props;
+
   return <span className={getClassNames(props)} style={style}>{children}</span>;
 };
 
 export const MutedDiv = (props: Props) => {
   const { style, children } = props;
+
   return <div className={getClassNames(props)} style={style}>{children}</div>;
 };

+ 3 - 0
pioneer/packages/joy-utils-old/src/Section.tsx → pioneer/packages/joy-utils/src/react/components/Section.tsx

@@ -46,6 +46,7 @@ type Props = BareProps & {
 export default class Section extends React.PureComponent<Props> {
   render () {
     let { className, children, pagination } = this.props;
+
     className = (className || '') + ' JoySection';
 
     return (
@@ -62,10 +63,12 @@ export default class Section extends React.PureComponent<Props> {
 
   private renderTitle = () => {
     const { title, level = 2, pagination } = this.props;
+
     if (!title) return null;
 
     const className = 'JoySection-title';
     const style = pagination ? { margin: '0' } : {};
+
     return React.createElement(
       `h${level}`,
       { className, style },

+ 46 - 50
pioneer/packages/joy-utils-old/src/TxButton.tsx → pioneer/packages/joy-utils/src/react/components/TxButton.tsx

@@ -5,11 +5,8 @@ import { Button } from '@polkadot/react-components/index';
 import { QueueConsumer } from '@polkadot/react-components/Status/Context';
 import { withApi } from '@polkadot/react-api/index';
 import { assert } from '@polkadot/util';
-import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
-import { useTransportContext } from '@polkadot/joy-media/TransportContext';
-import { MockTransport } from '@polkadot/joy-media/transport.mock';
-import { Button$Sizes } from '@polkadot/react-components/Button/types';
-import { SemanticShorthandItem, IconProps } from 'semantic-ui-react';
+import { withMyAccount, MyAccountProps } from '../hocs/accounts';
+import { IconName } from '@fortawesome/fontawesome-svg-core';
 
 type InjectedProps = {
   queueExtrinsic: QueueTxExtrinsicAdd;
@@ -20,9 +17,7 @@ export type OnTxButtonClick = (sendTx: () => void) => void;
 type BasicButtonProps = {
   accountId?: string;
   type?: 'submit' | 'button';
-  size?: Button$Sizes;
   isBasic?: boolean;
-  isPrimary?: boolean;
   isDisabled?: boolean;
   label?: React.ReactNode;
   params: Array<any>;
@@ -32,7 +27,7 @@ type BasicButtonProps = {
   style?: Record<string, string | number>;
   children?: React.ReactNode;
   compact?: boolean;
-  icon?: boolean | SemanticShorthandItem<IconProps>;
+  icon?: IconName;
 
   onClick?: OnTxButtonClick;
   txFailedCb?: TxFailedCallback;
@@ -45,15 +40,14 @@ type PropsWithApi = BareProps & ApiProps & MyAccountProps & PartialQueueTxExtrin
 
 class TxButtonInner extends React.PureComponent<PropsWithApi & InjectedProps> {
   render () {
-    const { myAddress, accountId, isPrimary = true, isDisabled, icon = '', onClick } = this.props;
+    const { myAddress, accountId, isDisabled, icon = 'check', onClick } = this.props;
     const origin = accountId || myAddress;
 
     return (
       <Button
         {...this.props}
         isDisabled={isDisabled || !origin}
-        isPrimary={isPrimary}
-        icon={icon as string}
+        icon={icon}
         onClick={() => {
           if (onClick) onClick(this.send);
           else this.send();
@@ -74,7 +68,7 @@ class TxButtonInner extends React.PureComponent<PropsWithApi & InjectedProps> {
 
     queueExtrinsic({
       accountId: origin,
-      extrinsic: api.tx[section][method](...params) as any, // ???
+      extrinsic: api.tx[section][method](...params),
       txFailedCb,
       txSuccessCb,
       txStartCb,
@@ -98,41 +92,43 @@ class TxButton extends React.PureComponent<PropsWithApi> {
   }
 }
 
-const SubstrateTxButton = withApi(withMyAccount(TxButton));
-
-const mockSendTx = () => {
-  const msg = 'Cannot send a Substrate tx in a mock mode';
-  if (typeof window !== 'undefined') {
-    window.alert(`WARN: ${msg}`);
-  } else if (typeof console.warn === 'function') {
-    console.warn(msg);
-  } else {
-    console.log(`WARN: ${msg}`);
-  }
-};
-
-function MockTxButton (props: BasicButtonProps) {
-  const { isPrimary = true, icon = '', onClick } = props;
-
-  return (
-    <Button
-      {...props}
-      isPrimary={isPrimary}
-      icon={icon as string}
-      onClick={() => {
-        if (onClick) onClick(mockSendTx);
-        else mockSendTx();
-      }}
-    />
-  );
-}
-
-function ResolvedButton (props: BasicButtonProps) {
-  const isMock = useTransportContext() instanceof MockTransport;
-
-  return isMock
-    ? <MockTxButton {...props} />
-    : <SubstrateTxButton {...props} />;
-}
-
-export default ResolvedButton;
+export default withApi(withMyAccount(TxButton));
+
+// const SubstrateTxButton = withApi(withMyAccount(TxButton));
+
+// const mockSendTx = () => {
+//   const msg = 'Cannot send a Substrate tx in a mock mode';
+//   if (typeof window !== 'undefined') {
+//     window.alert(`WARN: ${msg}`);
+//   } else if (typeof console.warn === 'function') {
+//     console.warn(msg);
+//   } else {
+//     console.log(`WARN: ${msg}`);
+//   }
+// };
+
+// function MockTxButton (props: BasicButtonProps) {
+//   const { isPrimary = true, icon = '', onClick } = props;
+
+//   return (
+//     <Button
+//       {...props}
+//       isPrimary={isPrimary}
+//       icon={icon as string}
+//       onClick={() => {
+//         if (onClick) onClick(mockSendTx);
+//         else mockSendTx();
+//       }}
+//     />
+//   );
+// }
+
+// function ResolvedButton (props: BasicButtonProps) {
+//   const isMock = useTransportContext() instanceof MockTransport;
+
+//   return isMock
+//     ? <MockTxButton {...props} />
+//     : <SubstrateTxButton {...props} />;
+// }
+
+// export default ResolvedButton;

+ 5 - 5
pioneer/packages/joy-utils-old/src/forms.tsx → pioneer/packages/joy-utils/src/react/components/forms.tsx

@@ -1,7 +1,6 @@
 import React from 'react';
 import { Field, FormikErrors, FormikTouched } from 'formik';
-
-import { nonEmptyStr } from '@polkadot/joy-utils/index';
+import { nonEmptyStr } from '../../functions/misc';
 import { Popup, Icon } from 'semantic-ui-react';
 
 export type LabelledProps<FormValues> = {
@@ -38,9 +37,9 @@ export function LabelledField<FormValues> () {
       const renderLabel = () =>
         nonEmptyStr(label)
           ? <>
-              {required && <b style={{ color: 'red' }} title='This field is required'>* </b>}
-              {label}
-            </>
+            {required && <b style={{ color: 'red' }} title='This field is required'>* </b>}
+            {label}
+          </>
           : null;
 
       return (label || invisibleLabel)
@@ -57,6 +56,7 @@ export function LabelledField<FormValues> () {
           {fieldWithError}
         </div>;
     };
+
   return LabelledFieldInner;
 }
 

+ 4 - 0
pioneer/packages/joy-utils/src/react/components/index.tsx

@@ -0,0 +1,4 @@
+export { default as Section } from './Section';
+export { default as TxButton } from './TxButton';
+export { MutedSpan, MutedDiv } from './MutedText';
+export { FlexCenter } from './FlexCenter';

+ 30 - 10
pioneer/packages/joy-utils-old/src/MyAccountContext.tsx → pioneer/packages/joy-utils/src/react/context/account.tsx

@@ -1,11 +1,14 @@
-import React, { useReducer, createContext, useContext, useEffect } from 'react';
+import React, { useReducer, createContext, useEffect } from 'react';
 import store from 'store';
 
-export const MY_ADDRESS = 'joy.myAddress';
+export const ACCOUNT_CHANGED_EVENT_NAME = 'account-changed';
+export const MY_ADDRESS_STORAGE_KEY = 'joy.myAddress';
 
 function readMyAddress (): string | undefined {
-  const myAddress: string | undefined = store.get(MY_ADDRESS);
+  const myAddress = store.get(MY_ADDRESS_STORAGE_KEY) as string | undefined;
+
   console.log('Read my address from the local storage:', myAddress);
+
   return myAddress;
 }
 
@@ -22,7 +25,8 @@ type MyAccountAction = {
 function reducer (state: MyAccountState, action: MyAccountAction): MyAccountState {
   function forget () {
     console.log('Forget my address');
-    store.remove(MY_ADDRESS);
+    store.remove(MY_ADDRESS_STORAGE_KEY);
+
     return { ...state, address: undefined };
   }
 
@@ -32,29 +36,35 @@ function reducer (state: MyAccountState, action: MyAccountAction): MyAccountStat
     case 'reload': {
       address = readMyAddress();
       console.log('Reload my address:', address);
+
       return { ...state, address, inited: true };
     }
 
     case 'set': {
       address = action.address;
+
       if (address !== state.address) {
         if (address) {
           console.log('Set my new address:', address);
-          store.set(MY_ADDRESS, address);
+          store.set(MY_ADDRESS_STORAGE_KEY, address);
+
           return { ...state, address, inited: true };
         } else {
           return forget();
         }
       }
+
       return state;
     }
 
     case 'forget': {
       address = action.address;
       const isMyAddress = address && address === readMyAddress();
+
       if (!address || isMyAddress) {
         return forget();
       }
+
       return state;
     }
 
@@ -88,9 +98,23 @@ const contextStub: MyAccountContextProps = {
 
 export const MyAccountContext = createContext<MyAccountContextProps>(contextStub);
 
-export function MyAccountProvider (props: React.PropsWithChildren<{}>) {
+export function MyAccountProvider (props: React.PropsWithChildren<unknown>) {
   const [state, dispatch] = useReducer(reducer, initialState);
 
+  const handleAccountChangeEvent = (e: Event) => {
+    const { detail: address } = e as CustomEvent<string>;
+
+    dispatch({ type: 'set', address });
+  };
+
+  useEffect(() => {
+    window.addEventListener(ACCOUNT_CHANGED_EVENT_NAME, handleAccountChangeEvent);
+
+    return () => {
+      window.removeEventListener(ACCOUNT_CHANGED_EVENT_NAME, handleAccountChangeEvent);
+    };
+  });
+
   useEffect(() => {
     if (!state.inited) {
       dispatch({ type: 'reload' });
@@ -110,7 +134,3 @@ export function MyAccountProvider (props: React.PropsWithChildren<{}>) {
     </MyAccountContext.Provider>
   );
 }
-
-export function useMyAccount () {
-  return useContext(MyAccountContext);
-}

+ 2 - 0
pioneer/packages/joy-utils/src/react/context/index.tsx

@@ -0,0 +1,2 @@
+export { MyAccountContext, MyAccountProvider } from './account';
+export { MyMembershipContext, MyMembershipProvider } from './membership';

+ 2 - 6
pioneer/packages/joy-utils-old/src/MyMembershipContext.tsx → pioneer/packages/joy-utils/src/react/context/membership.tsx

@@ -1,5 +1,5 @@
-import React, { createContext, useContext } from 'react';
-import { MyAccountProps, withMyAccount } from './MyAccount';
+import React, { createContext } from 'react';
+import { MyAccountProps, withMyAccount } from '../hocs/accounts';
 
 export const MyMembershipContext = createContext<MyAccountProps>({});
 
@@ -12,7 +12,3 @@ function InnerMyMembershipProvider (props: React.PropsWithChildren<MyAccountProp
 }
 
 export const MyMembershipProvider = withMyAccount(InnerMyMembershipProvider);
-
-export function useMyMembership () {
-  return useContext(MyMembershipContext);
-}

+ 0 - 0
pioneer/packages/joy-utils-old/src/react/helpers/index.ts → pioneer/packages/joy-utils/src/react/helpers/index.ts


+ 124 - 0
pioneer/packages/joy-utils/src/react/hocs/accounts.tsx

@@ -0,0 +1,124 @@
+import React, { useContext } from 'react';
+
+import { AccountId } from '@polkadot/types/interfaces';
+import { Vec, Option } from '@polkadot/types';
+import accountObservable from '@polkadot/ui-keyring/observable/accounts';
+import { withCalls, withMulti, withObservable, ApiContext } from '@polkadot/react-api/index';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+
+import { MemberId, Membership } from '@joystream/types/members';
+import { LeadId } from '@joystream/types/content-working-group';
+
+import { queryMembershipToProp } from '@polkadot/joy-members/utils';
+import useMyAccount from '../hooks/useMyAccount';
+import { componentName } from '../helpers';
+
+export type MyAddressProps = {
+  myAddress?: string;
+};
+
+export type MyAccountProps = MyAddressProps & {
+  myAccountId?: AccountId;
+  myMemberId?: MemberId;
+  memberIdsByRootAccountId?: Vec<MemberId>;
+  memberIdsByControllerAccountId?: Vec<MemberId>;
+  myMemberIdChecked?: boolean;
+  iAmMember?: boolean;
+  myMembership?: Membership | null;
+
+  // Content Working Group
+  curatorEntries?: any; // entire linked_map: CuratorId => Curator
+  isLeadSet?: Option<LeadId>;
+  contentLeadId?: LeadId;
+  contentLeadEntry?: any; // linked_map value
+
+  curationActor?: any;
+  allAccounts?: SubjectInfo;
+};
+
+function withMyAddress<P extends MyAccountProps> (Component: React.ComponentType<P>) {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const {
+      state: { address }
+    } = useMyAccount();
+    const { api } = useContext(ApiContext);
+    const myAccountId = (address && api.isReady)
+      ? api.createType('AccountId', address)
+      : undefined;
+
+    return <Component myAddress={address} myAccountId={myAccountId} {...props} />;
+  };
+
+  ResultComponent.displayName = `withMyAddress(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+const withMyMemberIds = withCalls<MyAccountProps>(
+  queryMembershipToProp('memberIdsByRootAccountId', 'myAddress'),
+  queryMembershipToProp('memberIdsByControllerAccountId', 'myAddress')
+);
+
+function withMyMembership<P extends MyAccountProps> (Component: React.ComponentType<P>) {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const { memberIdsByRootAccountId, memberIdsByControllerAccountId } = props;
+
+    const myMemberIdChecked = memberIdsByRootAccountId && memberIdsByControllerAccountId;
+
+    let myMemberId: MemberId | undefined;
+
+    if (memberIdsByRootAccountId && memberIdsByControllerAccountId) {
+      const [memberIdByAccount] = memberIdsByRootAccountId.toArray().concat(memberIdsByControllerAccountId.toArray());
+
+      myMemberId = memberIdByAccount;
+    }
+
+    const iAmMember = myMemberId !== undefined;
+
+    const newProps = {
+      myMemberIdChecked,
+      myMemberId,
+      iAmMember
+    };
+
+    return <Component {...props} {...newProps} />;
+  };
+
+  ResultComponent.displayName = `withMyMembership(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+function resolveMyProfile<P extends { myMembership?: Membership | null }> (Component: React.ComponentType<P>) {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    let { myMembership } = props;
+
+    myMembership = (!myMembership || myMembership.handle.isEmpty) ? null : myMembership;
+
+    return <Component {...props} myMembership={ myMembership } />;
+  };
+
+  ResultComponent.displayName = `resolveMyProfile(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+const withMyProfileCall = withCalls<MyAccountProps>(queryMembershipToProp('membershipById', {
+  paramName: 'myMemberId',
+  propName: 'myMembership'
+}));
+
+const withMyProfile = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
+  withMulti(Component, withMyProfileCall, resolveMyProfile);
+
+export const withMyAccount = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
+  withMulti(
+    Component,
+    withObservable(accountObservable.subject, { propName: 'allAccounts' }),
+    withMyAddress,
+    withMyMemberIds,
+    withMyMembership,
+    withMyProfile
+    // withContentWorkingGroup,
+    // withCurationActor
+  );

+ 104 - 0
pioneer/packages/joy-utils/src/react/hocs/guards.tsx

@@ -0,0 +1,104 @@
+import React from 'react';
+import { Message } from 'semantic-ui-react';
+import { Link } from 'react-router-dom';
+
+import { AccountId } from '@polkadot/types/interfaces';
+import { Vec, Option } from '@polkadot/types';
+import { withMulti } from '@polkadot/react-api/index';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+
+import { MemberId, Membership } from '@joystream/types/members';
+import { LeadId } from '@joystream/types/content-working-group';
+import { useMyMembership } from '../hooks';
+import { componentName } from '../helpers';
+import { withMyAccount } from './accounts';
+
+export type MyAddressProps = {
+  myAddress?: string;
+};
+
+export type MyAccountProps = MyAddressProps & {
+  myAccountId?: AccountId;
+  myMemberId?: MemberId;
+  memberIdsByRootAccountId?: Vec<MemberId>;
+  memberIdsByControllerAccountId?: Vec<MemberId>;
+  myMemberIdChecked?: boolean;
+  iAmMember?: boolean;
+  myMembership?: Membership | null;
+
+  // Content Working Group
+  curatorEntries?: any; // entire linked_map: CuratorId => Curator
+  isLeadSet?: Option<LeadId>;
+  contentLeadId?: LeadId;
+  contentLeadEntry?: any; // linked_map value
+
+  curationActor?: any;
+  allAccounts?: SubjectInfo;
+};
+
+export function MembershipRequired<P extends Record<string, unknown>> (Component: React.ComponentType<P>): React.ComponentType<P> {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const { myMemberIdChecked, iAmMember } = useMyMembership();
+
+    if (!myMemberIdChecked) {
+      return <em>Loading...</em>;
+    } else if (iAmMember) {
+      return <Component {...props} />;
+    }
+
+    return (
+      <Message warning className='JoyMainStatus'>
+        <Message.Header>Only members can access this functionality.</Message.Header>
+        <div style={{ marginTop: '1rem' }}>
+          <Link to={'/members/edit'} className='ui button orange'>
+            Register here
+          </Link>
+          <span style={{ margin: '0 .5rem' }}> or </span>
+          <Link to={'/accounts'} className='ui button'>
+            Change key
+          </Link>
+        </div>
+      </Message>
+    );
+  };
+
+  ResultComponent.displayName = `MembershipRequired(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+export function AccountRequired<P extends Record<string, unknown>> (Component: React.ComponentType<P>): React.ComponentType<P> {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const { allAccounts } = useMyMembership();
+
+    if (allAccounts && !Object.keys(allAccounts).length) {
+      return (
+        <Message warning className='JoyMainStatus'>
+          <Message.Header>Please create a key to get started.</Message.Header>
+          <div style={{ marginTop: '1rem' }}>
+            <Link to={'/accounts'} className='ui button orange'>
+              Create key
+            </Link>
+          </div>
+        </Message>
+      );
+    }
+
+    return <Component {...props} />;
+  };
+
+  ResultComponent.displayName = `AccountRequired(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+// TODO: We could probably use withAccountRequired, which wouldn't pass any addiotional props, just like withMembershipRequired.
+// Just need to make sure those passed props are not used in the extended components (they probably aren't).
+export const withOnlyAccounts = <P extends MyAccountProps>(Component: React.ComponentType<P>): React.ComponentType<P> =>
+  withMulti(Component, withMyAccount, AccountRequired);
+
+export const withMembershipRequired = <P extends Record<string, unknown>> (Component: React.ComponentType<P>): React.ComponentType<P> =>
+  withMulti(Component, AccountRequired, MembershipRequired);
+
+export const withOnlyMembers = <P extends MyAccountProps>(Component: React.ComponentType<P>): React.ComponentType<P> =>
+  withMulti(Component, withMyAccount, withMembershipRequired);

+ 2 - 0
pioneer/packages/joy-utils/src/react/hooks/index.ts

@@ -0,0 +1,2 @@
+export { default as useMyAccount } from './useMyAccount';
+export { default as useMyMembership } from './useMyMembership';

+ 6 - 0
pioneer/packages/joy-utils/src/react/hooks/useMyAccount.tsx

@@ -0,0 +1,6 @@
+import { useContext } from 'react';
+import { MyAccountContext } from '../context/account';
+
+export default function useMyAccount () {
+  return useContext(MyAccountContext);
+}

+ 6 - 0
pioneer/packages/joy-utils/src/react/hooks/useMyMembership.tsx

@@ -0,0 +1,6 @@
+import { useContext } from 'react';
+import { MyMembershipContext } from '../context';
+
+export default function useMyMembership () {
+  return useContext(MyMembershipContext);
+}

+ 0 - 28
pioneer/packages/old-apps/apps-routing/src/joy-pages.ts

@@ -1,28 +0,0 @@
-import { Routes } from './types';
-
-import { ToS, Privacy } from '@polkadot/joy-pages/index';
-
-export default ([
-  {
-    Component: ToS,
-    display: {
-      isHidden: true
-    },
-    i18n: {
-      defaultValue: 'Terms of Service'
-    },
-    icon: 'file outline',
-    name: 'pages/tos'
-  },
-  {
-    Component: Privacy,
-    display: {
-      isHidden: true
-    },
-    i18n: {
-      defaultValue: 'Privacy Policy'
-    },
-    icon: 'file outline',
-    name: 'pages/privacy'
-  }
-] as Routes);

+ 0 - 17
pioneer/packages/old-apps/apps/src/TopBar.css

@@ -1,17 +0,0 @@
-.JoyTopBar {
-  padding: .5rem .5rem;
-  background-color: #3f3f3f;
-  border-bottom: 1px solid #d4d4d5;
-  text-align: right;
-  margin: 0 -2rem;
-
-  &.NoMyAddress {
-    background-color: #ffeb83;
-    color: #000;
-    text-align: center;
-  }
-
-  .ui--InputAddress {
-    display: inline-block;
-  }
-}

+ 0 - 47
pioneer/packages/old-apps/apps/src/TopBar.tsx

@@ -1,47 +0,0 @@
-import React from 'react';
-// import { Link } from 'react-router-dom';
-import { I18nProps } from '@polkadot/react-components/types';
-import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
-import { InputAddress } from '@polkadot/react-components';
-import { Available } from '@polkadot/react-query';
-import translate from './translate';
-import './TopBar.css';
-
-type Props = I18nProps & {};
-
-function renderAddress (address: string) {
-  const balance = <span className="label">Balance: </span>;
-
-  return (
-    <div className="JoyTopBar">
-      <InputAddress
-        defaultValue={address}
-        help="My current key that signs transactions"
-        label="My key"
-        labelExtra={<Available label={balance} params={address} />}
-        type="account"
-      />
-    </div>
-  );
-}
-
-// function renderNoAddress() {
-//   return (
-//     <div className="JoyTopBar NoMyAddress">
-//       <i className="warning sign icon"></i>
-//       <span style={{ marginRight: '1rem' }}>You need to create a key if you want to use all features.</span>
-//       <Link className="ui small button orange" to="/accounts">
-//         Create key
-//       </Link>
-//     </div>
-//   );
-// }
-
-function Component (_props: Props) {
-  const {
-    state: { address }
-  } = useMyAccount();
-  return address ? renderAddress(address) : null;
-}
-
-export default translate(Component);

+ 0 - 11
pioneer/packages/old-apps/react-components/src/styles/old-theme.ts-unused

@@ -1,11 +0,0 @@
-/* Copyright 2017-2019 @polkadot/react-components authors & contributors
-/* This software may be modified and distributed under the terms
-/* of the Apache-2.0 license. See the LICENSE file for details. */
-
-import theme from 'styled-theming';
-
-export const primaryColor = theme('theme', {
-  substrate: '#DB2828',
-  polkadot: '#E6007A',
-  default: 'darkorange'
-});

+ 1 - 1
pioneer/packages/page-staking/src/Targets/Summary.tsx

@@ -66,7 +66,7 @@ function Summary ({ lastReward, numNominators, numValidators, totalStaked }: Pro
           </CardSummary>
         )}
       </section>
-      {numValidators && numNominators && (
+      {numValidators !== undefined && numNominators !== undefined && (
         <CardSummary label={`${t<string>('validators')} / ${t<string>('nominators')}`}>
           {numValidators}&nbsp;/&nbsp;{numNominators}
         </CardSummary>

+ 8 - 0
pioneer/packages/react-components/src/InputAddress/index.tsx

@@ -20,6 +20,8 @@ import Dropdown from '../Dropdown';
 import createHeader from './createHeader';
 import createItem from './createItem';
 
+import { ACCOUNT_CHANGED_EVENT_NAME } from '@polkadot/joy-utils/react/context/account';
+
 interface Props {
   className?: string;
   defaultValue?: Uint8Array | string | null;
@@ -115,6 +117,12 @@ function setLastValue (type: KeyringOption$Type = DEFAULT_TYPE, value: string):
 
   options.defaults[type] = value;
   store.set(STORAGE_KEY, options);
+
+  if (type === 'account') {
+    // This lets us update joy-utils account context in order to always be in sync
+    // with options:InputAddress: { defaults: { account } }) from local storage
+    window.dispatchEvent(new CustomEvent<string>(ACCOUNT_CHANGED_EVENT_NAME, { detail: value }));
+  }
 }
 
 class InputAddress extends React.PureComponent<Props, State> {

+ 5 - 2
pioneer/packages/react-components/src/Tabs/Tab.tsx

@@ -16,16 +16,19 @@ interface Props extends TabItem {
   index: number;
   isSequence?: boolean;
   num: number;
+  forceMatchParams?: boolean;
 }
 
-function Tab ({ basePath, className = '', hasParams, index, isExact, isRoot, isSequence, name, num, text }: Props): React.ReactElement<Props> {
+function Tab ({ basePath, className = '', hasParams, index, isExact, isRoot, isSequence, name, num, text, forceMatchParams }: Props): React.ReactElement<Props> {
   const to = isRoot
     ? basePath
     : `${basePath}/${name}`;
 
   // only do exact matching when not the fallback (first position tab),
   // params are problematic for dynamic hidden such as app-accounts
-  const tabIsExact = isExact || !hasParams || (!isSequence && index === 0);
+  const tabIsExact = forceMatchParams
+    ? false
+    : (isExact || !hasParams || (!isSequence && index === 0));
 
   return (
     <NavLink

+ 1 - 0
pioneer/packages/react-components/src/Tabs/types.ts

@@ -11,4 +11,5 @@ export interface TabItem {
   isRoot?: boolean;
   name: string;
   text: React.ReactNode;
+  forceMatchParams?: boolean;
 }

+ 3 - 1
pioneer/packages/react-components/src/styles/index.ts

@@ -10,12 +10,13 @@ import cssMedia from './media';
 import cssRx from './rx';
 import cssSemantic from './semantic';
 import cssTheme from './theme';
+import cssJoystream from './joystream';
 
 interface Props {
   uiHighlight?: string;
 }
 
-const defaultHighlight = '#f19135'; // #999
+const defaultHighlight = '#4038FF'; // #999
 
 const getHighlight = (props: Props): string =>
   (props.uiHighlight || defaultHighlight);
@@ -281,4 +282,5 @@ export default createGlobalStyle<Props>`
   ${cssMedia}
   ${cssRx}
   ${cssComponents}
+  ${cssJoystream}
 `;

+ 16 - 8
pioneer/packages/old-apps/react-components/src/styles/joystream.ts → pioneer/packages/react-components/src/styles/joystream.ts

@@ -94,14 +94,6 @@ export default css`
     }
   }
 
-  .apps--SideBar-logo {
-    max-height: 26px !important;
-    margin: 1rem 1.5rem 2.5rem 0.75rem !important;
-  }
-  .collapsed .apps--SideBar-logo {
-    margin: 1rem 0.75rem 2.5rem 0.5rem !important;
-  }
-
   .JoyForm {
     margin-bottom: 1.5rem;
 
@@ -192,4 +184,20 @@ export default css`
   .text-blue {
     color: #3b83c0;
   }
+
+  /* Overrides */
+  .ui--IdentityIcon {
+    border: none !important;
+  }
+  /* Normalize SideBar icons width */
+  .apps--SideBar-Item-NavLink svg {
+    width: 20px !important;
+  }
+  /* Fix "collapsed" sidebar on mobile */
+  .apps--Wrapper:not(.menu-open) .apps--SideBar-Scroll {
+    padding: 0 !important;
+  }
+  h1 {
+    text-transform: none;
+  }
 `;

+ 1 - 1
pioneer/packages/react-components/src/styles/theme.ts

@@ -10,7 +10,7 @@ export const colorBtnDefault = '#767778';
 export const colorBtnShadow = '#98999a';
 
 /* highlighted buttons, orange */
-export const colorBtnHighlight = '#f19135';
+export const colorBtnHighlight = '#4038FF';
 
 /* primary buttons, blue */
 export const colorBtnPrimary = colorBtnDefault; // '#2e86ab';

+ 7 - 9
pioneer/tsconfig.json

@@ -4,16 +4,13 @@
     "build/**/*",
     "**/build/**/*",
     "packages/old-apps/**",
-    "packages/joy-members/**/*",
     "packages/joy-election/**/*",
     "packages/joy-forum/**/*",
     "packages/joy-help/**/*",
     "packages/joy-media/**/*",
-    "packages/joy-pages/**/*",
     "packages/joy-proposals/**/*",
     "packages/joy-roles/**/*",
     "packages/joy-settings/**/*",
-    "packages/joy-utils/**/*",
     "packages/joy-utils-old/**/*"
   ],
   "compilerOptions": {
@@ -23,6 +20,7 @@
     "resolveJsonModule": true,
     "baseUrl": ".",
     "paths": {
+      "@polkadot/types/augment": [ "../types/src/definitions/augment-types.ts" ],
       // "@joystream/types/": [ "../types/src/" ],
       // "@joystream/types/*": [ "../types/src/*" ],
       // "@polkadot/joy-election/": [ "packages/joy-election/src/" ],
@@ -33,18 +31,18 @@
       // "@polkadot/joy-help/*": [ "packages/joy-help/src/*" ],
       // "@polkadot/joy-media/": [ "packages/joy-media/src/" ],
       // "@polkadot/joy-media/*": [ "packages/joy-media/src/*" ],
-      // "@polkadot/joy-members/": [ "packages/joy-members/src/" ],
-      // "@polkadot/joy-members/*": [ "packages/joy-members/src/*" ],
-      // "@polkadot/joy-pages/": [ "packages/joy-pages/src/" ],
-      // "@polkadot/joy-pages/*": [ "packages/joy-pages/src/*" ],
+      "@polkadot/joy-members/": [ "packages/joy-members/src/" ],
+      "@polkadot/joy-members/*": [ "packages/joy-members/src/*" ],
+      "@polkadot/joy-pages/": [ "packages/joy-pages/src/" ],
+      "@polkadot/joy-pages/*": [ "packages/joy-pages/src/*" ],
       // "@polkadot/joy-proposals/": [ "packages/joy-proposals/src/" ],
       // "@polkadot/joy-proposals/*": [ "packages/joy-proposals/src/*" ],
       // "@polkadot/joy-roles/": [ "packages/joy-roles/src/" ],
       // "@polkadot/joy-roles/*": [ "packages/joy-roles/src/*" ],
       // "@polkadot/joy-settings/": [ "packages/joy-settings/src/" ],
       // "@polkadot/joy-settings/*": [ "packages/joy-settings/src/*" ],
-      // "@polkadot/joy-utils/": [ "packages/joy-utils/src/" ],
-      // "@polkadot/joy-utils/*": [ "packages/joy-utils/src/*" ],
+      "@polkadot/joy-utils/": [ "packages/joy-utils/src/" ],
+      "@polkadot/joy-utils/*": [ "packages/joy-utils/src/*" ],
       "@polkadot/apps/*": ["packages/apps/src/*"],
       "@polkadot/apps": ["packages/apps/src"],
       "@polkadot/apps-config/*": [ "packages/apps-config/src/*" ],

+ 15 - 43
runtime-modules/common/Cargo.toml

@@ -1,52 +1,24 @@
 [package]
-name = 'substrate-common-module'
-version = '1.2.0'
+name = 'pallet-common'
+version = '3.0.0'
 authors = ['Joystream contributors']
 edition = '2018'
 
+[dependencies]
+serde = { version = "1.0.101", optional = true, features = ["derive"] }
+codec = { package = 'parity-scale-codec', version = '1.3.1', default-features = false, features = ['derive'] }
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+
 [features]
 default = ['std']
 std = [
-	'sr-primitives/std',
-	'srml-support/std',
-	'system/std',
-	'timestamp/std',
+	'serde',
 	'codec/std',
-	'serde'
+	'sp-runtime/std',
+	'frame-support/std',
+	'system/std',
+	'pallet-timestamp/std',
 ]
-
-
-[dependencies.sr-primitives]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.srml-support]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-support'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.system]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-system'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.codec]
-default-features = false
-features = ['derive']
-package = 'parity-scale-codec'
-version = '1.0.0'
-
-[dependencies.serde]
-features = ['derive']
-optional = true
-version = '1.0.101'
-
-[dependencies.timestamp]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-timestamp'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'

+ 2 - 2
runtime-modules/common/src/currency.rs

@@ -1,5 +1,5 @@
-use sr_primitives::traits::Convert;
-use srml_support::traits::{Currency, LockableCurrency, ReservableCurrency};
+use frame_support::traits::{Currency, LockableCurrency, ReservableCurrency};
+use sp_runtime::traits::Convert;
 
 pub trait GovernanceCurrency: system::Trait + Sized {
     type Currency: Currency<Self::AccountId>

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

@@ -24,10 +24,10 @@ pub struct BlockAndTime<BlockNumber, Moment> {
 /// Gathers current block and time information for the runtime.
 /// If this function is used inside a config() at genesis the timestamp will be 0
 /// because the timestamp is actually produced by validators.
-pub fn current_block_time<T: system::Trait + timestamp::Trait>(
+pub fn current_block_time<T: system::Trait + pallet_timestamp::Trait>(
 ) -> BlockAndTime<T::BlockNumber, T::Moment> {
     BlockAndTime {
         block: <system::Module<T>>::block_number(),
-        time: <timestamp::Module<T>>::now(),
+        time: <pallet_timestamp::Module<T>>::now(),
     }
 }

+ 37 - 120
runtime-modules/content-working-group/Cargo.toml

@@ -1,131 +1,48 @@
 [package]
-name = 'substrate-content-working-group-module'
-version = '1.1.0'
+name = 'pallet-content-working-group'
+version = '3.0.0'
 authors = ['Joystream contributors']
 edition = '2018'
 
+[dependencies]
+serde = { version = "1.0.101", optional = true, features = ["derive"] }
+codec = { package = 'parity-scale-codec', version = '1.3.1', default-features = false, features = ['derive'] }
+sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
+stake = { package = 'pallet-stake', default-features = false, path = '../stake'}
+hiring = { package = 'pallet-hiring', default-features = false, path = '../hiring'}
+minting = { package = 'pallet-token-mint', default-features = false, path = '../token-minting'}
+recurringrewards = { package = 'pallet-recurring-reward', default-features = false, path = '../recurring-reward'}
+versioned_store = { package = 'pallet-versioned-store', default-features = false, path = '../versioned-store'}
+versioned_store_permissions = { package = 'pallet-versioned-store-permissions', default-features = false, path = '../versioned-store-permissions'}
+common = { package = 'pallet-common', default-features = false, path = '../common'}
+
+[dev-dependencies]
+sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+
 [features]
 default = ['std']
 std = [
-	'sr-primitives/std',
-	'srml-support/std',
+	'serde',
+	'codec/std',
+	'sp-std/std',
+	'frame-support/std',
 	'system/std',
-    'serde',
-    'codec/std',
-    'primitives/std',
-    'rstd/std',
-    'membership/std',
-    'forum/std',
-    'hiring/std',
-    'stake/std',
-    'minting/std',
+	'sp-arithmetic/std',
+	'sp-runtime/std',
+	'membership/std',
+	'stake/std',
+	'hiring/std',
+	'minting/std',
+	'recurringrewards/std',
     'versioned_store/std',
     'versioned_store_permissions/std',
-    'recurringrewards/std',
-    'common/std',
+	'common/std',
 ]
-
-
-[dependencies.sr-primitives]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.srml-support]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-support'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.system]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-system'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.rstd]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-std'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.serde]
-features = ['derive']
-optional = true
-version = '1.0.101'
-
-[dependencies.primitives]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.codec]
-default-features = false
-features = ['derive']
-package = 'parity-scale-codec'
-version = '1.0.0'
-
-[dependencies.forum]
-default_features = false
-package = 'substrate-forum-module'
-path = '../forum'
-
-[dependencies.minting]
-default_features = false
-package = 'substrate-token-mint-module'
-path = '../token-minting'
-
-[dependencies.stake]
-default_features = false
-package = 'substrate-stake-module'
-path = '../stake'
-
-[dependencies.recurringrewards]
-default_features = false
-package = 'substrate-recurring-reward-module'
-path = '../recurring-reward'
-
-[dependencies.hiring]
-default_features = false
-package = 'substrate-hiring-module'
-path = '../hiring'
-
-[dependencies.versioned_store]
-default_features = false
-package ='substrate-versioned-store'
-path = '../versioned-store'
-
-[dependencies.versioned_store_permissions]
-default_features = false
-package = 'substrate-versioned-store-permissions-module'
-path = '../versioned-store-permissions'
-
-[dependencies.membership]
-default_features = false
-package = 'substrate-membership-module'
-path = '../membership'
-
-[dependencies.common]
-default_features = false
-package = 'substrate-common-module'
-path = '../common'
-
-[dev-dependencies.runtime-io]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-io'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dev-dependencies.balances]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-balances'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dev-dependencies.timestamp]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-timestamp'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'

+ 2 - 60
runtime-modules/content-working-group/src/genesis.rs

@@ -1,36 +1,13 @@
 #![cfg(test)]
 
 use crate::{Trait, *};
-pub use primitives::{map, Blake2Hasher, H256};
-use rstd::prelude::*;
+use sp_std::map;
 
-/// DIRTY IMPORT BECAUSE
-/// InputValidationLengthConstraint has not been factored out yet!!!
 use common::constraints::InputValidationLengthConstraint;
 
-/// The way a map (linked_map) is represented in the GenesisConfig produced by decl_storage
-//pub type GenesisConfigMap<K, V> = std::vec::Vec<(K, V)>;
-
 /// Builder of genesis configuration of content working group.
 pub struct GenesisConfigBuilder<T: Trait> {
     mint_capacity: minting::BalanceOf<T>,
-    /*
-    lead_by_id: GenesisConfigMap<LeadId<T>, Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber>>,
-    next_lead_id: LeadId<T>,
-    curator_opening_by_id: GenesisConfigMap<CuratorOpeningId<T>, CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>>,
-    next_curator_opening_id: CuratorOpeningId<T>,
-    curator_application_by_id: GenesisConfigMap<CuratorApplicationId<T>, CuratorApplication<T::AccountId, CuratorOpeningId<T>, T::MemberId, T::ApplicationId>>,
-    next_curator_application_id: CuratorApplicationId<T>,
-    channel_by_id: GenesisConfigMap<ChannelId<T>, Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>>,
-    next_channel_id: ChannelId<T>,
-    channel_id_by_handle: GenesisConfigMap<Vec<u8>, ChannelId<T>>,
-    curator_by_id: GenesisConfigMap<CuratorId<T>, Curator<T::AccountId, T::RewardRelationshipId, T::StakeId, T::BlockNumber, LeadId<T>, CuratorApplicationId<T>, PrincipalId<T>>>,
-    next_curator_id: CuratorId<T>,
-    principal_by_id: GenesisConfigMap<PrincipalId<T>, Principal<CuratorId<T>, ChannelId<T>>>,
-    next_principal_id: PrincipalId<T>,
-
-    unstaker_by_stake_id: GenesisConfigMap<TestStakeId, WorkingGroupUnstaker<LeadId<T>, CuratorId<T>>>,
-    */
     channel_creation_enabled: bool,
     channel_handle_constraint: InputValidationLengthConstraint,
     channel_description_constraint: InputValidationLengthConstraint,
@@ -47,20 +24,7 @@ impl<T: Trait> GenesisConfigBuilder<T> {
         self.mint_capacity = capacity;
         self
     }
-    /*
-    pub fn set_channel_handle_constraint(mut self, constraint: InputValidationLengthConstraint) -> Self {
-        self.channel_description_constraint = constraint;
-        self
-    }
-    pub fn set_channel_description_constraint(mut self, constraint: InputValidationLengthConstraint) -> Self {
-        self.channel_description_constraint = constraint;
-        self
-    }
-    pub fn set_channel_creation_enabled(mut self, channel_creation_enabled: bool) -> Self {
-        self.channel_creation_enabled = channel_creation_enabled;
-        self
-    }
-    */
+
     pub fn build(self) -> GenesisConfig<T> {
         GenesisConfig {
             mint_capacity: self.mint_capacity,
@@ -68,7 +32,6 @@ impl<T: Trait> GenesisConfigBuilder<T> {
             next_curator_opening_id: CuratorOpeningId::<T>::default(),
             curator_application_by_id: map![], //GenesisConfigMap<CuratorApplicationId,CuratorApplication>,
             next_curator_application_id: CuratorApplicationId::<T>::default(),
-
             channel_by_id: map![], //GenesisConfigMap<ChannelId, Channel>,
             next_channel_id: ChannelId::<T>::default(),
             channel_id_by_handle: map![], //GenesisConfigMap<Vec<u8>, ChannelId>,
@@ -78,12 +41,10 @@ impl<T: Trait> GenesisConfigBuilder<T> {
             next_principal_id: PrincipalId::<T>::default(),
             channel_creation_enabled: self.channel_creation_enabled,
             unstaker_by_stake_id: map![], //GenesisConfigMap<LeadId, CuratorId>,
-
             channel_handle_constraint: self.channel_handle_constraint,
             channel_description_constraint: self.channel_description_constraint,
             curator_application_human_readable_text: self.curator_application_human_readable_text,
             curator_exit_rationale_text: self.curator_exit_rationale_text,
-
             channel_title_constraint: self.channel_title_constraint,
             channel_avatar_constraint: self.channel_avatar_constraint,
             channel_banner_constraint: self.channel_banner_constraint,
@@ -101,25 +62,6 @@ impl<T: Trait> Default for GenesisConfigBuilder<T> {
 
         Self {
             mint_capacity: minting::BalanceOf::<T>::from(10000),
-
-            /*
-            current_lead_id: LeadId::<T>::default(), //Option<LeadId>,
-            lead_by_id: map![], //GenesisConfigMap<LeadId, Lead>,
-            next_lead_id: 0,
-            curator_opening_by_id: map![], //GenesisConfigMap<CuratorOpeningId, Opening>,
-            next_curator_opening_id: 0,
-            curator_application_by_id: map![], //GenesisConfigMap<CuratorApplicationId,CuratorApplication>,
-            next_curator_application_id: 0,
-            channel_by_id: map![], //GenesisConfigMap<ChannelId, Channel>,
-            next_channel_id: 0,
-            channel_id_by_handle: map![], //GenesisConfigMap<Vec<u8>, ChannelId>,
-            curator_by_id: map![], //GenesisConfigMap<CuratorId, Curator>,
-            next_curator_id: 0,
-            principal_by_id: map![], //GenesisConfigMap<PrinicipalId, Prinicipal>,
-            next_principal_id: 0,
-
-            unstaker_by_stake_id: map![], //GenesisConfigMap<LeadId, CuratorId>,
-            */
             channel_creation_enabled: true,
             channel_handle_constraint: default_constraint.clone(),
             channel_description_constraint: default_constraint.clone(),

+ 103 - 88
runtime-modules/content-working-group/src/lib.rs

@@ -20,15 +20,14 @@ pub mod genesis;
 use serde::{Deserialize, Serialize};
 
 use codec::{Decode, Encode};
-use rstd::borrow::ToOwned;
-use rstd::collections::btree_map::BTreeMap;
-use rstd::collections::btree_set::BTreeSet;
-use rstd::convert::From;
-use rstd::prelude::*;
-use sr_primitives::traits::{One, Zero};
-use srml_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
-use srml_support::{decl_event, decl_module, decl_storage, dispatch, ensure};
-use system::{self, ensure_root, ensure_signed};
+use frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
+use frame_support::{decl_event, decl_module, decl_storage, ensure};
+use sp_arithmetic::traits::{One, Zero};
+use sp_std::borrow::ToOwned;
+use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
+use sp_std::vec;
+use sp_std::vec::Vec;
+use system::{ensure_root, ensure_signed};
 
 use common::constraints::InputValidationLengthConstraint;
 
@@ -42,8 +41,6 @@ pub trait Trait:
     + versioned_store_permissions::Trait
     + membership::Trait
 {
-    // + Sized
-
     /// The event type.
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 }
@@ -93,6 +90,10 @@ pub type CuratorApplicationIdToCuratorIdMap<T> = BTreeMap<CuratorApplicationId<T
 // Workaround for BTreeSet type
 pub type CuratorApplicationIdSet<T> = BTreeSet<CuratorApplicationId<T>>;
 
+//TODO: Convert errors to the Substrate decl_error! macro.
+/// Result with string error message. This exists for backward compatibility purpose.
+pub type DispatchResult = Result<(), &'static str>;
+
 /*
  * MOVE ALL OF THESE OUT TO COMMON LATER
  */
@@ -718,10 +719,7 @@ pub struct OpeningPolicyCommitment<BlockNumber, Balance> {
     /// Staking policy for role itself
     pub role_staking_policy: Option<hiring::StakingPolicy<Balance, BlockNumber>>,
 
-    // Slashing terms during application
-    // pub application_slashing_terms: SlashingTerms,
-
-    // Slashing terms during role, NOT application itself!
+    /// Slashing terms during role, NOT application itself!
     pub role_slashing_terms: SlashingTerms,
 
     /// When filling an opening: Unstaking period for application stake of successful applicants
@@ -769,12 +767,6 @@ impl<LeadId: Default, CuratorId> Default for WorkingGroupUnstaker<LeadId, Curato
 // Move section below, this out in its own file later                       //
 // ======================================================================== //
 
-/*
-struct WrappedBeginAcceptingApplicationsError { // can this be made generic, or does that undermine the whole orhpan rule spirit?
-    pub error: hiring::BeginAcceptingApplicationsError
-}
-*/
-
 pub struct WrappedError<E> {
     // can this be made generic, or does that undermine the whole orhpan rule spirit?
     pub error: E,
@@ -783,7 +775,9 @@ pub struct WrappedError<E> {
 /// ....
 macro_rules! ensure_on_wrapped_error {
     ($call:expr) => {{
-        { $call }.map_err(|err| WrappedError { error: err })
+        { $call }
+            .map_err(|err| WrappedError { error: err })
+            .map_err(<&str>::from)
     }};
 }
 
@@ -791,7 +785,7 @@ macro_rules! ensure_on_wrapped_error {
 //derive_from_impl(hiring::BeginAcceptingApplicationsError)
 //derive_from_impl(hiring::BeginAcceptingApplicationsError)
 
-impl rstd::convert::From<WrappedError<hiring::BeginAcceptingApplicationsError>> for &str {
+impl sp_std::convert::From<WrappedError<hiring::BeginAcceptingApplicationsError>> for &str {
     fn from(wrapper: WrappedError<hiring::BeginAcceptingApplicationsError>) -> Self {
         match wrapper.error {
             hiring::BeginAcceptingApplicationsError::OpeningDoesNotExist => {
@@ -804,7 +798,7 @@ impl rstd::convert::From<WrappedError<hiring::BeginAcceptingApplicationsError>>
     }
 }
 
-impl rstd::convert::From<WrappedError<hiring::AddOpeningError>> for &str {
+impl sp_std::convert::From<WrappedError<hiring::AddOpeningError>> for &str {
     fn from(wrapper: WrappedError<hiring::AddOpeningError>) -> Self {
         match wrapper.error {
             hiring::AddOpeningError::OpeningMustActivateInTheFuture => {
@@ -833,7 +827,7 @@ impl rstd::convert::From<WrappedError<hiring::AddOpeningError>> for &str {
     }
 }
 
-impl rstd::convert::From<WrappedError<hiring::BeginReviewError>> for &str {
+impl sp_std::convert::From<WrappedError<hiring::BeginReviewError>> for &str {
     fn from(wrapper: WrappedError<hiring::BeginReviewError>) -> Self {
         match wrapper.error {
             hiring::BeginReviewError::OpeningDoesNotExist => {
@@ -846,7 +840,7 @@ impl rstd::convert::From<WrappedError<hiring::BeginReviewError>> for &str {
     }
 }
 
-impl<T: hiring::Trait> rstd::convert::From<WrappedError<hiring::FillOpeningError<T>>> for &str {
+impl<T: hiring::Trait> sp_std::convert::From<WrappedError<hiring::FillOpeningError<T>>> for &str {
     fn from(wrapper: WrappedError<hiring::FillOpeningError<T>>) -> Self {
         match wrapper.error {
             hiring::FillOpeningError::<T>::OpeningDoesNotExist => MSG_FULL_CURATOR_OPENING_OPENING_DOES_NOT_EXIST,
@@ -884,7 +878,7 @@ impl<T: hiring::Trait> rstd::convert::From<WrappedError<hiring::FillOpeningError
     }
 }
 
-impl rstd::convert::From<WrappedError<hiring::DeactivateApplicationError>> for &str {
+impl sp_std::convert::From<WrappedError<hiring::DeactivateApplicationError>> for &str {
     fn from(wrapper: WrappedError<hiring::DeactivateApplicationError>) -> Self {
         match wrapper.error {
             hiring::DeactivateApplicationError::ApplicationDoesNotExist => {
@@ -906,7 +900,9 @@ impl rstd::convert::From<WrappedError<hiring::DeactivateApplicationError>> for &
     }
 }
 
-impl rstd::convert::From<WrappedError<membership::ControllerAccountForMemberCheckFailed>> for &str {
+impl sp_std::convert::From<WrappedError<membership::ControllerAccountForMemberCheckFailed>>
+    for &str
+{
     fn from(wrapper: WrappedError<membership::ControllerAccountForMemberCheckFailed>) -> Self {
         match wrapper.error {
             membership::ControllerAccountForMemberCheckFailed::NotMember => {
@@ -919,7 +915,7 @@ impl rstd::convert::From<WrappedError<membership::ControllerAccountForMemberChec
     }
 }
 
-impl rstd::convert::From<WrappedError<hiring::AddApplicationError>> for &str {
+impl sp_std::convert::From<WrappedError<hiring::AddApplicationError>> for &str {
     fn from(wrapper: WrappedError<hiring::AddApplicationError>) -> Self {
         match wrapper.error {
             hiring::AddApplicationError::OpeningDoesNotExist => {
@@ -944,7 +940,7 @@ impl rstd::convert::From<WrappedError<hiring::AddApplicationError>> for &str {
     }
 }
 
-impl rstd::convert::From<WrappedError<membership::MemberControllerAccountDidNotSign>> for &str {
+impl sp_std::convert::From<WrappedError<membership::MemberControllerAccountDidNotSign>> for &str {
     fn from(wrapper: WrappedError<membership::MemberControllerAccountDidNotSign>) -> Self {
         match wrapper.error {
             membership::MemberControllerAccountDidNotSign::UnsignedOrigin => {
@@ -974,83 +970,83 @@ decl_storage! {
     trait Store for Module<T: Trait> as ContentWorkingGroup {
 
         /// The mint currently funding the rewards for this module.
-        pub Mint get(mint) : <T as minting::Trait>::MintId;
+        pub Mint get(fn mint) : <T as minting::Trait>::MintId;
 
         /// The current lead.
-        pub CurrentLeadId get(current_lead_id) : Option<LeadId<T>>;
+        pub CurrentLeadId get(fn current_lead_id) : Option<LeadId<T>>;
 
         /// Maps identifier to corresponding lead.
-        pub LeadById get(lead_by_id): linked_map LeadId<T> => Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber, T::MemberId>;
+        pub LeadById get(fn lead_by_id): map hasher(blake2_128_concat)
+            LeadId<T> => Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber, T::MemberId>;
 
         /// Next identifier for new current lead.
-        pub NextLeadId get(next_lead_id): LeadId<T>;
+        pub NextLeadId get(fn next_lead_id): LeadId<T>;
 
         /// Maps identifeir to curator opening.
-        pub CuratorOpeningById get(curator_opening_by_id) config(): linked_map CuratorOpeningId<T> => CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>;
+        pub CuratorOpeningById get(fn curator_opening_by_id) config(): map hasher(blake2_128_concat)
+            CuratorOpeningId<T> => CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>;
 
         /// Next identifier valuefor new curator opening.
-        pub NextCuratorOpeningId get(next_curator_opening_id) config(): CuratorOpeningId<T>;
+        pub NextCuratorOpeningId get(fn next_curator_opening_id) config(): CuratorOpeningId<T>;
 
         /// Maps identifier to curator application on opening.
-        pub CuratorApplicationById get(curator_application_by_id) config(): linked_map CuratorApplicationId<T> => CuratorApplication<T::AccountId, CuratorOpeningId<T>, T::MemberId, T::ApplicationId>;
+        pub CuratorApplicationById get(fn curator_application_by_id) config(): map hasher(blake2_128_concat)
+            CuratorApplicationId<T> => CuratorApplication<T::AccountId, CuratorOpeningId<T>, T::MemberId, T::ApplicationId>;
 
         /// Next identifier value for new curator application.
-        pub NextCuratorApplicationId get(next_curator_application_id) config(): CuratorApplicationId<T>;
+        pub NextCuratorApplicationId get(fn next_curator_application_id) config(): CuratorApplicationId<T>;
 
         /// Maps identifier to corresponding channel.
-        pub ChannelById get(channel_by_id) config(): linked_map ChannelId<T> => Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>;
+        pub ChannelById get(fn channel_by_id) config(): map hasher(blake2_128_concat)
+            ChannelId<T> => Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>;
 
         /// Identifier to be used by the next channel introduced.
-        pub NextChannelId get(next_channel_id) config(): ChannelId<T>;
+        pub NextChannelId get(fn next_channel_id) config(): ChannelId<T>;
 
         /// Maps (unique) channel handle to the corresponding identifier for the channel.
         /// Mapping is required to allow efficient (O(log N)) on-chain verification that a proposed handle is indeed unique
         /// at the time it is being proposed.
-        pub ChannelIdByHandle get(channel_id_by_handle) config(): linked_map Vec<u8> => ChannelId<T>;
+        pub ChannelIdByHandle get(fn channel_id_by_handle) config(): map hasher(blake2_128_concat)
+            Vec<u8> => ChannelId<T>;
 
         /// Maps identifier to corresponding curator.
-        pub CuratorById get(curator_by_id) config(): linked_map CuratorId<T> => Curator<T::AccountId, T::RewardRelationshipId, T::StakeId, T::BlockNumber, LeadId<T>, CuratorApplicationId<T>, PrincipalId<T>>;
+        pub CuratorById get(fn curator_by_id) config(): map hasher(blake2_128_concat)
+            CuratorId<T> => Curator<T::AccountId, T::RewardRelationshipId, T::StakeId, T::BlockNumber, LeadId<T>, CuratorApplicationId<T>, PrincipalId<T>>;
 
         /// Next identifier for new curator.
-        pub NextCuratorId get(next_curator_id) config(): CuratorId<T>;
+        pub NextCuratorId get(fn next_curator_id) config(): CuratorId<T>;
 
         /// Maps identifier to principal.
-        pub PrincipalById get(principal_by_id) config(): linked_map PrincipalId<T> => Principal<CuratorId<T>, ChannelId<T>>;
+        pub PrincipalById get(fn principal_by_id) config(): map hasher(blake2_128_concat)
+            PrincipalId<T> => Principal<CuratorId<T>, ChannelId<T>>;
 
         /// Next identifier for
-        pub NextPrincipalId get(next_principal_id) config(): PrincipalId<T>;
+        pub NextPrincipalId get(fn next_principal_id) config(): PrincipalId<T>;
 
         /// Whether it is currently possible to create a channel via `create_channel` extrinsic.
-        pub ChannelCreationEnabled get(channel_creation_enabled) config(): bool;
+        pub ChannelCreationEnabled get(fn channel_creation_enabled) config(): bool;
 
         /// Recover curator by the role stake which is currently unstaking.
-        pub UnstakerByStakeId get(unstaker_by_stake_id) config(): linked_map StakeId<T> => WorkingGroupUnstaker<LeadId<T>, CuratorId<T>>;
-
-        // Limits
-
-        /// Limits the total number of curators which can be active.
-        //pub MaxSimultanouslyActiveCurators get(max_simultanously_active_curators) config(): Option<u16>;
+        pub UnstakerByStakeId get(fn unstaker_by_stake_id) config(): map hasher(blake2_128_concat)
+            StakeId<T> => WorkingGroupUnstaker<LeadId<T>, CuratorId<T>>;
 
-        // Limits the total number of openings which are not yet deactivated.
-        // pub MaxSimultaneouslyActiveOpenings get(max_simultaneously_active_openings) config(): Option<u16>,
 
         // Vector length input guards
-
-        pub ChannelHandleConstraint get(channel_handle_constraint) config(): InputValidationLengthConstraint;
-        pub ChannelTitleConstraint get(channel_title_constraint) config(): InputValidationLengthConstraint;
-        pub ChannelDescriptionConstraint get(channel_description_constraint) config(): InputValidationLengthConstraint;
-        pub ChannelAvatarConstraint get(channel_avatar_constraint) config(): InputValidationLengthConstraint;
-        pub ChannelBannerConstraint get(channel_banner_constraint) config(): InputValidationLengthConstraint;
-        pub OpeningHumanReadableText get(opening_human_readable_text) config(): InputValidationLengthConstraint;
-        pub CuratorApplicationHumanReadableText get(curator_application_human_readable_text) config(): InputValidationLengthConstraint;
-        pub CuratorExitRationaleText get(curator_exit_rationale_text) config(): InputValidationLengthConstraint;
+        pub ChannelHandleConstraint get(fn channel_handle_constraint) config(): InputValidationLengthConstraint;
+        pub ChannelTitleConstraint get(fn channel_title_constraint) config(): InputValidationLengthConstraint;
+        pub ChannelDescriptionConstraint get(fn channel_description_constraint) config(): InputValidationLengthConstraint;
+        pub ChannelAvatarConstraint get(fn channel_avatar_constraint) config(): InputValidationLengthConstraint;
+        pub ChannelBannerConstraint get(fn channel_banner_constraint) config(): InputValidationLengthConstraint;
+        pub OpeningHumanReadableText get(fn opening_human_readable_text) config(): InputValidationLengthConstraint;
+        pub CuratorApplicationHumanReadableText get(fn curator_application_human_readable_text) config(): InputValidationLengthConstraint;
+        pub CuratorExitRationaleText get(fn curator_exit_rationale_text) config(): InputValidationLengthConstraint;
     }
     add_extra_genesis {
         config(mint_capacity): minting::BalanceOf<T>;
-        // config(mint_adjustment): minting::Adjustment<BalanceOf<T>, T::BlockNumber> (add serialize/deserialize derivation for type)
         build(|config: &GenesisConfig<T>| {
             // create mint
-            let mint_id = <minting::Module<T>>::add_mint(config.mint_capacity, None).expect("Failed to create a mint for the content working group");
+            let mint_id = <minting::Module<T>>::add_mint(config.mint_capacity, None)
+                .expect("Failed to create a mint for the content working group");
             Mint::<T>::put(mint_id);
         });
     }
@@ -1075,7 +1071,7 @@ decl_event! {
         CuratorOpeningAdded(CuratorOpeningId),
         AcceptedCuratorApplications(CuratorOpeningId),
         BeganCuratorApplicationReview(CuratorOpeningId),
-        CuratorOpeningFilled(CuratorOpeningId, CuratorApplicationIdToCuratorIdMap), //BTreeSet<CuratorApplicationId>),
+        CuratorOpeningFilled(CuratorOpeningId, CuratorApplicationIdToCuratorIdMap),
         TerminatedCurator(CuratorId),
         AppliedOnCuratorOpening(CuratorOpeningId, CuratorApplicationId),
         CuratorExited(CuratorId),
@@ -1101,6 +1097,7 @@ decl_module! {
          */
 
         /// Create a new channel.
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn create_channel(
             origin,
             owner: T::MemberId,
@@ -1187,6 +1184,7 @@ decl_module! {
         /// Notice that working group participants cannot do this.
         /// Notice that censored or unlisted channel may still be transferred.
         /// Notice that transfers are unilateral, so new owner cannot block. This may be problematic: https://github.com/Joystream/substrate-runtime-joystream/issues/95
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn transfer_channel_ownership(origin, channel_id: ChannelId<T>, new_owner: T::MemberId, new_role_account: T::AccountId) {
 
             // Ensure channel owner has signed
@@ -1211,6 +1209,7 @@ decl_module! {
         }
 
         /// Channel owner updates some channel properties
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn update_channel_as_owner(
             origin,
             channel_id: ChannelId<T>,
@@ -1268,6 +1267,7 @@ decl_module! {
         }
 
         /// Update channel as a curation actor
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn update_channel_as_curation_actor(
             origin,
             curation_actor: CurationActor<CuratorId<T>>,
@@ -1297,6 +1297,7 @@ decl_module! {
         }
 
         /// Add an opening for a curator role.
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn add_curator_opening(origin, activate_at: hiring::ActivateOpeningAt<T::BlockNumber>, commitment: OpeningPolicyCommitment<T::BlockNumber, BalanceOf<T>>, human_readable_text: Vec<u8>)  {
 
             // Ensure lead is set and is origin signer
@@ -1344,6 +1345,7 @@ decl_module! {
         }
 
         /// Begin accepting curator applications to an opening that is active.
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn accept_curator_applications(origin, curator_opening_id: CuratorOpeningId<T>)  {
 
             // Ensure lead is set and is origin signer
@@ -1370,6 +1372,7 @@ decl_module! {
         }
 
         /// Begin reviewing, and therefore not accepting new applications.
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn begin_curator_applicant_review(origin, curator_opening_id: CuratorOpeningId<T>) {
 
             // Ensure lead is set and is origin signer
@@ -1396,6 +1399,7 @@ decl_module! {
         }
 
         /// Fill opening for curator
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn fill_curator_opening(
             origin,
             curator_opening_id: CuratorOpeningId<T>,
@@ -1414,7 +1418,7 @@ decl_module! {
                 let mint_id = Self::mint();
 
                 // Technically this is a bug-check and should not be here.
-                ensure!(<minting::Mints<T>>::exists(mint_id), MSG_FILL_CURATOR_OPENING_MINT_DOES_NOT_EXIST);
+                ensure!(<minting::Mints<T>>::contains_key(mint_id), MSG_FILL_CURATOR_OPENING_MINT_DOES_NOT_EXIST);
 
                 // Make sure valid parameters are selected for next payment at block number
                 ensure!(policy.next_payment_at_block > <system::Module<T>>::block_number(), MSG_FILL_CURATOR_OPENING_INVALID_NEXT_PAYMENT_BLOCK);
@@ -1555,6 +1559,7 @@ decl_module! {
 
         }
 
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn withdraw_curator_application(
             origin,
             curator_application_id: CuratorApplicationId<T>
@@ -1591,6 +1596,7 @@ decl_module! {
         }
 
         /// Lead terminate curator application
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn terminate_curator_application(
             origin,
             curator_application_id: CuratorApplicationId<T>
@@ -1621,6 +1627,7 @@ decl_module! {
         }
 
         /// Apply on a curator opening.
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn apply_on_curator_opening(
             origin,
             member_id: T::MemberId,
@@ -1709,6 +1716,7 @@ decl_module! {
         }
 
         /// An active curator can update the associated role account.
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn update_curator_role_account(
             origin,
             member_id: T::MemberId,
@@ -1736,6 +1744,7 @@ decl_module! {
 
         /// An active curator can update the reward account associated
         /// with a set reward relationship.
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn update_curator_reward_account(
             origin,
             curator_id: CuratorId<T>,
@@ -1767,6 +1776,7 @@ decl_module! {
         }
 
         /// An active curator leaves role
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn leave_curator_role(
             origin,
             curator_id: CuratorId<T>,
@@ -1788,6 +1798,7 @@ decl_module! {
         }
 
         /// Lead can terminate and active curator
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn terminate_curator_role(
             origin,
             curator_id: CuratorId<T>,
@@ -1819,6 +1830,7 @@ decl_module! {
         /// If a value is provided for new_lead it will then set that new lead.
         /// It is responsibility of the caller to ensure the new lead can be set
         /// to avoid the lead role being vacant at the end of the call.
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn replace_lead(origin, new_lead: Option<(T::MemberId, T::AccountId)>) {
             // Ensure root is origin
             ensure_root(origin)?;
@@ -1835,6 +1847,7 @@ decl_module! {
         }
 
         /// Add an opening for a curator role.
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn set_channel_creation_enabled(origin, enabled: bool)  {
 
             // Ensure lead is set and is origin signer
@@ -1856,6 +1869,7 @@ decl_module! {
         /// both increase and decrease capacity. Although when considering that it may be executed
         /// by a proposal, given the temporal delay in approving a proposal, it might be more suitable
         /// than set_mint_capacity?
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn increase_mint_capacity(
             origin,
             additional_capacity: minting::BalanceOf<T>
@@ -1865,7 +1879,7 @@ decl_module! {
             let mint_id = Self::mint();
             let mint = <minting::Module<T>>::mints(mint_id); // must exist
             let new_capacity = mint.capacity() + additional_capacity;
-            <minting::Module<T>>::set_mint_capacity(mint_id, new_capacity)?;
+            <minting::Module<T>>::set_mint_capacity(mint_id, new_capacity).map_err(<&str>::from)?;
 
             Self::deposit_event(RawEvent::MintCapacityIncreased(
                 mint_id, additional_capacity, new_capacity
@@ -1873,6 +1887,7 @@ decl_module! {
         }
 
         /// Sets the capacity of the current active mint
+        #[weight = 10_000_000] // TODO: adjust weight
         pub fn set_mint_capacity(
             origin,
             new_capacity: minting::BalanceOf<T>
@@ -1890,7 +1905,7 @@ decl_module! {
 
             if new_capacity != current_capacity {
                 // Cannot fail if mint exists
-                <minting::Module<T>>::set_mint_capacity(mint_id, new_capacity)?;
+                <minting::Module<T>>::set_mint_capacity(mint_id, new_capacity).map_err(<&str>::from)?;
 
                 if new_capacity > current_capacity {
                     Self::deposit_event(RawEvent::MintCapacityIncreased(
@@ -1902,7 +1917,6 @@ decl_module! {
                     ));
                 }
             }
-
         }
     }
 }
@@ -1910,7 +1924,7 @@ decl_module! {
 impl<T: Trait> versioned_store_permissions::CredentialChecker<T> for Module<T> {
     fn account_has_credential(account: &T::AccountId, id: PrincipalId<T>) -> bool {
         // Check that principal exists
-        if !PrincipalById::<T>::exists(&id) {
+        if !PrincipalById::<T>::contains_key(&id) {
             return false;
         }
 
@@ -1950,7 +1964,7 @@ impl<T: Trait> versioned_store_permissions::CredentialChecker<T> for Module<T> {
 
 impl<T: Trait> Module<T> {
     /// Introduce a lead when one is not currently set.
-    fn set_lead(member: T::MemberId, role_account: T::AccountId) -> dispatch::Result {
+    fn set_lead(member: T::MemberId, role_account: T::AccountId) -> DispatchResult {
         // Ensure there is no current lead
         ensure!(
             <CurrentLeadId<T>>::get().is_none(),
@@ -1988,7 +2002,7 @@ impl<T: Trait> Module<T> {
     }
 
     /// Evict the currently set lead
-    fn unset_lead() -> dispatch::Result {
+    fn unset_lead() -> DispatchResult {
         // Ensure there is a lead set
         let (lead_id, lead) = Self::ensure_lead_is_set()?;
 
@@ -2039,7 +2053,7 @@ impl<T: Trait> Module<T> {
     }
 
     // TODO: convert InputConstraint ensurer routines into macroes
-    fn ensure_channel_handle_is_valid(handle: &[u8]) -> dispatch::Result {
+    fn ensure_channel_handle_is_valid(handle: &[u8]) -> DispatchResult {
         ChannelHandleConstraint::get().ensure_valid(
             handle.len(),
             MSG_CHANNEL_HANDLE_TOO_SHORT,
@@ -2048,14 +2062,14 @@ impl<T: Trait> Module<T> {
 
         // Has to not already be occupied
         ensure!(
-            !ChannelIdByHandle::<T>::exists(handle),
+            !ChannelIdByHandle::<T>::contains_key(handle),
             MSG_CHANNEL_HANDLE_ALREADY_TAKEN
         );
 
         Ok(())
     }
 
-    fn ensure_channel_title_is_valid(text_opt: &OptionalText) -> dispatch::Result {
+    fn ensure_channel_title_is_valid(text_opt: &OptionalText) -> DispatchResult {
         if let Some(text) = text_opt {
             ChannelTitleConstraint::get().ensure_valid(
                 text.len(),
@@ -2067,7 +2081,7 @@ impl<T: Trait> Module<T> {
         }
     }
 
-    fn ensure_channel_description_is_valid(text_opt: &OptionalText) -> dispatch::Result {
+    fn ensure_channel_description_is_valid(text_opt: &OptionalText) -> DispatchResult {
         if let Some(text) = text_opt {
             ChannelDescriptionConstraint::get().ensure_valid(
                 text.len(),
@@ -2079,7 +2093,7 @@ impl<T: Trait> Module<T> {
         }
     }
 
-    fn ensure_channel_avatar_is_valid(text_opt: &OptionalText) -> dispatch::Result {
+    fn ensure_channel_avatar_is_valid(text_opt: &OptionalText) -> DispatchResult {
         if let Some(text) = text_opt {
             ChannelAvatarConstraint::get().ensure_valid(
                 text.len(),
@@ -2091,7 +2105,7 @@ impl<T: Trait> Module<T> {
         }
     }
 
-    fn ensure_channel_banner_is_valid(text_opt: &OptionalText) -> dispatch::Result {
+    fn ensure_channel_banner_is_valid(text_opt: &OptionalText) -> DispatchResult {
         if let Some(text) = text_opt {
             ChannelBannerConstraint::get().ensure_valid(
                 text.len(),
@@ -2103,7 +2117,7 @@ impl<T: Trait> Module<T> {
         }
     }
 
-    fn ensure_curator_application_text_is_valid(text: &[u8]) -> dispatch::Result {
+    fn ensure_curator_application_text_is_valid(text: &[u8]) -> DispatchResult {
         CuratorApplicationHumanReadableText::get().ensure_valid(
             text.len(),
             MSG_CURATOR_APPLICATION_TEXT_TOO_SHORT,
@@ -2111,7 +2125,7 @@ impl<T: Trait> Module<T> {
         )
     }
 
-    fn ensure_curator_exit_rationale_text_is_valid(text: &[u8]) -> dispatch::Result {
+    fn ensure_curator_exit_rationale_text_is_valid(text: &[u8]) -> DispatchResult {
         CuratorExitRationaleText::get().ensure_valid(
             text.len(),
             MSG_CURATOR_EXIT_RATIONALE_TEXT_TOO_SHORT,
@@ -2119,7 +2133,7 @@ impl<T: Trait> Module<T> {
         )
     }
 
-    fn ensure_opening_human_readable_text_is_valid(text: &[u8]) -> dispatch::Result {
+    fn ensure_opening_human_readable_text_is_valid(text: &[u8]) -> DispatchResult {
         OpeningHumanReadableText::get().ensure_valid(
             text.len(),
             MSG_CHANNEL_DESCRIPTION_TOO_SHORT,
@@ -2131,7 +2145,7 @@ impl<T: Trait> Module<T> {
         channel_id: &ChannelId<T>,
     ) -> Result<Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>, &'static str>
     {
-        if ChannelById::<T>::exists(channel_id) {
+        if ChannelById::<T>::contains_key(channel_id) {
             let channel = ChannelById::<T>::get(channel_id);
 
             Ok(channel)
@@ -2198,7 +2212,7 @@ impl<T: Trait> Module<T> {
         &'static str,
     > {
         ensure!(
-            CuratorOpeningById::<T>::exists(curator_opening_id),
+            CuratorOpeningById::<T>::contains_key(curator_opening_id),
             MSG_CURATOR_OPENING_DOES_NOT_EXIST
         );
 
@@ -2224,7 +2238,7 @@ impl<T: Trait> Module<T> {
         &'static str,
     > {
         ensure!(
-            CuratorById::<T>::exists(curator_id),
+            CuratorById::<T>::contains_key(curator_id),
             MSG_CURATOR_DOES_NOT_EXIST
         );
 
@@ -2237,7 +2251,7 @@ impl<T: Trait> Module<T> {
         stake_id: &StakeId<T>,
     ) -> Result<WorkingGroupUnstaker<LeadId<T>, CuratorId<T>>, &'static str> {
         ensure!(
-            UnstakerByStakeId::<T>::exists(stake_id),
+            UnstakerByStakeId::<T>::contains_key(stake_id),
             MSG_UNSTAKER_DOES_NOT_EXIST
         );
 
@@ -2353,7 +2367,7 @@ impl<T: Trait> Module<T> {
         &'static str,
     > {
         ensure!(
-            CuratorApplicationById::<T>::exists(curator_application_id),
+            CuratorApplicationById::<T>::contains_key(curator_application_id),
             MSG_CURATOR_APPLICATION_DOES_NOT_EXIST
         );
 
@@ -2424,6 +2438,7 @@ impl<T: Trait> Module<T> {
                     WithdrawReasons::all(),
                     new_balance,
                 )
+                .map_err(<&str>::from)
             }
         } else {
             Ok(())
@@ -2613,7 +2628,7 @@ impl<T: Trait> Module<T> {
     /// to this module.
     pub fn unstaked(stake_id: StakeId<T>) {
         // Ignore if unstaked doesn't exist
-        if !<UnstakerByStakeId<T>>::exists(stake_id) {
+        if !<UnstakerByStakeId<T>>::contains_key(stake_id) {
             return;
         }
 

+ 37 - 32
runtime-modules/content-working-group/src/mock.rs

@@ -1,18 +1,16 @@
 #![cfg(test)]
 
 pub use crate::*;
-pub use srml_support::traits::Currency;
-pub use system;
 
-pub use primitives::{map, Blake2Hasher, H256};
-pub use sr_primitives::{
-    testing::{Digest, DigestItem, Header, UintAuthorityId},
-    traits::{BlakeTwo256, Convert, IdentityLookup, OnFinalize},
-    weights::Weight,
-    BuildStorage, Perbill,
+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,
 };
-
-use srml_support::{impl_outer_event, impl_outer_origin, parameter_types};
+pub use system;
 
 pub use common::currency::GovernanceCurrency;
 pub use hiring;
@@ -32,10 +30,6 @@ parameter_types! {
     pub const AvailableBlockRatio: Perbill = Perbill::one();
     pub const MinimumPeriod: u64 = 5;
     pub const ExistentialDeposit: u32 = 0;
-    pub const TransferFee: u32 = 0;
-    pub const CreationFee: u32 = 0;
-    pub const TransactionBaseFee: u32 = 1;
-    pub const TransactionByteFee: u32 = 0;
     pub const StakePoolId: [u8; 8] = *b"joystake";
 }
 
@@ -56,6 +50,7 @@ impl_outer_event! {
         versioned_store<T>,
         membership<T>,
         balances<T>,
+        system<T>,
         lib<T>,
     }
 }
@@ -80,47 +75,45 @@ pub fn get_last_event_or_panic() -> RawLibTestEvent {
     }
 }
 
-type TestAccountId = u64;
-type TestBlockNumber = u64;
 impl system::Trait for Test {
+    type BaseCallFilter = ();
     type Origin = Origin;
-    type Index = u64;
-    type BlockNumber = TestBlockNumber;
     type Call = ();
+    type Index = u64;
+    type BlockNumber = u64;
     type Hash = H256;
     type Hashing = BlakeTwo256;
-    type AccountId = TestAccountId;
+    type AccountId = u64;
     type Lookup = IdentityLookup<Self::AccountId>;
     type Header = Header;
     type Event = TestEvent;
     type BlockHashCount = BlockHashCount;
     type MaximumBlockWeight = MaximumBlockWeight;
+    type DbWeight = ();
+    type BlockExecutionWeight = ();
+    type ExtrinsicBaseWeight = ();
+    type MaximumExtrinsicWeight = ();
     type MaximumBlockLength = MaximumBlockLength;
     type AvailableBlockRatio = AvailableBlockRatio;
     type Version = ();
+    type ModuleToIndex = ();
+    type AccountData = balances::AccountData<u64>;
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
 }
 
-impl timestamp::Trait for Test {
+impl pallet_timestamp::Trait for Test {
     type Moment = u64;
     type OnTimestampSet = ();
     type MinimumPeriod = MinimumPeriod;
 }
 
 impl balances::Trait for Test {
-    /// The type for recording an account's balance.
     type Balance = u64;
-    /// What to do if an account's free balance gets zeroed.
-    type OnFreeBalanceZero = ();
-    /// What to do if a new account is created.
-    type OnNewAccount = ();
-    /// The ubiquitous event type.
-    type Event = TestEvent;
-
     type DustRemoval = ();
-    type TransferPayment = ();
+    type Event = TestEvent;
     type ExistentialDeposit = ExistentialDeposit;
-    type TransferFee = TransferFee;
-    type CreationFee = CreationFee;
+    type AccountStore = System;
 }
 
 impl GovernanceCurrency for Test {
@@ -206,7 +199,7 @@ impl<T: Trait> TestExternalitiesBuilder<T> {
         self
     }
 
-    pub fn build(self) -> runtime_io::TestExternalities {
+    pub fn build(self) -> sp_io::TestExternalities {
         // Add system
         let mut t = self
             .system_config
@@ -241,3 +234,15 @@ pub type System = system::Module<Test>;
 pub type Balances = balances::Module<Test>;
 pub type ContentWorkingGroup = Module<Test>;
 pub type Minting = minting::Module<Test>;
+
+// Recommendation from Parity on testing on_finalize
+// https://substrate.dev/docs/en/next/development/module/tests
+pub fn run_to_block(n: u64) {
+    while System::block_number() < n {
+        <System as OnFinalize<u64>>::on_finalize(System::block_number());
+        <ContentWorkingGroup as OnFinalize<u64>>::on_finalize(System::block_number());
+        System::set_block_number(System::block_number() + 1);
+        <System as OnInitialize<u64>>::on_initialize(System::block_number());
+        <ContentWorkingGroup as OnInitialize<u64>>::on_initialize(System::block_number());
+    }
+}

+ 0 - 6
runtime-modules/content-working-group/src/mod.rs

@@ -1,6 +0,0 @@
-pub mod genesis;
-pub mod lib;
-//pub mod types;
-
-mod mock;
-mod tests;

+ 79 - 62
runtime-modules/content-working-group/src/tests.rs

@@ -1,20 +1,28 @@
 #![cfg(test)]
 
 use super::genesis;
-use super::mock::{self, *};
-use hiring;
-use rstd::collections::btree_map::BTreeMap;
-use rstd::collections::btree_set::BTreeSet;
-use sr_primitives::traits::One;
-use srml_support::{assert_err, assert_ok, StorageLinkedMap, StorageValue};
+use super::mock::*;
+
+use frame_support::{assert_err, assert_ok, traits::Currency, StorageValue};
+use sp_arithmetic::traits::One;
+use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
+use system::RawOrigin;
 
 use common::constraints::InputValidationLengthConstraint;
+use hiring;
 
 #[test]
 fn create_channel_success() {
     TestExternalitiesBuilder::<Test>::default()
         .build()
         .execute_with(|| {
+            /*
+               Events are not emitted on block 0.
+               So any dispatchable calls made during genesis block formation will have no events emitted.
+               https://substrate.dev/recipes/2-appetizers/4-events.html
+            */
+            run_to_block(1);
+
             // Add channel creator as member
             let channel_creator_member_root_and_controller_account = 12312;
 
@@ -171,6 +179,13 @@ fn transfer_channel_ownership_success() {
     TestExternalitiesBuilder::<Test>::default()
         .build()
         .execute_with(|| {
+            /*
+               Events are not emitted on block 0.
+               So any dispatchable calls made during genesis block formation will have no events emitted.
+               https://substrate.dev/recipes/2-appetizers/4-events.html
+            */
+            run_to_block(1);
+
             // Add channel creator as member
             let channel_creator_member_root_and_controller_account_1 = 1111;
             let channel_creator_member_root_and_controller_account_2 = 2222;
@@ -242,6 +257,7 @@ impl UpdateChannelAsCurationActorFixture {
             self.new_verified,
             self.new_curation_status,
         )
+        .map_err(<&str>::from)
     }
 
     pub fn call_and_assert_success(&self, channel_id: ChannelId<Test>) {
@@ -284,7 +300,7 @@ impl UpdateChannelAsCurationActorFixture {
         assert_eq!(event_channel_id, channel_id);
 
         // Channel has been updated correctly
-        assert!(ChannelById::<Test>::exists(channel_id));
+        assert!(ChannelById::<Test>::contains_key(channel_id));
 
         let updated_channel = ChannelById::<Test>::get(channel_id);
 
@@ -394,7 +410,7 @@ fn add_curator_opening_success() {
 
             // Assert that given opening id has been added,
             // and has the right properties.
-            assert!(crate::CuratorOpeningById::<Test>::exists(
+            assert!(crate::CuratorOpeningById::<Test>::contains_key(
                 expected_curator_opening_id
             ));
 
@@ -748,7 +764,7 @@ fn apply_on_curator_opening_success() {
                 )
             );
 
-            assert!(CuratorApplicationById::<Test>::exists(
+            assert!(CuratorApplicationById::<Test>::contains_key(
                 new_curator_application_id
             ));
 
@@ -874,6 +890,7 @@ impl UpdateCuratorRoleAccountFixture {
             self.curator_id,
             self.new_role_account,
         )
+        .map_err(<&str>::from)
     }
 
     pub fn call_and_assert_success(&self) {
@@ -950,6 +967,7 @@ impl UpdateCuratorRewardAccountFixture {
             self.curator_id,
             self.new_reward_account,
         )
+        .map_err(<&str>::from)
     }
 
     #[allow(dead_code)] // delete if the method is unnecessary
@@ -1016,6 +1034,7 @@ impl LeaveCuratorRoleFixture {
             self.curator_id,
             self.rationale_text.clone(),
         )
+        .map_err(<&str>::from)
     }
 
     pub fn call_and_assert_success(&self) {
@@ -1046,7 +1065,9 @@ impl LeaveCuratorRoleFixture {
         // Tracking unstaking
         let curator_role_stake_id = original_curator.role_stake_profile.unwrap().stake_id;
 
-        assert!(UnstakerByStakeId::<Test>::exists(curator_role_stake_id));
+        assert!(UnstakerByStakeId::<Test>::contains_key(
+            curator_role_stake_id
+        ));
 
         let unstaker = UnstakerByStakeId::<Test>::get(curator_role_stake_id);
 
@@ -1089,6 +1110,7 @@ impl TerminateCuratorRoleFixture {
             self.curator_id,
             self.rationale_text.clone(),
         )
+        .map_err(<&str>::from)
     }
 
     pub fn call_and_assert_success(&self) {
@@ -1119,7 +1141,9 @@ impl TerminateCuratorRoleFixture {
         // Tracking unstaking
         let curator_role_stake_id = original_curator.role_stake_profile.unwrap().stake_id;
 
-        assert!(UnstakerByStakeId::<Test>::exists(curator_role_stake_id));
+        assert!(UnstakerByStakeId::<Test>::contains_key(
+            curator_role_stake_id
+        ));
 
         let unstaker = UnstakerByStakeId::<Test>::get(curator_role_stake_id);
 
@@ -1161,6 +1185,7 @@ impl SetLeadFixture {
             self.origin.clone(),
             Some((self.member_id, self.new_role_account)),
         )
+        .map_err(<&str>::from)
     }
 
     pub fn call_and_assert_success(&self) {
@@ -1204,11 +1229,18 @@ fn set_lead_success() {
     TestExternalitiesBuilder::<Test>::default()
         .build()
         .execute_with(|| {
+            /*
+               Events are not emitted on block 0.
+               So any dispatchable calls made during genesis block formation will have no events emitted.
+               https://substrate.dev/recipes/2-appetizers/4-events.html
+            */
+            run_to_block(1);
+
             let member_id =
                 add_member(LEAD_ROOT_AND_CONTROLLER_ACCOUNT, to_vec(LEAD_MEMBER_HANDLE));
 
             SetLeadFixture {
-                origin: Origin::system(system::RawOrigin::Root),
+                origin: RawOrigin::Root.into(),
                 member_id,
                 new_role_account: 44444,
             }
@@ -1222,7 +1254,7 @@ struct UnsetLeadFixture {
 
 impl UnsetLeadFixture {
     fn call(&self) -> Result<(), &'static str> {
-        ContentWorkingGroup::replace_lead(self.origin.clone(), None)
+        ContentWorkingGroup::replace_lead(self.origin.clone(), None).map_err(<&str>::from)
     }
 
     pub fn call_and_assert_success(&self) {
@@ -1261,7 +1293,7 @@ fn unset_lead_success() {
             let _ = add_member_and_set_as_lead();
 
             UnsetLeadFixture {
-                origin: Origin::system(system::RawOrigin::Root),
+                origin: RawOrigin::Root.into(),
             }
             .call_and_assert_success();
         });
@@ -1311,7 +1343,7 @@ impl UnstakedFixture {
         );
 
         // Unstaker gone
-        assert!(!UnstakerByStakeId::<Test>::exists(self.stake_id));
+        assert!(!UnstakerByStakeId::<Test>::contains_key(self.stake_id));
     }
 
     // pub fn call_and_assert_failed_result(&self, error_message: &'static str) {
@@ -1406,39 +1438,6 @@ pub fn to_vec(s: &str) -> Vec<u8> {
     s.as_bytes().to_vec()
 }
 
-/*
- * Setups
- */
-
-//type TestSeed = u128;
-
-/*
-fn account_from_seed(TestSeed: seed) -> <Test as system::Trait>::AccountId {
-
-}
-
-fn vector_from_seed(TestSeed: seed) {
-
-}
-*/
-
-/*
-static INITIAL_SEED_VALUE: u128 = 0;
-static CURRENT_SEED: u128 = INITIAL_SEED_VALUE;
-
-fn get_current_seed() {
-
-}
-
-fn update_seed() {
-
-}
-
-fn reset_seed() {
-    CURRENT_SEED: u128 = INITIAL_SEED_VALUE;
-}
-*/
-
 // MOVE THIS LATER WHEN fill_opening is factored out
 #[derive(Clone)]
 pub struct FillOpeningApplicantParams {
@@ -1568,7 +1567,7 @@ fn add_member_and_apply_on_opening(
         crate::RawEvent::AppliedOnCuratorOpening(curator_opening_id, new_curator_application_id)
     );
 
-    assert!(CuratorApplicationById::<Test>::exists(
+    assert!(CuratorApplicationById::<Test>::contains_key(
         new_curator_application_id
     ));
 
@@ -1718,7 +1717,6 @@ fn setup_and_fill_opening(
     let applicants_to_hire_iter = applicants.iter().filter(|params| params.hire);
 
     let num_applicants_hired = applicants_to_hire_iter.cloned().count();
-    //let num_applicants_not_to_hire = (applicants.len() - num_applicants_hired) as usize;
 
     let hired_applicant_and_result = setup_opening_in_review
         .added_members_application_result
@@ -1799,7 +1797,7 @@ fn setup_and_fill_opening(
         let expected_added_principal_id: u64 = (hired_index as u64) + original_next_principal_id;
 
         // Curator added
-        assert!(CuratorById::<Test>::exists(expected_added_curator_id));
+        assert!(CuratorById::<Test>::contains_key(expected_added_curator_id));
 
         let added_curator = CuratorById::<Test>::get(expected_added_curator_id);
 
@@ -1848,7 +1846,9 @@ fn setup_and_fill_opening(
         assert_eq!(expected_curator, added_curator);
 
         // Principal added
-        assert!(PrincipalById::<Test>::exists(expected_added_principal_id));
+        assert!(PrincipalById::<Test>::contains_key(
+            expected_added_principal_id
+        ));
 
         let added_principal = PrincipalById::<Test>::get(expected_added_principal_id);
 
@@ -1978,6 +1978,7 @@ impl CreateChannelFixture {
             self.banner.clone(),
             self.publication_status.clone(),
         )
+        .map_err(<&str>::from)
     }
 
     pub fn call_and_assert_error(&self, err_message: &'static str) {
@@ -2010,7 +2011,7 @@ impl CreateChannelFixture {
 
         // Assert that given channel id has been added,
         // and has the right properties.
-        assert!(crate::ChannelById::<Test>::exists(channel_id));
+        assert!(crate::ChannelById::<Test>::contains_key(channel_id));
 
         let created_channel = crate::ChannelById::<Test>::get(channel_id);
 
@@ -2044,7 +2045,7 @@ impl CreateChannelFixture {
         );
 
         // Check that principal actually has been added
-        assert!(crate::PrincipalById::<Test>::exists(
+        assert!(crate::PrincipalById::<Test>::contains_key(
             created_channel.principal_id
         ));
 
@@ -2113,15 +2114,18 @@ pub fn set_lead(
     member_id: <Test as membership::Trait>::MemberId,
     new_role_account: <Test as system::Trait>::AccountId,
 ) -> LeadId<Test> {
-    // Get controller account
-    //let lead_member_controller_account = membership::Module::<Test>::ensure_membership(member_id).unwrap().controller_account;
+    /*
+       Events are not emitted on block 0.
+       So any dispatchable calls made during genesis block formation will have no events emitted.
+       https://substrate.dev/recipes/2-appetizers/4-events.html
+    */
+    run_to_block(1);
 
     let expected_lead_id = NextLeadId::<Test>::get();
-
     // Set lead
     assert_eq!(
         ContentWorkingGroup::replace_lead(
-            mock::Origin::system(system::RawOrigin::Root),
+            RawOrigin::Root.into(),
             Some((member_id, new_role_account))
         )
         .unwrap(),
@@ -2136,7 +2140,6 @@ pub fn set_lead(
     expected_lead_id
 }
 
-// lead_role_account: <Test as system::Trait>::AccountId
 pub fn add_curator_opening() -> CuratorOpeningId<Test> {
     let activate_at = hiring::ActivateOpeningAt::ExactBlock(34);
 
@@ -2195,6 +2198,13 @@ fn increasing_mint_capacity() {
         )
         .build()
         .execute_with(|| {
+            /*
+               Events are not emitted on block 0.
+               So any dispatchable calls made during genesis block formation will have no events emitted.
+               https://substrate.dev/recipes/2-appetizers/4-events.html
+            */
+            run_to_block(1);
+
             let mint_id = ContentWorkingGroup::mint();
             let mint = Minting::mints(mint_id);
             assert_eq!(mint.capacity(), MINT_CAPACITY);
@@ -2203,7 +2213,7 @@ fn increasing_mint_capacity() {
             // Increasing mint capacity
             let expected_new_capacity = MINT_CAPACITY + increase;
             assert_ok!(ContentWorkingGroup::increase_mint_capacity(
-                Origin::ROOT,
+                RawOrigin::Root.into(),
                 increase
             ));
             // Excpected event after increasing
@@ -2229,6 +2239,13 @@ fn setting_mint_capacity() {
         )
         .build()
         .execute_with(|| {
+            /*
+               Events are not emitted on block 0.
+               So any dispatchable calls made during genesis block formation will have no events emitted.
+               https://substrate.dev/recipes/2-appetizers/4-events.html
+            */
+            run_to_block(1);
+
             let mint_id = ContentWorkingGroup::mint();
             let mint = Minting::mints(mint_id);
             assert_eq!(mint.capacity(), MINT_CAPACITY);
@@ -2237,7 +2254,7 @@ fn setting_mint_capacity() {
             let new_lower_capacity = 10000;
             let decrease = MINT_CAPACITY - new_lower_capacity;
             assert_ok!(ContentWorkingGroup::set_mint_capacity(
-                Origin::ROOT,
+                RawOrigin::Root.into(),
                 new_lower_capacity
             ));
             // Correct event after decreasing
@@ -2253,7 +2270,7 @@ fn setting_mint_capacity() {
             let new_higher_capacity = 25000;
             let increase = new_higher_capacity - mint.capacity();
             assert_ok!(ContentWorkingGroup::set_mint_capacity(
-                Origin::ROOT,
+                RawOrigin::Root.into(),
                 new_higher_capacity
             ));
             // Excpected event after increasing

+ 19 - 41
runtime-modules/forum/Cargo.toml

@@ -1,56 +1,34 @@
 [package]
-name = 'substrate-forum-module'
-version = '1.2.2'
+name = 'pallet-forum'
+version = '3.0.0'
 authors = ['Joystream contributors']
 edition = '2018'
 
 [dependencies]
-hex-literal = '0.1.0'
-serde = { version = '1.0.101', optional = true}
-serde_derive = { version = '1.0.101', optional = true }
-rstd = { package = 'sr-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'}
-runtime-primitives = { package = 'sr-primitives', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'}
-srml-support = { package = 'srml-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'}
-srml-support-procedural = { package = 'srml-support-procedural', git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'}
-system = { package = 'srml-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'}
-balances = { package = 'srml-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'}
-codec = { package = 'parity-scale-codec', version = '1.0.0', default-features = false, features = ['derive'] }
-# https://users.rust-lang.org/t/failure-derive-compilation-error/39062
-quote = '<=1.0.2'
-
-[dependencies.timestamp]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-timestamp'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.runtime-io]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-io'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
+serde = { version = "1.0.101", optional = true, features = ["derive"] }
+codec = { package = 'parity-scale-codec', version = '1.3.1', default-features = false, features = ['derive'] }
+frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+common = { package = 'pallet-common', default-features = false, path = '../common'}
 
 [dev-dependencies]
-runtime-io = { package = 'sr-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'}
-primitives = { package = 'substrate-primitives', git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'}
-
-[dependencies.common]
-default_features = false
-package = 'substrate-common-module'
-path = '../common'
+sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 
 [features]
 default = ['std']
 std = [
 	'serde',
-	'serde_derive',
 	'codec/std',
-	'rstd/std',
-	'runtime-io/std',
-	'runtime-primitives/std',
-	'srml-support/std',
+	'frame-support/std',
 	'system/std',
-  	'balances/std',
-	'timestamp/std',
+	'sp-std/std',
+	'sp-arithmetic/std',
+	'sp-runtime/std',
+	'pallet-timestamp/std',
 	'common/std',
-]
+]

+ 59 - 66
runtime-modules/forum/src/lib.rs

@@ -7,14 +7,19 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 #[cfg(feature = "std")]
-use serde_derive::{Deserialize, Serialize};
+use serde::{Deserialize, Serialize};
 
-use rstd::borrow::ToOwned;
-use rstd::prelude::*;
+//TODO: Convert errors to the Substrate decl_error! macro.
+/// Result with string error message. This exists for backward compatibility purpose.
+pub type DispatchResult = Result<(), &'static str>;
 
 use codec::{Codec, Decode, Encode};
-use runtime_primitives::traits::{MaybeSerialize, Member, One, SimpleArithmetic};
-use srml_support::{decl_event, decl_module, decl_storage, dispatch, ensure, Parameter};
+use frame_support::{decl_event, decl_module, decl_storage, ensure, Parameter};
+use sp_arithmetic::traits::{BaseArithmetic, One};
+use sp_runtime::traits::{MaybeSerialize, Member};
+use sp_std::borrow::ToOwned;
+use sp_std::vec;
+use sp_std::vec::Vec;
 
 mod mock;
 mod tests;
@@ -256,7 +261,7 @@ impl<BlockNumber, Moment, AccountId> Category<BlockNumber, Moment, AccountId> {
 type CategoryTreePath<BlockNumber, Moment, AccountId> =
     Vec<Category<BlockNumber, Moment, AccountId>>;
 
-pub trait Trait: system::Trait + timestamp::Trait + Sized {
+pub trait Trait: system::Trait + pallet_timestamp::Trait + Sized {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
     type MembershipRegistry: ForumUserRegistry<Self::AccountId>;
@@ -264,7 +269,7 @@ pub trait Trait: system::Trait + timestamp::Trait + Sized {
     /// Thread Id type
     type ThreadId: Parameter
         + Member
-        + SimpleArithmetic
+        + BaseArithmetic
         + Codec
         + Default
         + Copy
@@ -274,7 +279,7 @@ pub trait Trait: system::Trait + timestamp::Trait + Sized {
     /// Post Id type
     type PostId: Parameter
         + Member
-        + SimpleArithmetic
+        + BaseArithmetic
         + Codec
         + Default
         + Copy
@@ -286,59 +291,39 @@ decl_storage! {
     trait Store for Module<T: Trait> as Forum {
 
         /// Map category identifier to corresponding category.
-        pub CategoryById get(category_by_id) config(): map CategoryId => Category<T::BlockNumber, T::Moment, T::AccountId>;
+        pub CategoryById get(fn category_by_id) config(): map hasher(blake2_128_concat)
+            CategoryId => Category<T::BlockNumber, T::Moment, T::AccountId>;
 
         /// Category identifier value to be used for the next Category created.
-        pub NextCategoryId get(next_category_id) config(): CategoryId;
+        pub NextCategoryId get(fn next_category_id) config(): CategoryId;
 
         /// Map thread identifier to corresponding thread.
-        pub ThreadById get(thread_by_id) config(): map T::ThreadId => Thread<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId>;
+        pub ThreadById get(fn thread_by_id) config(): map hasher(blake2_128_concat)
+            T::ThreadId => Thread<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId>;
 
         /// Thread identifier value to be used for next Thread in threadById.
-        pub NextThreadId get(next_thread_id) config(): T::ThreadId;
+        pub NextThreadId get(fn next_thread_id) config(): T::ThreadId;
 
         /// Map post identifier to corresponding post.
-        pub PostById get(post_by_id) config(): map T::PostId => Post<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId, T::PostId>;
+        pub PostById get(fn post_by_id) config(): map hasher(blake2_128_concat)
+            T::PostId => Post<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId, T::PostId>;
 
         /// Post identifier value to be used for for next post created.
-        pub NextPostId get(next_post_id) config(): T::PostId;
+        pub NextPostId get(fn next_post_id) config(): T::PostId;
 
         /// Account of forum sudo.
-        pub ForumSudo get(forum_sudo) config(): Option<T::AccountId>;
+        pub ForumSudo get(fn forum_sudo) config(): Option<T::AccountId>;
 
         /// Input constraints
         /// These are all forward looking, that is they are enforced on all
         /// future calls.
-        pub CategoryTitleConstraint get(category_title_constraint) config(): InputValidationLengthConstraint;
-        pub CategoryDescriptionConstraint get(category_description_constraint) config(): InputValidationLengthConstraint;
-        pub ThreadTitleConstraint get(thread_title_constraint) config(): InputValidationLengthConstraint;
-        pub PostTextConstraint get(post_text_constraint) config(): InputValidationLengthConstraint;
-        pub ThreadModerationRationaleConstraint get(thread_moderation_rationale_constraint) config(): InputValidationLengthConstraint;
-        pub PostModerationRationaleConstraint get(post_moderation_rationale_constraint) config(): InputValidationLengthConstraint;
+        pub CategoryTitleConstraint get(fn category_title_constraint) config(): InputValidationLengthConstraint;
+        pub CategoryDescriptionConstraint get(fn category_description_constraint) config(): InputValidationLengthConstraint;
+        pub ThreadTitleConstraint get(fn thread_title_constraint) config(): InputValidationLengthConstraint;
+        pub PostTextConstraint get(fn post_text_constraint) config(): InputValidationLengthConstraint;
+        pub ThreadModerationRationaleConstraint get(fn thread_moderation_rationale_constraint) config(): InputValidationLengthConstraint;
+        pub PostModerationRationaleConstraint get(fn post_moderation_rationale_constraint) config(): InputValidationLengthConstraint;
     }
-    /*
-    JUST GIVING UP ON ALL THIS FOR NOW BECAUSE ITS TAKING TOO LONG
-    Review : https://github.com/paritytech/polkadot/blob/620b8610431e7b5fdd71ce3e94c3ee0177406dcc/runtime/src/parachains.rs#L123-L141
-
-    add_extra_genesis {
-
-        // Explain why we need to put this here.
-        config(initial_forum_sudo) : Option<T::AccountId>;
-
-        build(|
-            storage: &mut generator::StorageOverlay,
-            _: &mut generator::ChildrenStorageOverlay,
-            config: &GenesisConfig<T>
-            | {
-
-
-            if let Some(account_id) = &config.initial_forum_sudo {
-                println!("{}: <ForumSudo<T>>::put(account_id)", account_id);
-                <ForumSudo<T> as generator::StorageValue<_>>::put(&account_id, storage);
-            }
-        })
-    }
-    */
 }
 
 decl_event!(
@@ -383,7 +368,8 @@ decl_module! {
         fn deposit_event() = default;
 
         /// Set forum sudo.
-        fn set_forum_sudo(origin, new_forum_sudo: Option<T::AccountId>) -> dispatch::Result {
+        #[weight = 10_000_000] // TODO: adjust weight
+        fn set_forum_sudo(origin, new_forum_sudo: Option<T::AccountId>) -> DispatchResult {
             ensure_root(origin)?;
 
             /*
@@ -408,7 +394,8 @@ decl_module! {
         }
 
         /// Add a new category.
-        fn create_category(origin, parent: Option<CategoryId>, title: Vec<u8>, description: Vec<u8>) -> dispatch::Result {
+        #[weight = 10_000_000] // TODO: adjust weight
+        fn create_category(origin, parent: Option<CategoryId>, title: Vec<u8>, description: Vec<u8>) -> DispatchResult {
 
             // Check that its a valid signature
             let who = ensure_signed(origin)?;
@@ -487,7 +474,8 @@ decl_module! {
         }
 
         /// Update category
-        fn update_category(origin, category_id: CategoryId, new_archival_status: Option<bool>, new_deletion_status: Option<bool>) -> dispatch::Result {
+        #[weight = 10_000_000] // TODO: adjust weight
+        fn update_category(origin, category_id: CategoryId, new_archival_status: Option<bool>, new_deletion_status: Option<bool>) -> DispatchResult {
 
             // Check that its a valid signature
             let who = ensure_signed(origin)?;
@@ -550,7 +538,8 @@ decl_module! {
         }
 
         /// Create new thread in category
-        fn create_thread(origin, category_id: CategoryId, title: Vec<u8>, text: Vec<u8>) -> dispatch::Result {
+        #[weight = 10_000_000] // TODO: adjust weight
+        fn create_thread(origin, category_id: CategoryId, title: Vec<u8>, text: Vec<u8>) -> DispatchResult {
 
             /*
              * Update SPEC with new errors,
@@ -593,7 +582,8 @@ decl_module! {
         }
 
         /// Moderate thread
-        fn moderate_thread(origin, thread_id: T::ThreadId, rationale: Vec<u8>) -> dispatch::Result {
+        #[weight = 10_000_000] // TODO: adjust weight
+        fn moderate_thread(origin, thread_id: T::ThreadId, rationale: Vec<u8>) -> DispatchResult {
 
             // Check that its a valid signature
             let who = ensure_signed(origin)?;
@@ -644,7 +634,8 @@ decl_module! {
         }
 
         /// Edit post text
-        fn add_post(origin, thread_id: T::ThreadId, text: Vec<u8>) -> dispatch::Result {
+        #[weight = 10_000_000] // TODO: adjust weight
+        fn add_post(origin, thread_id: T::ThreadId, text: Vec<u8>) -> DispatchResult {
 
             /*
              * Update SPEC with new errors,
@@ -681,7 +672,8 @@ decl_module! {
         }
 
         /// Edit post text
-        fn edit_post_text(origin, post_id: T::PostId, new_text: Vec<u8>) -> dispatch::Result {
+        #[weight = 10_000_000] // TODO: adjust weight
+        fn edit_post_text(origin, post_id: T::PostId, new_text: Vec<u8>) -> DispatchResult {
 
             /* Edit spec.
               - forum member guard missing
@@ -728,7 +720,8 @@ decl_module! {
         }
 
         /// Moderate post
-        fn moderate_post(origin, post_id: T::PostId, rationale: Vec<u8>) -> dispatch::Result {
+        #[weight = 10_000_000] // TODO: adjust weight
+        fn moderate_post(origin, post_id: T::PostId, rationale: Vec<u8>) -> DispatchResult {
 
             // Check that its a valid signature
             let who = ensure_signed(origin)?;
@@ -772,7 +765,7 @@ decl_module! {
 }
 
 impl<T: Trait> Module<T> {
-    fn ensure_category_title_is_valid(title: &[u8]) -> dispatch::Result {
+    fn ensure_category_title_is_valid(title: &[u8]) -> DispatchResult {
         CategoryTitleConstraint::get().ensure_valid(
             title.len(),
             ERROR_CATEGORY_TITLE_TOO_SHORT,
@@ -780,7 +773,7 @@ impl<T: Trait> Module<T> {
         )
     }
 
-    fn ensure_category_description_is_valid(description: &[u8]) -> dispatch::Result {
+    fn ensure_category_description_is_valid(description: &[u8]) -> DispatchResult {
         CategoryDescriptionConstraint::get().ensure_valid(
             description.len(),
             ERROR_CATEGORY_DESCRIPTION_TOO_SHORT,
@@ -788,7 +781,7 @@ impl<T: Trait> Module<T> {
         )
     }
 
-    fn ensure_thread_moderation_rationale_is_valid(rationale: &[u8]) -> dispatch::Result {
+    fn ensure_thread_moderation_rationale_is_valid(rationale: &[u8]) -> DispatchResult {
         ThreadModerationRationaleConstraint::get().ensure_valid(
             rationale.len(),
             ERROR_THREAD_MODERATION_RATIONALE_TOO_SHORT,
@@ -796,7 +789,7 @@ impl<T: Trait> Module<T> {
         )
     }
 
-    fn ensure_thread_title_is_valid(title: &[u8]) -> dispatch::Result {
+    fn ensure_thread_title_is_valid(title: &[u8]) -> DispatchResult {
         ThreadTitleConstraint::get().ensure_valid(
             title.len(),
             ERROR_THREAD_TITLE_TOO_SHORT,
@@ -804,7 +797,7 @@ impl<T: Trait> Module<T> {
         )
     }
 
-    fn ensure_post_text_is_valid(text: &[u8]) -> dispatch::Result {
+    fn ensure_post_text_is_valid(text: &[u8]) -> DispatchResult {
         PostTextConstraint::get().ensure_valid(
             text.len(),
             ERROR_POST_TEXT_TOO_SHORT,
@@ -812,7 +805,7 @@ impl<T: Trait> Module<T> {
         )
     }
 
-    fn ensure_post_moderation_rationale_is_valid(rationale: &[u8]) -> dispatch::Result {
+    fn ensure_post_moderation_rationale_is_valid(rationale: &[u8]) -> DispatchResult {
         PostModerationRationaleConstraint::get().ensure_valid(
             rationale.len(),
             ERROR_POST_MODERATION_RATIONALE_TOO_SHORT,
@@ -840,7 +833,7 @@ impl<T: Trait> Module<T> {
         post_id: T::PostId,
     ) -> Result<Post<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId, T::PostId>, &'static str>
     {
-        if <PostById<T>>::exists(post_id) {
+        if <PostById<T>>::contains_key(post_id) {
             Ok(<PostById<T>>::get(post_id))
         } else {
             Err(ERROR_POST_DOES_NOT_EXIST)
@@ -865,7 +858,7 @@ impl<T: Trait> Module<T> {
     fn ensure_thread_exists(
         thread_id: T::ThreadId,
     ) -> Result<Thread<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId>, &'static str> {
-        if <ThreadById<T>>::exists(thread_id) {
+        if <ThreadById<T>>::contains_key(thread_id) {
             Ok(<ThreadById<T>>::get(thread_id))
         } else {
             Err(ERROR_THREAD_DOES_NOT_EXIST)
@@ -879,7 +872,7 @@ impl<T: Trait> Module<T> {
         }
     }
 
-    fn ensure_is_forum_sudo(account_id: &T::AccountId) -> dispatch::Result {
+    fn ensure_is_forum_sudo(account_id: &T::AccountId) -> DispatchResult {
         let forum_sudo_account = Self::ensure_forum_sudo_set()?;
 
         ensure!(
@@ -901,7 +894,7 @@ impl<T: Trait> Module<T> {
         }
     }
 
-    fn ensure_catgory_is_mutable(category_id: CategoryId) -> dispatch::Result {
+    fn ensure_catgory_is_mutable(category_id: CategoryId) -> DispatchResult {
         let category_tree_path = Self::build_category_tree_path(category_id);
 
         Self::ensure_can_mutate_in_path_leaf(&category_tree_path)
@@ -913,7 +906,7 @@ impl<T: Trait> Module<T> {
     #[allow(clippy::ptr_arg)]
     fn ensure_can_mutate_in_path_leaf(
         category_tree_path: &CategoryTreePath<T::BlockNumber, T::Moment, T::AccountId>,
-    ) -> dispatch::Result {
+    ) -> DispatchResult {
         // Is parent category directly or indirectly deleted or archived category
         ensure!(
             !category_tree_path.iter().any(
@@ -930,7 +923,7 @@ impl<T: Trait> Module<T> {
     #[allow(clippy::ptr_arg)] // disable it because of possible frontend API break
     fn ensure_can_add_subcategory_path_leaf(
         category_tree_path: &CategoryTreePath<T::BlockNumber, T::Moment, T::AccountId>,
-    ) -> dispatch::Result {
+    ) -> DispatchResult {
         Self::ensure_can_mutate_in_path_leaf(category_tree_path)?;
 
         // Does adding a new category exceed maximum depth
@@ -948,7 +941,7 @@ impl<T: Trait> Module<T> {
         category_id: CategoryId,
     ) -> Result<CategoryTreePath<T::BlockNumber, T::Moment, T::AccountId>, &'static str> {
         ensure!(
-            <CategoryById<T>>::exists(&category_id),
+            <CategoryById<T>>::contains_key(&category_id),
             ERROR_CATEGORY_DOES_NOT_EXIST
         );
 
@@ -990,7 +983,7 @@ impl<T: Trait> Module<T> {
 
         // Make recursive call on parent if we are not at root
         if let Some(child_position_in_parent) = position_in_parent_category_field {
-            assert!(<CategoryById<T>>::exists(
+            assert!(<CategoryById<T>>::contains_key(
                 &child_position_in_parent.parent_id
             ));
 

+ 39 - 33
runtime-modules/forum/src/mock.rs

@@ -3,22 +3,19 @@
 use crate::*;
 use common::BlockAndTime;
 
-use primitives::H256;
-
-use crate::{GenesisConfig, Module, Trait};
-use runtime_primitives::{
+use frame_support::{impl_outer_origin, parameter_types};
+use sp_core::H256;
+use sp_runtime::{
     testing::Header,
     traits::{BlakeTwo256, IdentityLookup},
     Perbill,
 };
-use srml_support::{impl_outer_origin, parameter_types};
 
 /// Module which has a full Substrate module for
 /// mocking behaviour of MembershipRegistry
 pub mod registry {
 
     use super::*;
-    // use srml_support::*;
 
     #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
     pub struct Member<AccountId> {
@@ -27,9 +24,8 @@ pub mod registry {
 
     decl_storage! {
         trait Store for Module<T: Trait> as MockForumUserRegistry {
-
-            pub ForumUserById get(forum_user_by_id) config(): map T::AccountId => Member<T::AccountId>;
-
+            pub ForumUserById get(fn forum_user_by_id) config(): map hasher(blake2_128_concat)
+                T::AccountId => Member<T::AccountId>;
         }
     }
 
@@ -45,7 +41,7 @@ pub mod registry {
 
     impl<T: Trait> ForumUserRegistry<T::AccountId> for Module<T> {
         fn get_forum_user(id: &T::AccountId) -> Option<ForumUser<T::AccountId>> {
-            if <ForumUserById<T>>::exists(id) {
+            if <ForumUserById<T>>::contains_key(id) {
                 let m = <ForumUserById<T>>::get(id);
 
                 Some(ForumUser { id: m.id })
@@ -74,25 +70,33 @@ parameter_types! {
 }
 
 impl system::Trait for Runtime {
+    type BaseCallFilter = ();
     type Origin = Origin;
+    type Call = ();
     type Index = u64;
     type BlockNumber = u64;
-    type Call = ();
     type Hash = H256;
     type Hashing = BlakeTwo256;
     type AccountId = u64;
     type Lookup = IdentityLookup<Self::AccountId>;
     type Header = Header;
-    // type WeightMultiplierUpdate = ();
     type Event = ();
     type BlockHashCount = BlockHashCount;
     type MaximumBlockWeight = MaximumBlockWeight;
+    type DbWeight = ();
+    type BlockExecutionWeight = ();
+    type ExtrinsicBaseWeight = ();
+    type MaximumExtrinsicWeight = ();
     type MaximumBlockLength = MaximumBlockLength;
     type AvailableBlockRatio = AvailableBlockRatio;
     type Version = ();
+    type ModuleToIndex = ();
+    type AccountData = ();
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
 }
 
-impl timestamp::Trait for Runtime {
+impl pallet_timestamp::Trait for Runtime {
     type Moment = u64;
     type OnTimestampSet = ();
     type MinimumPeriod = MinimumPeriod;
@@ -168,7 +172,7 @@ pub struct CreateCategoryFixture {
     pub parent: Option<CategoryId>,
     pub title: Vec<u8>,
     pub description: Vec<u8>,
-    pub result: dispatch::Result,
+    pub result: DispatchResult,
 }
 
 impl CreateCategoryFixture {
@@ -190,7 +194,7 @@ pub struct UpdateCategoryFixture {
     pub category_id: CategoryId,
     pub new_archival_status: Option<bool>,
     pub new_deletion_status: Option<bool>,
-    pub result: dispatch::Result,
+    pub result: DispatchResult,
 }
 
 impl UpdateCategoryFixture {
@@ -212,7 +216,7 @@ pub struct CreateThreadFixture {
     pub category_id: CategoryId,
     pub title: Vec<u8>,
     pub text: Vec<u8>,
-    pub result: dispatch::Result,
+    pub result: DispatchResult,
 }
 
 impl CreateThreadFixture {
@@ -233,7 +237,7 @@ pub struct CreatePostFixture {
     pub origin: OriginType,
     pub thread_id: RuntimeThreadId,
     pub text: Vec<u8>,
-    pub result: dispatch::Result,
+    pub result: DispatchResult,
 }
 
 impl CreatePostFixture {
@@ -259,7 +263,7 @@ pub fn create_forum_member() -> OriginType {
 pub fn assert_create_category(
     forum_sudo: OriginType,
     parent_category_id: Option<CategoryId>,
-    expected_result: dispatch::Result,
+    expected_result: DispatchResult,
 ) {
     CreateCategoryFixture {
         origin: forum_sudo,
@@ -274,7 +278,7 @@ pub fn assert_create_category(
 pub fn assert_create_thread(
     forum_sudo: OriginType,
     category_id: CategoryId,
-    expected_result: dispatch::Result,
+    expected_result: DispatchResult,
 ) {
     CreateThreadFixture {
         origin: forum_sudo,
@@ -289,7 +293,7 @@ pub fn assert_create_thread(
 pub fn assert_create_post(
     forum_sudo: OriginType,
     thread_id: RuntimeThreadId,
-    expected_result: dispatch::Result,
+    expected_result: DispatchResult,
 ) {
     CreatePostFixture {
         origin: forum_sudo,
@@ -353,7 +357,7 @@ pub fn moderate_thread(
     forum_sudo: OriginType,
     thread_id: RuntimeThreadId,
     rationale: Vec<u8>,
-) -> dispatch::Result {
+) -> DispatchResult {
     TestForumModule::moderate_thread(mock_origin(forum_sudo), thread_id, rationale)
 }
 
@@ -361,28 +365,28 @@ pub fn moderate_post(
     forum_sudo: OriginType,
     post_id: RuntimePostId,
     rationale: Vec<u8>,
-) -> dispatch::Result {
+) -> DispatchResult {
     TestForumModule::moderate_post(mock_origin(forum_sudo), post_id, rationale)
 }
 
-pub fn archive_category(forum_sudo: OriginType, category_id: CategoryId) -> dispatch::Result {
+pub fn archive_category(forum_sudo: OriginType, category_id: CategoryId) -> DispatchResult {
     TestForumModule::update_category(mock_origin(forum_sudo), category_id, Some(true), None)
 }
 
-pub fn unarchive_category(forum_sudo: OriginType, category_id: CategoryId) -> dispatch::Result {
+pub fn unarchive_category(forum_sudo: OriginType, category_id: CategoryId) -> DispatchResult {
     TestForumModule::update_category(mock_origin(forum_sudo), category_id, Some(false), None)
 }
 
-pub fn delete_category(forum_sudo: OriginType, category_id: CategoryId) -> dispatch::Result {
+pub fn delete_category(forum_sudo: OriginType, category_id: CategoryId) -> DispatchResult {
     TestForumModule::update_category(mock_origin(forum_sudo), category_id, None, Some(true))
 }
 
-pub fn undelete_category(forum_sudo: OriginType, category_id: CategoryId) -> dispatch::Result {
+pub fn undelete_category(forum_sudo: OriginType, category_id: CategoryId) -> DispatchResult {
     TestForumModule::update_category(mock_origin(forum_sudo), category_id, None, Some(false))
 }
 
 pub fn assert_not_forum_sudo_cannot_update_category(
-    update_operation: fn(OriginType, CategoryId) -> dispatch::Result,
+    update_operation: fn(OriginType, CategoryId) -> DispatchResult,
 ) {
     let config = default_genesis_config();
     let origin = OriginType::Signed(config.forum_sudo);
@@ -452,24 +456,26 @@ pub fn default_genesis_config() -> GenesisConfig<Runtime> {
 pub type RuntimeMap<K, V> = std::vec::Vec<(K, V)>;
 pub type RuntimeCategory = Category<
     <Runtime as system::Trait>::BlockNumber,
-    <Runtime as timestamp::Trait>::Moment,
+    <Runtime as pallet_timestamp::Trait>::Moment,
     <Runtime as system::Trait>::AccountId,
 >;
 pub type RuntimeThread = Thread<
     <Runtime as system::Trait>::BlockNumber,
-    <Runtime as timestamp::Trait>::Moment,
+    <Runtime as pallet_timestamp::Trait>::Moment,
     <Runtime as system::Trait>::AccountId,
     RuntimeThreadId,
 >;
 pub type RuntimePost = Post<
     <Runtime as system::Trait>::BlockNumber,
-    <Runtime as timestamp::Trait>::Moment,
+    <Runtime as pallet_timestamp::Trait>::Moment,
     <Runtime as system::Trait>::AccountId,
     RuntimeThreadId,
     RuntimePostId,
 >;
-pub type RuntimeBlockchainTimestamp =
-    BlockAndTime<<Runtime as system::Trait>::BlockNumber, <Runtime as timestamp::Trait>::Moment>;
+pub type RuntimeBlockchainTimestamp = BlockAndTime<
+    <Runtime as system::Trait>::BlockNumber,
+    <Runtime as pallet_timestamp::Trait>::Moment,
+>;
 
 pub type RuntimeThreadId = <Runtime as Trait>::ThreadId;
 pub type RuntimePostId = <Runtime as Trait>::PostId;
@@ -516,7 +522,7 @@ pub fn default_mock_forum_user_registry_genesis_config() -> registry::GenesisCon
 // NB!:
 // Wanted to have payload: a: &GenesisConfig<Test>
 // but borrow checker made my life miserabl, so giving up for now.
-pub fn build_test_externalities(config: GenesisConfig<Runtime>) -> runtime_io::TestExternalities {
+pub fn build_test_externalities(config: GenesisConfig<Runtime>) -> sp_io::TestExternalities {
     let mut t = system::GenesisConfig::default()
         .build_storage::<Runtime>()
         .unwrap();

+ 1 - 1
runtime-modules/forum/src/tests.rs

@@ -3,7 +3,7 @@
 use super::*;
 use crate::mock::*;
 
-use srml_support::{assert_err, assert_ok};
+use frame_support::{assert_err, assert_ok};
 
 /*
 * NB!: No test checks for event emission!!!!

+ 30 - 89
runtime-modules/governance/Cargo.toml

@@ -1,100 +1,41 @@
 [package]
-name = 'substrate-governance-module'
-version = '1.0.0'
+name = 'pallet-governance'
+version = '3.0.0'
 authors = ['Joystream contributors']
 edition = '2018'
 
+[dependencies]
+serde = { version = "1.0.101", optional = true, features = ["derive"] }
+codec = { package = 'parity-scale-codec', version = '1.3.1', default-features = false, features = ['derive'] }
+sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
+minting = { package = 'pallet-token-mint', default-features = false, path = '../token-minting'}
+recurringrewards = { package = 'pallet-recurring-reward', default-features = false, path = '../recurring-reward'}
+common = { package = 'pallet-common', default-features = false, path = '../common'}
+
+[dev-dependencies]
+sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+
 [features]
 default = ['std']
 std = [
-	'sr-primitives/std',
-	'srml-support/std',
-	'system/std',
 	'serde',
 	'codec/std',
-	'timestamp/std',
-	'primitives/std',
-	'rstd/std',
-	'common/std',
+	'sp-std/std',
+	'frame-support/std',
+	'system/std',
+	'sp-arithmetic/std',
+	'sp-runtime/std',
+	'pallet-timestamp/std',
 	'membership/std',
 	'minting/std',
-]
-
-[dependencies.sr-primitives]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.srml-support]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-support'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.system]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-system'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.rstd]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-std'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.serde]
-features = ['derive']
-optional = true
-version = '1.0.101'
-
-[dependencies.timestamp]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-timestamp'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.primitives]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.codec]
-default-features = false
-features = ['derive']
-package = 'parity-scale-codec'
-version = '1.0.0'
-
-[dependencies.common]
-default_features = false
-package = 'substrate-common-module'
-path = '../common'
-
-[dependencies.membership]
-default_features = false
-package = 'substrate-membership-module'
-path = '../membership'
-
-[dev-dependencies.runtime-io]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-io'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dev-dependencies.balances]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'srml-balances'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.minting]
-default_features = false
-package = 'substrate-token-mint-module'
-path = '../token-minting'
-
-[dependencies.recurringrewards]
-default_features = false
-package = 'substrate-recurring-reward-module'
-path = '../recurring-reward'
+	'recurringrewards/std',
+	'common/std',
+]

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