Browse Source

Merge branch 'development' into bureaucracy_roles_lifecycle

# Conflicts:
#	runtime-modules/forum/src/lib.rs
#	runtime-modules/forum/src/mock.rs
#	runtime/src/lib.rs
Shamil Gadelshin 4 years ago
parent
commit
1ef7c9063b
52 changed files with 1607 additions and 430 deletions
  1. 3 0
      .gitignore
  2. 38 54
      .travis.yml
  3. 3 2
      Cargo.lock
  4. 13 7
      README.md
  5. 9 1
      devops/dockerfiles/node-and-runtime/Dockerfile
  6. 1 1
      devops/dockerfiles/rust-builder/Dockerfile
  7. 5 0
      devops/git-hooks/pre-commit
  8. 10 0
      devops/git-hooks/pre-push
  9. 1 1
      node/Cargo.toml
  10. 4 2
      node/src/chain_spec.rs
  11. 9 4
      node/src/forum_config/from_serialized.rs
  12. 14 2
      package.json
  13. 1 1
      runtime-modules/content-working-group/src/lib.rs
  14. 55 36
      runtime-modules/forum/src/lib.rs
  15. 17 10
      runtime-modules/forum/src/mock.rs
  16. 6 1
      runtime-modules/governance/Cargo.toml
  17. 169 9
      runtime-modules/governance/src/council.rs
  18. 5 0
      runtime-modules/governance/src/mock.rs
  19. 154 84
      runtime-modules/proposals/codex/src/lib.rs
  20. 16 46
      runtime-modules/proposals/codex/src/proposal_types/parameters.rs
  21. 2 2
      runtime-modules/proposals/codex/src/tests/mock.rs
  22. 55 48
      runtime-modules/proposals/codex/src/tests/mod.rs
  23. 16 4
      runtime-modules/proposals/discussion/src/lib.rs
  24. 2 2
      runtime-modules/proposals/discussion/src/tests/mock.rs
  25. 10 10
      runtime-modules/proposals/discussion/src/tests/mod.rs
  26. 15 0
      runtime-modules/proposals/engine/src/lib.rs
  27. 1 1
      runtime-modules/roles/src/actors.rs
  28. 3 4
      runtime/CHANGELOG.md
  29. 2 2
      runtime/Cargo.toml
  30. 1 1
      runtime/build.rs
  31. 30 12
      runtime/src/lib.rs
  32. 26 33
      runtime/src/migration.rs
  33. 17 12
      runtime/src/test/proposals_integration.rs
  34. 3 0
      scripts/cargo-build.sh
  35. 4 0
      scripts/compute-runtime-blob-hash.sh
  36. 0 1
      scripts/raspberry-cross-build.sh
  37. 11 3
      setup.sh
  38. 8 2
      tests/network-tests/.env
  39. 5 2
      tests/network-tests/package.json
  40. 4 4
      tests/network-tests/src/tests/constantinople/electingCouncilTest.ts
  41. 9 7
      tests/network-tests/src/tests/constantinople/membershipCreationTest.ts
  42. 371 0
      tests/network-tests/src/tests/constantinople/utils/apiWrapper.ts
  43. 0 0
      tests/network-tests/src/tests/constantinople/utils/config.ts
  44. 0 1
      tests/network-tests/src/tests/constantinople/utils/sender.ts
  45. 6 0
      tests/network-tests/src/tests/constantinople/utils/utils.ts
  46. 127 0
      tests/network-tests/src/tests/rome/electingCouncilTest.ts
  47. 94 0
      tests/network-tests/src/tests/rome/membershipCreationTest.ts
  48. 10 10
      tests/network-tests/src/tests/rome/romeRuntimeUpgradeTest.ts
  49. 121 8
      tests/network-tests/src/tests/rome/utils/apiWrapper.ts
  50. 5 0
      tests/network-tests/src/tests/rome/utils/config.ts
  51. 66 0
      tests/network-tests/src/tests/rome/utils/sender.ts
  52. 50 0
      tests/network-tests/src/tests/rome/utils/utils.ts

+ 3 - 0
.gitignore

@@ -22,3 +22,6 @@ yarn*
 
 # Visual Studio Code
 .vscode
+
+# Compiled WASM code
+*.wasm

+ 38 - 54
.travis.yml

@@ -1,90 +1,74 @@
 language: rust
 
+# Caching of the runtime .wasm blob poses a problem.
+# See: https://github.com/Joystream/joystream/issues/466
+# Always starting with a clean slate is probably better, it allows us to ensure
+# the WASM runtime is always rebuilt. It also allows us to detect when certain upstream dependencies
+# 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
+
 rust:
-  - 1.43.0
+  - stable
 
 matrix:
   include:
     - os: linux
       env: TARGET=x86_64-unknown-linux-gnu
-    - os: linux
-      env: TARGET=arm-unknown-linux-gnueabihf
-      services: docker
-    - os: osx
-      env: TARGET=x86_64-apple-darwin
+  # Removed all other plaftorm builds sice the repo is growing in terms of activitly
+  # and CI checks are taking long to start due to concurrent number of jobs
+  # one job should be sufficient for doing all manners of checks and unit nets. Will defer
+  # building all platform binanires and wasm blob to a different system.
 
-before_install:
+install:
+  - rustup install nightly-2020-05-23
+  - rustup target add wasm32-unknown-unknown --toolchain nightly-2020-05-23
+  # travis installs rust using rustup with the "minimal" profile so these tools are not installed by default
   - rustup component add rustfmt
-  - cargo fmt --all -- --check
+  # choosing to add clippy for the nightly toolchain only
+  # becuase we want to run it with nighly to avoid having to set BUILD_DUMMY_WASM_BINARY=0
+  # setting that variable requires changing it in a later run of cargo bulid to ensure runtime/build.rs
+  # actually builds the runtime .. the behaviour is a bit odd so choosing to avoid messing with it.
   - rustup component add clippy
-  - BUILD_DUMMY_WASM_BINARY=1 cargo clippy -- -D warnings
-  - rustup default stable
-  - rustup update nightly
-  - rustup target add wasm32-unknown-unknown --toolchain nightly
-  - cargo test --verbose --all
 
-install:
-  - |
-    if [ "$TARGET" = "arm-unknown-linux-gnueabihf" ]
-    then
-      docker pull joystream/rust-raspberry
-    fi
+before_script:
+  - cargo fmt --all -- --check
 
 script:
-  - |
-    if [ "$TARGET" = "arm-unknown-linux-gnueabihf" ]
-    then
-      docker run -u root \
-        --volume ${TRAVIS_BUILD_DIR}:/home/cross/project \
-          joystream/rust-raspberry \
-        build --release
-      sudo chmod a+r ${TRAVIS_BUILD_DIR}/target/${TARGET}/release/joystream-node
-    else
-      cargo build --release --target=${TARGET}
-    fi
+  # we set release as build type for all steps to benefit from already compiled packages in prior steps
+  # skipping clippy ...
+  - BUILD_DUMMY_WASM_BINARY=1 cargo clippy --release --target=${TARGET} -- -D warnings
+  - BUILD_DUMMY_WASM_BINARY=1 cargo test --release --verbose --all --target=${TARGET}
+  - BUILD_DUMMY_WASM_BINARY=0 WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release --target=${TARGET} -p joystream-node
+  - ls -l ./target/${TARGET}/release/wbuild/joystream-node-runtime/
 
 before_deploy:
   - cp ./target/${TARGET}/release/joystream-node .
-  - |
-    if [ "$TARGET" = "arm-unknown-linux-gnueabihf" ]
-    then
-      export FILENAME="joystream-node-armv7-linux-gnueabihf"
-    else
-      export FILENAME=`./joystream-node --version | sed -e "s/ /-/g"`
-    fi
+  - FILENAME=`./joystream-node --version | sed -e "s/ /-/g"`
   - tar -cf ${FILENAME}.tar ./joystream-node
   - gzip ${FILENAME}.tar
+  - export ASSET=${FILENAME}.tar.gz
 
 deploy:
   - provider: releases
     api_key:
-      secure: QTna4XzKmPrXNA5KnYfLsH8cAKxESLdFbQ5HJ6nvB9reE10SVtg8lZ+ShL+no7TACNBUNt09Qv9HNgs6JcNRJ9QMHEJHKIbMyjplhBtZ+W3l0k+6TL0yeKHZ/OvddDF+vDbpN+y4xBfOf0xqZcNH3lZJTms/NPBn/KT5DpQ3JZ8bibdMP2HSCazfvHLwj38OuLX6VWbFcmN2RAmUR9AXYvk5wWYVw8g1VDzTCxjH1G+dGWH1L9+ZDgFfv7BNSNhPc6V9GghgLVZ+37r/STzTTAQ/gPv+yruglEWUhSAngFfVYUegvTmIeQLi/V+g0tKUS+l7eNX08xz6eZcn0+/32V7P+oEN/dhU84E0kgmiOsiUEGI/KFM+qw9TyX3GtD67UmG5TZrD7OUMIu1qCuPSetsTOK2kvpwlYAn+j5iFB30Uz4hXhOH5dib2zz2I7cYHi1kvzeNQqQOPNDCmaO48bcbRIaeqMAHdsb6scGzh/+CD2V2HOmHlhd+4o1PpX6hAMwmOXAu3bMDi4zlB9Hb1cSZnsYNBHawkD6y45QGepFKpGW/6u5VRPeMK62Gm9wu815C36B4mVg6CVqtZMbk0WYPIk6zfoTft3i04YthKbRO96a5VD9LssVbiSYnudXuZJjSllSZVCi9AKS8JVIS2jC2z+tWkquAesSrwztriRcs=
-    file: ${FILENAME}.tar.gz
+      secure: FfxZGQexxAGT0Skbctl1FuqmEvNHejPDPtNG8Du1ACSGjS7Y+M6o/aPqE6HL158AmddOgndsIPR+HM7VfMDAUMkLTbOhv3nMpDBZu1h25vwk+jHOM65tm5LWUu/ROWBpaAQiG7NKrvtfkNfbNBSETsEbWBt/DPrhlIfSbgsXBFDiid7uRrCiwvDUJ097/EUOJ9OVUrk+O4ebSzfIfKPGPtRU2rQQ0eNX7yX3TCm3jbQm/kplkQNRL9mnAJNxtKuvuko4LqZ6jN4XLoLTHUMjO7E0r6wXVB4GVjA4HA214eLlQD6BhgTbWMDxKgWyuKzPG+2GLKyluSSn0RurSl8tYryXKxKxuN3H1FX9r23a8AzGtpRACJtIePC2YmPuQRSnz2Bw8jlSP2WPLJtXGD036J/wVMj6W9TROm7IBigiC7QlqAqCYNByOnoKyhRCgYyAJZb0Jpa3qWaFhA6b6gCGhyH85QCcrc0q6JAB3oqH8Wfm/K2HVzBobmKaSFu5DpwInNnUXnLWGVzhSt3oCq6ld773izReGdLJtLC2vaJ9rZVaVw29s9M662EEuAGgaVLO/sinZJFeIIaCF4i4zUXwXSLIdfKXGOR0ZibkyT2FS6qPGvl/lLN5IREzD7v/rV8htGMLmw4jpPLNskvRjCHX42ewRRYdMvZzQQOAvSlWcsw=
+    file: ${ASSET}
     on:
       tags: true
-      repo: Joystream/substrate-node-joystream
+      repo: Joystream/joystream
     draft: true
     overwrite: true
     skip_cleanup: true
   - provider: releases
     api_key:
-      secure: QTna4XzKmPrXNA5KnYfLsH8cAKxESLdFbQ5HJ6nvB9reE10SVtg8lZ+ShL+no7TACNBUNt09Qv9HNgs6JcNRJ9QMHEJHKIbMyjplhBtZ+W3l0k+6TL0yeKHZ/OvddDF+vDbpN+y4xBfOf0xqZcNH3lZJTms/NPBn/KT5DpQ3JZ8bibdMP2HSCazfvHLwj38OuLX6VWbFcmN2RAmUR9AXYvk5wWYVw8g1VDzTCxjH1G+dGWH1L9+ZDgFfv7BNSNhPc6V9GghgLVZ+37r/STzTTAQ/gPv+yruglEWUhSAngFfVYUegvTmIeQLi/V+g0tKUS+l7eNX08xz6eZcn0+/32V7P+oEN/dhU84E0kgmiOsiUEGI/KFM+qw9TyX3GtD67UmG5TZrD7OUMIu1qCuPSetsTOK2kvpwlYAn+j5iFB30Uz4hXhOH5dib2zz2I7cYHi1kvzeNQqQOPNDCmaO48bcbRIaeqMAHdsb6scGzh/+CD2V2HOmHlhd+4o1PpX6hAMwmOXAu3bMDi4zlB9Hb1cSZnsYNBHawkD6y45QGepFKpGW/6u5VRPeMK62Gm9wu815C36B4mVg6CVqtZMbk0WYPIk6zfoTft3i04YthKbRO96a5VD9LssVbiSYnudXuZJjSllSZVCi9AKS8JVIS2jC2z+tWkquAesSrwztriRcs=
-    file: ${FILENAME}.tar.gz
+      secure: FfxZGQexxAGT0Skbctl1FuqmEvNHejPDPtNG8Du1ACSGjS7Y+M6o/aPqE6HL158AmddOgndsIPR+HM7VfMDAUMkLTbOhv3nMpDBZu1h25vwk+jHOM65tm5LWUu/ROWBpaAQiG7NKrvtfkNfbNBSETsEbWBt/DPrhlIfSbgsXBFDiid7uRrCiwvDUJ097/EUOJ9OVUrk+O4ebSzfIfKPGPtRU2rQQ0eNX7yX3TCm3jbQm/kplkQNRL9mnAJNxtKuvuko4LqZ6jN4XLoLTHUMjO7E0r6wXVB4GVjA4HA214eLlQD6BhgTbWMDxKgWyuKzPG+2GLKyluSSn0RurSl8tYryXKxKxuN3H1FX9r23a8AzGtpRACJtIePC2YmPuQRSnz2Bw8jlSP2WPLJtXGD036J/wVMj6W9TROm7IBigiC7QlqAqCYNByOnoKyhRCgYyAJZb0Jpa3qWaFhA6b6gCGhyH85QCcrc0q6JAB3oqH8Wfm/K2HVzBobmKaSFu5DpwInNnUXnLWGVzhSt3oCq6ld773izReGdLJtLC2vaJ9rZVaVw29s9M662EEuAGgaVLO/sinZJFeIIaCF4i4zUXwXSLIdfKXGOR0ZibkyT2FS6qPGvl/lLN5IREzD7v/rV8htGMLmw4jpPLNskvRjCHX42ewRRYdMvZzQQOAvSlWcsw=
+    file: ${ASSET}
     on:
       branch: development
-      repo: Joystream/substrate-node-joystream
+      repo: Joystream/joystream
     draft: true
     prerelease: true
     overwrite: true
     skip_cleanup: true
-  - provider: releases
-    api_key:
-      secure: ZoEXp8g+yZOEG8JZ1Fg6tWnW3aYDfupFbZflEejYaAdXhj1nw7G9N10ZX5VDdb/O1iFx8BhfFymQxk0ynxNC8c52LzOjKIhXEporxgvEPdnoPS/N1JhfsOUV0ragwZDLv2tFVi2AT0K4w8WJFJDzrK4qHOMMQgVbVQZtFmDL1whHdfBD5FyFyKmMdZdWBtTGy4s7X0LnmxjNB4/3AMa540T3LowZ5H66MYZkQmAbtg8ib93WomVanhS23vbjNaH9x1Kmzxd2B3pCSgI8uaxBrpmzINvAeSusYVJQt0EF/cAPXmq0+JmGoocvcS1ecg/SNZoKUNmeElB4ns/obg/QAyE+fyQtyl+iDYBilhFLm5xRMUnqkpyeUUD3u824i/Z+/tfLvtm5Egg1QAiXtIIJMeAj1nN8OIeSlHR4phnSTA3jl2PZw9QYidtV9WCqHC0qxtpkYSKkC8ItaefScPB1AuvOvVx8xvnIxfR/tXvL8Y3Y2BvhiLgpky9JkbdMln1b0m0E5c4vyGCEVqHqpbxM63VJkpct8sVx0atGvipWEelVjz5XpkxW2PYbgg4EKUzl3FiYcXwf5Y/ykxaZNZt7I4gv9nz2KkVwUCCPqdwWF7ww1shFWW5tCoCmJuUESOdPFx0jQ7LVWz7SDLDsqvvaW2c2OPxG6DIx9BiTeAE4qIQ=
-    file: "${FILENAME}.tar.gz"
-    skip_cleanup: true
-    draft: true
-    prerelease: true
-    overwrite: true
-    on:
-      repo: mnaamani/substrate-node-joystream
-      branch: deploy

+ 3 - 2
Cargo.lock

@@ -1569,7 +1569,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node"
-version = "2.1.6"
+version = "2.2.0"
 dependencies = [
  "ctrlc",
  "derive_more 0.14.1",
@@ -1614,7 +1614,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node-runtime"
-version = "6.12.2"
+version = "6.13.0"
 dependencies = [
  "parity-scale-codec",
  "safe-mix",
@@ -4869,6 +4869,7 @@ dependencies = [
  "substrate-common-module",
  "substrate-membership-module",
  "substrate-primitives",
+ "substrate-recurring-reward-module",
  "substrate-token-mint-module",
 ]
 

+ 13 - 7
README.md

@@ -1,12 +1,18 @@
-# Joystream
+# Joystream [![Build Status](https://travis-ci.org/Joystream/joystream.svg?branch=master)](https://travis-ci.org/Joystream/joystream)
 
 This is the main code reposity for all joystream software. It will  house the substrate chain project, the full node and runtime and all reusable substrate runtime modules that make up the joystream runtime. In addition to all front-end apps and infrastructure servers necessary for operating the network.
 
 The repository is currently just a cargo workspace, but eventually will also contain yarn workspaces, and possibly other project type workspaces.
 
+## Build Status
+
+Development [![Development Branch Build Status](https://travis-ci.org/Joystream/joystream.svg?branch=development)](https://travis-ci.org/Joystream/joystream)
+
+More detailed build history on [Travis CI](https://travis-ci.org/github/Joystream/joystream/builds)
+
 ## Overview
 
-The joystream network builds on a pre-release version of [substrate v2.0](https://substrate.dev/) and adds additional
+The Joystream network builds on a pre-release version of [substrate v2.0](https://substrate.dev/) and adds additional
 functionality to support the [various roles](https://www.joystream.org/roles) that can be entered into on the platform.
 
 
@@ -20,7 +26,7 @@ To setup a full node and validator review the [advanced guide from the helpdesk]
 
 ###  Pre-built Binaries
 
-The latest pre-built binaries can be downloads from the [releases](https://github.com/Joystream/substrate-runtime-joystream/releases) page.
+The latest pre-built binaries can be downloaded from the [releases](https://github.com/Joystream/joystream/releases) page.
 
 
 ### Building from source
@@ -28,9 +34,9 @@ The latest pre-built binaries can be downloads from the [releases](https://githu
 Clone the repository and install build tools:
 
 ```bash
-git clone https://github.com/Joystream/substrate-runtime-joystream.git
+git clone https://github.com/Joystream/joystream.git
 
-cd substrate-runtime-joystream/
+cd joystream/
 
 ./setup.sh
 ```
@@ -49,7 +55,7 @@ Run the node and connect to the public testnet.
 cargo run --release -- --chain ./rome-tesnet.json
 ```
 
-The `rome-testnet.json` chain file can be ontained from the [release page](https://github.com/Joystream/substrate-runtime-joystream/releases/tag/v6.8.0)
+The `rome-testnet.json` chain file can be obtained from the [releases page](https://github.com/Joystream/joystream/releases/tag/v6.8.0)
 
 
 ### Installing a release build
@@ -113,7 +119,7 @@ Deploying the compiled runtime on a live system can be done in one of two ways:
 ### Versioning the runtime
 
 Versioning of the runtime is set in `runtime/src/lib.rs`
-For detailed information about how to set correct version numbers when developing a new runtime, [see this](https://github.com/Joystream/substrate-runtime-joystream/issues/1)
+For detailed information about how to set correct version numbers when developing a new runtime, [see this](https://github.com/Joystream/joystream/issues/1)
 
 
 ## Coding style

+ 9 - 1
devops/dockerfiles/node-and-runtime/Dockerfile

@@ -3,7 +3,8 @@ LABEL description="Compiles all workspace artifacts"
 WORKDIR /joystream
 COPY . /joystream
 
-RUN cargo build --release
+# Build joystream-node and its dependencies - runtime
+RUN cargo build --release -p joystream-node
 
 FROM debian:stretch
 LABEL description="Joystream node"
@@ -14,6 +15,13 @@ COPY --from=builder /joystream/target/release/wbuild/joystream-node-runtime/joys
 # confirm it works
 RUN /joystream/node --version
 
+# https://manpages.debian.org/stretch/coreutils/b2sum.1.en.html
+# RUN apt-get install coreutils
+# print the blake2 256 hash of the wasm blob
+RUN b2sum -l 256 /joystream/runtime.compact.wasm
+# print the blake2 512 hash of the wasm blob
+RUN b2sum -l 512 /joystream/runtime.compact.wasm
+
 EXPOSE 30333 9933 9944
 
 # Use these volumes to persits chain state and keystore, eg.:

+ 1 - 1
devops/dockerfiles/rust-builder/Dockerfile

@@ -1,4 +1,4 @@
-FROM liuchong/rustup:1.42.0 AS builder
+FROM liuchong/rustup:1.43.0 AS builder
 LABEL description="Rust and WASM build environment for joystream and substrate"
 
 WORKDIR /setup

+ 5 - 0
devops/git-hooks/pre-commit

@@ -0,0 +1,5 @@
+#!/bin/sh
+set -e
+
+echo 'cargo fmt --all -- --check'
+cargo fmt --all -- --check

+ 10 - 0
devops/git-hooks/pre-push

@@ -0,0 +1,10 @@
+#!/bin/sh
+set -e
+
+echo '+cargo test --release --all'
+BUILD_DUMMY_WASM_BINARY=1 cargo test --all
+
+echo '+cargo clippy --release --all -- -D warnings'
+BUILD_DUMMY_WASM_BINARY=1 cargo clippy --all -- -D warnings
+
+

+ 1 - 1
node/Cargo.toml

@@ -3,7 +3,7 @@ authors = ['Joystream']
 build = 'build.rs'
 edition = '2018'
 name = 'joystream-node'
-version = '2.1.6'
+version = '2.2.0'
 default-run = "joystream-node"
 
 [[bin]]

+ 4 - 2
node/src/chain_spec.rs

@@ -23,8 +23,9 @@ use node_runtime::{
     AuthorityDiscoveryConfig, BabeConfig, Balance, BalancesConfig, ContentWorkingGroupConfig,
     CouncilConfig, CouncilElectionConfig, DataObjectStorageRegistryConfig,
     DataObjectTypeRegistryConfig, ElectionParameters, GrandpaConfig, ImOnlineConfig, IndicesConfig,
-    MembersConfig, Perbill, ProposalsCodexConfig, SessionConfig, SessionKeys, Signature,
-    StakerStatus, StakingConfig, SudoConfig, SystemConfig, VersionedStoreConfig, DAYS, WASM_BINARY,
+    MembersConfig, MigrationConfig, Perbill, ProposalsCodexConfig, SessionConfig, SessionKeys,
+    Signature, StakerStatus, StakingConfig, SudoConfig, SystemConfig, VersionedStoreConfig, DAYS,
+    WASM_BINARY,
 };
 pub use node_runtime::{AccountId, GenesisConfig};
 use primitives::{sr25519, Pair, Public};
@@ -301,6 +302,7 @@ pub fn testnet_genesis(
             channel_banner_constraint: crate::forum_config::new_validation(5, 1024),
             channel_title_constraint: crate::forum_config::new_validation(5, 1024),
         }),
+        migration: Some(MigrationConfig {}),
         proposals_codex: Some(ProposalsCodexConfig {
             set_validator_count_proposal_voting_period: cpcp
                 .set_validator_count_proposal_voting_period,

+ 9 - 4
node/src/forum_config/from_serialized.rs

@@ -1,7 +1,9 @@
+#![allow(clippy::type_complexity)]
+
 use super::new_validation;
 use node_runtime::{
-    forum::{Category, CategoryId, Post, PostId, Thread, ThreadId},
-    AccountId, BlockNumber, ForumConfig, Moment,
+    forum::{Category, CategoryId, Post, Thread},
+    AccountId, BlockNumber, ForumConfig, Moment, PostId, ThreadId,
 };
 use serde::Deserialize;
 use serde_json::Result;
@@ -9,8 +11,11 @@ use serde_json::Result;
 #[derive(Deserialize)]
 struct ForumData {
     categories: Vec<(CategoryId, Category<BlockNumber, Moment, AccountId>)>,
-    posts: Vec<(PostId, Post<BlockNumber, Moment, AccountId>)>,
-    threads: Vec<(ThreadId, Thread<BlockNumber, Moment, AccountId>)>,
+    posts: Vec<(
+        PostId,
+        Post<BlockNumber, Moment, AccountId, ThreadId, PostId>,
+    )>,
+    threads: Vec<(ThreadId, Thread<BlockNumber, Moment, AccountId, ThreadId>)>,
 }
 
 fn parse_forum_json() -> Result<ForumData> {

+ 14 - 2
package.json

@@ -3,9 +3,21 @@
 	"name": "joystream",
 	"license": "GPL-3.0-only",
 	"scripts": {
-		"test": "yarn && yarn workspaces run test"
+		"test": "yarn && yarn workspaces run test",
+		"test-migration": "yarn && yarn workspaces run test-migration",
+		"cargo-checks": "devops/git-hooks/pre-commit && devops/git-hooks/pre-push",
+		"cargo-build": "scripts/cargo-build.sh"
 	},
 	"workspaces": [
 		"tests/network-tests"
-	]
+	],
+	"devDependencies": {
+		"husky": "^4.2.5"
+	},
+	"husky": {
+	  "hooks": {
+		"pre-commit": "devops/git-hooks/pre-commit",
+		"pre-push": "devops/git-hooks/pre-push"
+	  }
+	}
 }

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

@@ -1892,7 +1892,7 @@ decl_module! {
             origin,
             curator_id: CuratorId<T>,
             rationale_text: Vec<u8>
-            ) {
+        ) {
 
             // Ensure lead is set and is origin signer
             Self::ensure_origin_is_set_lead(origin)?;

+ 55 - 36
runtime-modules/forum/src/lib.rs

@@ -9,11 +9,12 @@
 #[cfg(feature = "std")]
 use serde_derive::{Deserialize, Serialize};
 
-use codec::{Decode, Encode};
+use codec::{Codec, Decode, Encode};
 use rstd::borrow::ToOwned;
 use rstd::prelude::*;
 use runtime_primitives::traits::EnsureOrigin;
-use srml_support::{decl_event, decl_module, decl_storage, dispatch, ensure};
+use runtime_primitives::traits::{MaybeSerialize, Member, One, SimpleArithmetic};
+use srml_support::{decl_event, decl_module, decl_storage, dispatch, ensure, Parameter};
 use system::{ensure_signed, RawOrigin};
 
 pub use common::constraints::InputValidationLengthConstraint;
@@ -104,13 +105,10 @@ pub struct PostTextChange<BlockNumber, Moment> {
     text: Vec<u8>,
 }
 
-/// Represents a post identifier
-pub type PostId = u64;
-
 /// Represents a thread post
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
-pub struct Post<BlockNumber, Moment, AccountId> {
+pub struct Post<BlockNumber, Moment, AccountId, ThreadId, PostId> {
     /// Post identifier
     id: PostId,
 
@@ -140,13 +138,10 @@ pub struct Post<BlockNumber, Moment, AccountId> {
     author_id: AccountId,
 }
 
-/// Represents a thread identifier
-pub type ThreadId = u64;
-
 /// Represents a thread
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
-pub struct Thread<BlockNumber, Moment, AccountId> {
+pub struct Thread<BlockNumber, Moment, AccountId, ThreadId> {
     /// Thread identifier
     id: ThreadId,
 
@@ -186,7 +181,7 @@ pub struct Thread<BlockNumber, Moment, AccountId> {
     author_id: AccountId,
 }
 
-impl<BlockNumber, Moment, AccountId> Thread<BlockNumber, Moment, AccountId> {
+impl<BlockNumber, Moment, AccountId, ThreadId> Thread<BlockNumber, Moment, AccountId, ThreadId> {
     fn num_posts_ever_created(&self) -> u32 {
         self.num_unmoderated_posts + self.num_moderated_posts
     }
@@ -272,6 +267,26 @@ pub trait Trait: system::Trait + timestamp::Trait + Sized {
 
     /// Checks that provided signed account belongs to the leader
     type EnsureForumLeader: EnsureOrigin<Self::Origin>;
+
+    /// Thread Id type
+    type ThreadId: Parameter
+        + Member
+        + SimpleArithmetic
+        + Codec
+        + Default
+        + Copy
+        + MaybeSerialize
+        + PartialEq;
+
+    /// Post Id type
+    type PostId: Parameter
+        + Member
+        + SimpleArithmetic
+        + Codec
+        + Default
+        + Copy
+        + MaybeSerialize
+        + PartialEq;
 }
 
 decl_storage! {
@@ -284,16 +299,16 @@ decl_storage! {
         pub NextCategoryId get(next_category_id) config(): CategoryId;
 
         /// Map thread identifier to corresponding thread.
-        pub ThreadById get(thread_by_id) config(): map ThreadId => Thread<T::BlockNumber, T::Moment, T::AccountId>;
+        pub ThreadById get(thread_by_id) config(): map 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(): ThreadId;
+        pub NextThreadId get(next_thread_id) config(): T::ThreadId;
 
         /// Map post identifier to corresponding post.
-        pub PostById get(post_by_id) config(): map PostId => Post<T::BlockNumber, T::Moment, T::AccountId>;
+        pub PostById get(post_by_id) config(): map 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(): PostId;
+        pub NextPostId get(next_post_id) config(): T::PostId;
 
         /// Input constraints
         /// These are all forward looking, that is they are enforced on all
@@ -334,6 +349,8 @@ decl_event!(
     pub enum Event<T>
     where
         <T as system::Trait>::AccountId,
+        <T as Trait>::ThreadId,
+        <T as Trait>::PostId,
     {
         /// A category was introduced
         CategoryCreated(CategoryId),
@@ -555,7 +572,7 @@ decl_module! {
         }
 
         /// Moderate thread
-        fn moderate_thread(origin, thread_id: ThreadId, rationale: Vec<u8>) -> dispatch::Result {
+        fn moderate_thread(origin, thread_id: T::ThreadId, rationale: Vec<u8>) -> dispatch::Result {
 
             // Check that its a valid signature
             let who = ensure_signed(origin)?;
@@ -606,7 +623,7 @@ decl_module! {
         }
 
         /// Edit post text
-        fn add_post(origin, thread_id: ThreadId, text: Vec<u8>) -> dispatch::Result {
+        fn add_post(origin, thread_id: T::ThreadId, text: Vec<u8>) -> dispatch::Result {
 
             /*
              * Update SPEC with new errors,
@@ -643,7 +660,7 @@ decl_module! {
         }
 
         /// Edit post text
-        fn edit_post_text(origin, post_id: PostId, new_text: Vec<u8>) -> dispatch::Result {
+        fn edit_post_text(origin, post_id: T::PostId, new_text: Vec<u8>) -> dispatch::Result {
 
             /* Edit spec.
               - forum member guard missing
@@ -690,7 +707,7 @@ decl_module! {
         }
 
         /// Moderate post
-        fn moderate_post(origin, post_id: PostId, rationale: Vec<u8>) -> dispatch::Result {
+        fn moderate_post(origin, post_id: T::PostId, rationale: Vec<u8>) -> dispatch::Result {
 
             // Check that its a valid signature
             let who = ensure_signed(origin)?;
@@ -790,8 +807,9 @@ impl<T: Trait> Module<T> {
     }
 
     fn ensure_post_is_mutable(
-        post_id: PostId,
-    ) -> Result<Post<T::BlockNumber, T::Moment, T::AccountId>, &'static str> {
+        post_id: T::PostId,
+    ) -> Result<Post<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId, T::PostId>, &'static str>
+    {
         // Make sure post exists
         let post = Self::ensure_post_exists(post_id)?;
 
@@ -805,8 +823,9 @@ impl<T: Trait> Module<T> {
     }
 
     fn ensure_post_exists(
-        post_id: PostId,
-    ) -> Result<Post<T::BlockNumber, T::Moment, T::AccountId>, &'static str> {
+        post_id: T::PostId,
+    ) -> Result<Post<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId, T::PostId>, &'static str>
+    {
         if <PostById<T>>::exists(post_id) {
             Ok(<PostById<T>>::get(post_id))
         } else {
@@ -815,8 +834,8 @@ impl<T: Trait> Module<T> {
     }
 
     fn ensure_thread_is_mutable(
-        thread_id: ThreadId,
-    ) -> Result<Thread<T::BlockNumber, T::Moment, T::AccountId>, &'static str> {
+        thread_id: T::ThreadId,
+    ) -> Result<Thread<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId>, &'static str> {
         // Make sure thread exists
         let thread = Self::ensure_thread_exists(thread_id)?;
 
@@ -830,8 +849,8 @@ impl<T: Trait> Module<T> {
     }
 
     fn ensure_thread_exists(
-        thread_id: ThreadId,
-    ) -> Result<Thread<T::BlockNumber, T::Moment, T::AccountId>, &'static str> {
+        thread_id: T::ThreadId,
+    ) -> Result<Thread<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId>, &'static str> {
         if <ThreadById<T>>::exists(thread_id) {
             Ok(<ThreadById<T>>::get(thread_id))
         } else {
@@ -957,12 +976,12 @@ impl<T: Trait> Module<T> {
         category_id: CategoryId,
         title: &[u8],
         author_id: &T::AccountId,
-    ) -> Thread<T::BlockNumber, T::Moment, T::AccountId> {
+    ) -> Thread<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId> {
         // Get category
         let category = <CategoryById<T>>::get(category_id);
 
         // Create and add new thread
-        let new_thread_id = NextThreadId::get();
+        let new_thread_id = NextThreadId::<T>::get();
 
         let new_thread = Thread {
             id: new_thread_id,
@@ -980,8 +999,8 @@ impl<T: Trait> Module<T> {
         <ThreadById<T>>::insert(new_thread_id, new_thread.clone());
 
         // Update next thread id
-        NextThreadId::mutate(|n| {
-            *n += 1;
+        NextThreadId::<T>::mutate(|n| {
+            *n += One::one();
         });
 
         // Update unmoderated thread count in corresponding category
@@ -995,15 +1014,15 @@ impl<T: Trait> Module<T> {
     /// Creates and ads a new post ot the given thread, and makes all required state updates
     /// `thread_id` must be valid
     fn add_new_post(
-        thread_id: ThreadId,
+        thread_id: T::ThreadId,
         text: &[u8],
         author_id: &T::AccountId,
-    ) -> Post<T::BlockNumber, T::Moment, T::AccountId> {
+    ) -> Post<T::BlockNumber, T::Moment, T::AccountId, T::ThreadId, T::PostId> {
         // Get thread
         let thread = <ThreadById<T>>::get(thread_id);
 
         // Make and add initial post
-        let new_post_id = NextPostId::get();
+        let new_post_id = NextPostId::<T>::get();
 
         let new_post = Post {
             id: new_post_id,
@@ -1020,8 +1039,8 @@ impl<T: Trait> Module<T> {
         <PostById<T>>::insert(new_post_id, new_post.clone());
 
         // Update next post id
-        NextPostId::mutate(|n| {
-            *n += 1;
+        NextPostId::<T>::mutate(|n| {
+            *n += One::one();
         });
 
         // Update unmoderated post count of thread

+ 17 - 10
runtime-modules/forum/src/mock.rs

@@ -162,6 +162,8 @@ impl Trait for Runtime {
     type Event = ();
     type MembershipRegistry = registry::TestMembershipRegistryModule;
     type EnsureForumLeader = bureaucracy::Module<Runtime, bureaucracy::Instance1>;
+    type ThreadId = u64;
+    type PostId = u64;
 }
 
 #[derive(Clone)]
@@ -187,9 +189,9 @@ pub const NOT_MEMBER_ORIGIN: OriginType = OriginType::Signed(222);
 
 pub const INVLAID_CATEGORY_ID: CategoryId = 333;
 
-pub const INVLAID_THREAD_ID: ThreadId = 444;
+pub const INVLAID_THREAD_ID: RuntimeThreadId = 444;
 
-pub const INVLAID_POST_ID: ThreadId = 555;
+pub const INVLAID_POST_ID: RuntimePostId = 555;
 
 pub(crate) const FORUM_SUDO_ID: u64 = 33;
 
@@ -307,7 +309,7 @@ impl CreateThreadFixture {
 
 pub struct CreatePostFixture {
     pub origin: OriginType,
-    pub thread_id: ThreadId,
+    pub thread_id: RuntimeThreadId,
     pub text: Vec<u8>,
     pub result: dispatch::Result,
 }
@@ -364,7 +366,7 @@ pub fn assert_create_thread(
 
 pub fn assert_create_post(
     forum_sudo: OriginType,
-    thread_id: ThreadId,
+    thread_id: RuntimeThreadId,
     expected_result: dispatch::Result,
 ) {
     CreatePostFixture {
@@ -391,7 +393,7 @@ pub fn create_root_category(forum_sudo: OriginType) -> CategoryId {
 
 pub fn create_root_category_and_thread(
     forum_sudo: OriginType,
-) -> (OriginType, CategoryId, ThreadId) {
+) -> (OriginType, CategoryId, RuntimeThreadId) {
     let member_origin = create_forum_member();
     let category_id = create_root_category(forum_sudo);
     let thread_id = TestForumModule::next_thread_id();
@@ -410,7 +412,7 @@ pub fn create_root_category_and_thread(
 
 pub fn create_root_category_and_thread_and_post(
     forum_sudo: OriginType,
-) -> (OriginType, CategoryId, ThreadId, PostId) {
+) -> (OriginType, CategoryId, RuntimeThreadId, RuntimePostId) {
     let (member_origin, category_id, thread_id) = create_root_category_and_thread(forum_sudo);
     let post_id = TestForumModule::next_post_id();
 
@@ -427,7 +429,7 @@ pub fn create_root_category_and_thread_and_post(
 
 pub fn moderate_thread(
     forum_sudo: OriginType,
-    thread_id: ThreadId,
+    thread_id: RuntimeThreadId,
     rationale: Vec<u8>,
 ) -> dispatch::Result {
     TestForumModule::moderate_thread(mock_origin(forum_sudo), thread_id, rationale)
@@ -435,7 +437,7 @@ pub fn moderate_thread(
 
 pub fn moderate_post(
     forum_sudo: OriginType,
-    post_id: PostId,
+    post_id: RuntimePostId,
     rationale: Vec<u8>,
 ) -> dispatch::Result {
     TestForumModule::moderate_post(mock_origin(forum_sudo), post_id, rationale)
@@ -533,23 +535,28 @@ pub type RuntimeThread = Thread<
     <Runtime as system::Trait>::BlockNumber,
     <Runtime as 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 system::Trait>::AccountId,
+    RuntimeThreadId,
+    RuntimePostId,
 >;
 pub type RuntimeBlockchainTimestamp = BlockchainTimestamp<
     <Runtime as system::Trait>::BlockNumber,
     <Runtime as timestamp::Trait>::Moment,
 >;
+pub type RuntimeThreadId = <Runtime as Trait>::ThreadId;
+pub type RuntimePostId = <Runtime as Trait>::PostId;
 
 pub fn genesis_config(
     category_by_id: &RuntimeMap<CategoryId, RuntimeCategory>,
     next_category_id: u64,
-    thread_by_id: &RuntimeMap<ThreadId, RuntimeThread>,
+    thread_by_id: &RuntimeMap<RuntimeThreadId, RuntimeThread>,
     next_thread_id: u64,
-    post_by_id: &RuntimeMap<PostId, RuntimePost>,
+    post_by_id: &RuntimeMap<RuntimePostId, RuntimePost>,
     next_post_id: u64,
     category_title_constraint: &InputValidationLengthConstraint,
     category_description_constraint: &InputValidationLengthConstraint,

+ 6 - 1
runtime-modules/governance/Cargo.toml

@@ -92,4 +92,9 @@ rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 [dependencies.minting]
 default_features = false
 package = 'substrate-token-mint-module'
-path = '../token-minting'
+path = '../token-minting'
+
+[dependencies.recurringrewards]
+default_features = false
+package = 'substrate-recurring-reward-module'
+path = '../recurring-reward'

+ 169 - 9
runtime-modules/governance/src/council.rs

@@ -1,6 +1,6 @@
 use rstd::prelude::*;
-use sr_primitives::traits::Zero;
-use srml_support::{decl_event, decl_module, decl_storage, ensure};
+use sr_primitives::traits::{One, Zero};
+use srml_support::{debug, decl_event, decl_module, decl_storage, ensure};
 use system::{self, ensure_root};
 
 pub use super::election::{self, CouncilElected, Seat, Seats};
@@ -21,7 +21,7 @@ impl<X: CouncilTermEnded> CouncilTermEnded for (X,) {
     }
 }
 
-pub trait Trait: system::Trait + minting::Trait + GovernanceCurrency {
+pub trait Trait: system::Trait + recurringrewards::Trait + GovernanceCurrency {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
     type CouncilTermEnded: CouncilTermEnded;
@@ -37,6 +37,20 @@ decl_storage! {
         /// because it was introduced in a runtime upgrade. It will be automatically created when
         /// a successful call to set_council_mint_capacity() is made.
         pub CouncilMint get(council_mint) : Option<<T as minting::Trait>::MintId>;
+
+        /// The reward relationships currently in place. There may not necessarily be a 1-1 correspondance with
+        /// the active council, since there are multiple ways of setting/adding/removing council members, some of which
+        /// do not involve creating a relationship.
+        pub RewardRelationships get(reward_relationships) : map T::AccountId => T::RewardRelationshipId;
+
+        /// Reward amount paid out at each PayoutInterval
+        pub AmountPerPayout get(amount_per_payout): minting::BalanceOf<T>;
+
+        /// Optional interval in blocks on which a reward payout will be made to each council member
+        pub PayoutInterval get(payout_interval): Option<T::BlockNumber>;
+
+        /// How many blocks after the reward is created, the first payout will be made
+        pub FirstPayoutAfterRewardCreated get(first_payout_after_reward_created): T::BlockNumber;
     }
 }
 
@@ -50,10 +64,23 @@ decl_event!(
 
 impl<T: Trait> CouncilElected<Seats<T::AccountId, BalanceOf<T>>, T::BlockNumber> for Module<T> {
     fn council_elected(seats: Seats<T::AccountId, BalanceOf<T>>, term: T::BlockNumber) {
-        <ActiveCouncil<T>>::put(seats);
+        <ActiveCouncil<T>>::put(seats.clone());
 
         let next_term_ends_at = <system::Module<T>>::block_number() + term;
+
         <TermEndsAt<T>>::put(next_term_ends_at);
+
+        if let Some(reward_source) = Self::council_mint() {
+            for seat in seats.iter() {
+                Self::add_reward_relationship(&seat.member, reward_source);
+            }
+        } else {
+            // Skip trying to create rewards since no mint has been created yet
+            debug::warn!(
+                "Not creating reward relationship for council seats because no mint exists"
+            );
+        }
+
         Self::deposit_event(RawEvent::NewCouncilTermStarted(next_term_ends_at));
     }
 }
@@ -75,6 +102,51 @@ impl<T: Trait> Module<T> {
         CouncilMint::<T>::put(mint_id);
         Ok(mint_id)
     }
+
+    fn add_reward_relationship(destination: &T::AccountId, reward_source: T::MintId) {
+        let recipient = <recurringrewards::Module<T>>::add_recipient();
+
+        // When calculating when first payout occurs, add minimum of one block interval to ensure rewards module
+        // has a chance to execute its on_finalize routine.
+        let next_payout_at = system::Module::<T>::block_number()
+            + Self::first_payout_after_reward_created()
+            + T::BlockNumber::one();
+
+        if let Ok(relationship_id) = <recurringrewards::Module<T>>::add_reward_relationship(
+            reward_source,
+            recipient,
+            destination.clone(),
+            Self::amount_per_payout(),
+            next_payout_at,
+            Self::payout_interval(),
+        ) {
+            RewardRelationships::<T>::insert(destination, relationship_id);
+        } else {
+            debug::warn!("Failed to create a reward relationship for council seat");
+        }
+    }
+
+    fn remove_reward_relationships() {
+        for seat in Self::active_council().into_iter() {
+            if RewardRelationships::<T>::exists(&seat.member) {
+                let id = Self::reward_relationships(&seat.member);
+                <recurringrewards::Module<T>>::remove_reward_relationship(id);
+            }
+        }
+    }
+
+    fn on_term_ended(now: T::BlockNumber) {
+        // Stop paying out rewards when the term ends.
+        // Note: Is it not simpler to just do a single payout at end of term?
+        // During the term the recurring reward module could unfairly pay some but not all council members
+        // If there is insufficient mint capacity.. so doing it at this point offers more control
+        // and a potentially more fair outcome in such a case.
+        Self::remove_reward_relationships();
+
+        Self::deposit_event(RawEvent::CouncilTermEnded(now));
+
+        T::CouncilTermEnded::council_term_ended();
+    }
 }
 
 decl_module! {
@@ -83,16 +155,28 @@ decl_module! {
 
         fn on_finalize(now: T::BlockNumber) {
             if now == Self::term_ends_at() {
-                Self::deposit_event(RawEvent::CouncilTermEnded(now));
-                T::CouncilTermEnded::council_term_ended();
+                Self::on_term_ended(now);
             }
         }
 
         // Privileged methods
 
-        /// Force set a zero staked council. Stakes in existing council will vanish into thin air!
+        /// Force set a zero staked council. Stakes in existing council seats are not returned.
+        /// Existing council rewards are removed and new council members do NOT get any rewards.
+        /// Avoid using this call if possible, will be deprecated. The term of the new council is
+        /// not extended.
         pub fn set_council(origin, accounts: Vec<T::AccountId>) {
             ensure_root(origin)?;
+
+            // Council is being replaced so remove existing reward relationships if they exist
+            Self::remove_reward_relationships();
+
+            if let Some(reward_source) = Self::council_mint() {
+                for account in accounts.clone() {
+                    Self::add_reward_relationship(&account, reward_source);
+                }
+            }
+
             let new_council: Seats<T::AccountId, BalanceOf<T>> = accounts.into_iter().map(|account| {
                 Seat {
                     member: account,
@@ -100,13 +184,20 @@ decl_module! {
                     backers: vec![]
                 }
             }).collect();
+
             <ActiveCouncil<T>>::put(new_council);
         }
 
-        /// Adds a zero staked council member
+        /// Adds a zero staked council member. A member added in this way does not get a recurring reward.
         fn add_council_member(origin, account: T::AccountId) {
             ensure_root(origin)?;
+
             ensure!(!Self::is_councilor(&account), "cannot add same account multiple times");
+
+            if let Some(reward_source) = Self::council_mint() {
+                Self::add_reward_relationship(&account, reward_source);
+            }
+
             let seat = Seat {
                 member: account,
                 stake: BalanceOf::<T>::zero(),
@@ -117,13 +208,22 @@ decl_module! {
             <ActiveCouncil<T>>::mutate(|council| council.push(seat));
         }
 
+        /// Remove a single council member and their reward.
         fn remove_council_member(origin, account_to_remove: T::AccountId) {
             ensure_root(origin)?;
+
             ensure!(Self::is_councilor(&account_to_remove), "account is not a councilor");
+
+            if RewardRelationships::<T>::exists(&account_to_remove) {
+                let relationship_id = Self::reward_relationships(&account_to_remove);
+                <recurringrewards::Module<T>>::remove_reward_relationship(relationship_id);
+            }
+
             let filtered_council: Seats<T::AccountId, BalanceOf<T>> = Self::active_council()
                 .into_iter()
                 .filter(|c| c.member != account_to_remove)
                 .collect();
+
             <ActiveCouncil<T>>::put(filtered_council);
         }
 
@@ -153,7 +253,26 @@ decl_module! {
             if let Some(mint_id) = Self::council_mint() {
                 minting::Module::<T>::transfer_tokens(mint_id, amount, &destination)?;
             } else {
-                return Err("CouncilHashNoMint")
+                return Err("CouncilHasNoMint")
+            }
+        }
+
+        /// Sets the council rewards which is only applied on new council being elected.
+        fn set_council_rewards(
+            origin,
+            amount_per_payout: minting::BalanceOf<T>,
+            payout_interval: Option<T::BlockNumber>,
+            first_payout_after_reward_created: T::BlockNumber
+        ) {
+            ensure_root(origin)?;
+
+            AmountPerPayout::<T>::put(amount_per_payout);
+            FirstPayoutAfterRewardCreated::<T>::put(first_payout_after_reward_created);
+
+            if let Some(payout_interval) = payout_interval {
+                PayoutInterval::<T>::put(payout_interval);
+            } else {
+                PayoutInterval::<T>::take();
             }
         }
     }
@@ -161,6 +280,7 @@ decl_module! {
 
 #[cfg(test)]
 mod tests {
+    use super::*;
     use crate::mock::*;
     use srml_support::*;
 
@@ -212,4 +332,44 @@ mod tests {
             assert!(Council::is_councilor(&6));
         });
     }
+
+    #[test]
+    fn council_elected_test() {
+        initial_test_ext().execute_with(|| {
+            // Ensure a mint is created so we can create rewards
+            assert_ok!(Council::set_council_mint_capacity(
+                system::RawOrigin::Root.into(),
+                1000
+            ));
+
+            Council::council_elected(
+                vec![
+                    Seat {
+                        member: 5,
+                        stake: 0,
+                        backers: vec![],
+                    },
+                    Seat {
+                        member: 6,
+                        stake: 0,
+                        backers: vec![],
+                    },
+                    Seat {
+                        member: 7,
+                        stake: 0,
+                        backers: vec![],
+                    },
+                ],
+                50 as u64, // <Test as system::Trait>::BlockNumber::from(50)
+            );
+
+            assert!(Council::is_councilor(&5));
+            assert!(Council::is_councilor(&6));
+            assert!(Council::is_councilor(&7));
+
+            assert!(RewardRelationships::<Test>::exists(&5));
+            assert!(RewardRelationships::<Test>::exists(&6));
+            assert!(RewardRelationships::<Test>::exists(&7));
+        });
+    }
 }

+ 5 - 0
runtime-modules/governance/src/mock.rs

@@ -74,6 +74,11 @@ impl minting::Trait for Test {
     type Currency = Balances;
     type MintId = u64;
 }
+impl recurringrewards::Trait for Test {
+    type PayoutStatusHandler = ();
+    type RecipientId = u64;
+    type RewardRelationshipId = u64;
+}
 parameter_types! {
     pub const ExistentialDeposit: u32 = 0;
     pub const TransferFee: u32 = 0;

+ 154 - 84
runtime-modules/proposals/codex/src/lib.rs

@@ -61,13 +61,10 @@ use governance::election_params::ElectionParameters;
 use proposal_engine::ProposalParameters;
 use roles::actors::RoleParameters;
 use rstd::clone::Clone;
-use rstd::convert::TryInto;
 use rstd::prelude::*;
 use rstd::str::from_utf8;
 use rstd::vec::Vec;
-use sr_primitives::traits::SaturatedConversion;
-use sr_primitives::traits::{One, Zero};
-use sr_primitives::Perbill;
+use sr_primitives::traits::Zero;
 use srml_support::dispatch::DispatchResult;
 use srml_support::traits::{Currency, Get};
 use srml_support::{decl_error, decl_module, decl_storage, ensure, print};
@@ -76,9 +73,82 @@ use system::{ensure_root, RawOrigin};
 pub use crate::proposal_types::ProposalsConfigParameters;
 pub use proposal_types::{ProposalDetails, ProposalDetailsOf, ProposalEncoder};
 
-// Percentage of the total token issue as max mint balance value. Shared with spending
-// proposal max balance percentage.
-const COUNCIL_MINT_MAX_BALANCE_PERCENT: u32 = 2;
+// 'Set working group mint capacity' proposal limit
+const CONTENT_WORKING_GROUP_MINT_CAPACITY_MAX_VALUE: u32 = 1_000_000;
+// Max allowed value for 'spending' proposal
+const MAX_SPENDING_PROPOSAL_VALUE: u32 = 2_000_000_u32;
+// Max validator count for the 'set validator count' proposal
+const MAX_VALIDATOR_COUNT: u32 = 100;
+// min_actors min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_MIN_ACTORS_MAX_VALUE: u32 = 2;
+// max_actors min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_MAX_ACTORS_MIN_VALUE: u32 = 2;
+// max_actors max value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_MAX_ACTORS_MAX_VALUE: u32 = 100;
+// reward_period min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_REWARD_PERIOD_MIN_VALUE: u32 = 600;
+// reward_period max value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_REWARD_PERIOD_MAX_VALUE: u32 = 3600;
+// bonding_period min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_BONDING_PERIOD_MIN_VALUE: u32 = 600;
+// bonding_period max value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_BONDING_PERIOD_MAX_VALUE: u32 = 28800;
+// unbonding_period min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_UNBONDING_PERIOD_MIN_VALUE: u32 = 600;
+// unbonding_period max value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_UNBONDING_PERIOD_MAX_VALUE: u32 = 28800;
+// min_service_period min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_MIN_SERVICE_PERIOD_MIN_VALUE: u32 = 600;
+// min_service_period max value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_MIN_SERVICE_PERIOD_MAX_VALUE: u32 = 28800;
+// startup_grace_period min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_STARTUP_GRACE_PERIOD_MIN_VALUE: u32 = 600;
+// startup_grace_period max value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_STARTUP_GRACE_PERIOD_MAX_VALUE: u32 = 28800;
+// min_stake min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_MIN_STAKE_MIN_VALUE: u32 = 0;
+// min_stake max value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_MIN_STAKE_MAX_VALUE: u32 = 10_000_000;
+// entry_request_fee min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_ENTRY_REQUEST_FEE_MIN_VALUE: u32 = 0;
+// entry_request_fee max value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_ENTRY_REQUEST_FEE_MAX_VALUE: u32 = 100_000;
+// reward min value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_REWARD_MIN_VALUE: u32 = 0;
+// reward max value for the 'set storage role parameters' proposal
+const ROLE_PARAMETERS_REWARD_MAX_VALUE: u32 = 1000;
+// council_size min value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_COUNCIL_SIZE_MIN_VALUE: u32 = 4;
+// council_size max value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_COUNCIL_SIZE_MAX_VALUE: u32 = 20;
+// candidacy_limit min value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_CANDIDACY_LIMIT_MIN_VALUE: u32 = 25;
+// candidacy_limit max value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_CANDIDACY_LIMIT_MAX_VALUE: u32 = 100;
+// min_voting_stake min value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_MIN_STAKE_MIN_VALUE: u32 = 1;
+// min_voting_stake max value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_MIN_STAKE_MAX_VALUE: u32 = 100_000_u32;
+// new_term_duration min value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_NEW_TERM_DURATION_MIN_VALUE: u32 = 14400;
+// new_term_duration max value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_NEW_TERM_DURATION_MAX_VALUE: u32 = 432_000;
+// revealing_period min value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_REVEALING_PERIOD_MIN_VALUE: u32 = 14400;
+// revealing_period max value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_REVEALING_PERIOD_MAX_VALUE: u32 = 28800;
+// voting_period min value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_VOTING_PERIOD_MIN_VALUE: u32 = 14400;
+// voting_period max value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_VOTING_PERIOD_MAX_VALUE: u32 = 28800;
+// announcing_period min value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_ANNOUNCING_PERIOD_MIN_VALUE: u32 = 14400;
+// announcing_period max value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_ANNOUNCING_PERIOD_MAX_VALUE: u32 = 43200;
+// min_council_stake min value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_MIN_COUNCIL_STAKE_MIN_VALUE: u32 = 1;
+// min_council_stake max value for the 'set election parameters' proposal
+const ELECTION_PARAMETERS_MIN_COUNCIL_STAKE_MAX_VALUE: u32 = 100_000_u32;
 
 /// 'Proposals codex' substrate module Trait
 pub trait Trait:
@@ -340,6 +410,12 @@ decl_module! {
         /// Predefined errors
         type Error = Error;
 
+        /// Exports max allowed text proposal length const.
+        const TextProposalMaxLength: u32 = T::TextProposalMaxLength::get();
+
+        /// Exports max wasm code length of the runtime upgrade proposal const.
+        const RuntimeUpgradeWasmProposalMaxLength: u32 = T::RuntimeUpgradeWasmProposalMaxLength::get();
+
         /// Create 'Text (signal)' proposal type.
         pub fn create_text_proposal(
             origin,
@@ -440,12 +516,8 @@ decl_module! {
             stake_balance: Option<BalanceOf<T>>,
             mint_balance: BalanceOfMint<T>,
         ) {
-
-            let max_mint_capacity: u32 = get_required_stake_by_fraction::<T>(1, 100)
-                .try_into()
-                .unwrap_or_default() as u32;
             ensure!(
-                mint_balance < <BalanceOfMint<T>>::from(max_mint_capacity),
+                mint_balance <= <BalanceOfMint<T>>::from(CONTENT_WORKING_GROUP_MINT_CAPACITY_MAX_VALUE),
                 Error::InvalidStorageWorkingGroupMintCapacity
             );
 
@@ -478,16 +550,8 @@ decl_module! {
             destination: T::AccountId,
         ) {
             ensure!(balance != BalanceOfMint::<T>::zero(), Error::InvalidSpendingProposalBalance);
-
-            let max_balance: u32 = get_required_stake_by_fraction::<T>(
-                COUNCIL_MINT_MAX_BALANCE_PERCENT,
-                100
-            )
-            .try_into()
-            .unwrap_or_default() as u32;
-
             ensure!(
-                balance < <BalanceOfMint<T>>::from(max_balance),
+                balance <= <BalanceOfMint<T>>::from(MAX_SPENDING_PROPOSAL_VALUE),
                 Error::InvalidSpendingProposalBalance
             );
 
@@ -586,7 +650,7 @@ decl_module! {
             );
 
             ensure!(
-                new_validator_count <= 1000, // max validator count
+                new_validator_count <= MAX_VALIDATOR_COUNT,
                 Error::InvalidValidatorCount
             );
 
@@ -741,110 +805,117 @@ impl<T: Trait> Module<T> {
         role_parameters: &RoleParameters<BalanceOfGovernanceCurrency<T>, T::BlockNumber>,
     ) -> Result<(), Error> {
         ensure!(
-            role_parameters.min_actors <= 5,
+            role_parameters.min_actors < ROLE_PARAMETERS_MIN_ACTORS_MAX_VALUE,
             Error::InvalidStorageRoleParameterMinActors
         );
 
         ensure!(
-            role_parameters.max_actors >= 5,
+            role_parameters.max_actors >= ROLE_PARAMETERS_MAX_ACTORS_MIN_VALUE,
             Error::InvalidStorageRoleParameterMaxActors
         );
 
         ensure!(
-            role_parameters.max_actors < 100,
+            role_parameters.max_actors < ROLE_PARAMETERS_MAX_ACTORS_MAX_VALUE,
             Error::InvalidStorageRoleParameterMaxActors
         );
 
         ensure!(
-            role_parameters.reward_period >= T::BlockNumber::from(600),
+            role_parameters.reward_period
+                >= T::BlockNumber::from(ROLE_PARAMETERS_REWARD_PERIOD_MIN_VALUE),
             Error::InvalidStorageRoleParameterRewardPeriod
         );
 
         ensure!(
-            role_parameters.reward_period <= T::BlockNumber::from(3600),
+            role_parameters.reward_period
+                <= T::BlockNumber::from(ROLE_PARAMETERS_REWARD_PERIOD_MAX_VALUE),
             Error::InvalidStorageRoleParameterRewardPeriod
         );
 
         ensure!(
-            role_parameters.bonding_period >= T::BlockNumber::from(600),
+            role_parameters.bonding_period
+                >= T::BlockNumber::from(ROLE_PARAMETERS_BONDING_PERIOD_MIN_VALUE),
             Error::InvalidStorageRoleParameterBondingPeriod
         );
 
         ensure!(
-            role_parameters.bonding_period <= T::BlockNumber::from(28800),
+            role_parameters.bonding_period
+                <= T::BlockNumber::from(ROLE_PARAMETERS_BONDING_PERIOD_MAX_VALUE),
             Error::InvalidStorageRoleParameterBondingPeriod
         );
 
         ensure!(
-            role_parameters.unbonding_period >= T::BlockNumber::from(600),
+            role_parameters.unbonding_period
+                >= T::BlockNumber::from(ROLE_PARAMETERS_UNBONDING_PERIOD_MIN_VALUE),
             Error::InvalidStorageRoleParameterUnbondingPeriod
         );
 
         ensure!(
-            role_parameters.unbonding_period <= T::BlockNumber::from(28800),
+            role_parameters.unbonding_period
+                <= T::BlockNumber::from(ROLE_PARAMETERS_UNBONDING_PERIOD_MAX_VALUE),
             Error::InvalidStorageRoleParameterUnbondingPeriod
         );
 
         ensure!(
-            role_parameters.min_service_period >= T::BlockNumber::from(600),
+            role_parameters.min_service_period
+                >= T::BlockNumber::from(ROLE_PARAMETERS_MIN_SERVICE_PERIOD_MIN_VALUE),
             Error::InvalidStorageRoleParameterMinServicePeriod
         );
 
         ensure!(
-            role_parameters.min_service_period <= T::BlockNumber::from(28800),
+            role_parameters.min_service_period
+                <= T::BlockNumber::from(ROLE_PARAMETERS_MIN_SERVICE_PERIOD_MAX_VALUE),
             Error::InvalidStorageRoleParameterMinServicePeriod
         );
 
         ensure!(
-            role_parameters.startup_grace_period >= T::BlockNumber::from(600),
+            role_parameters.startup_grace_period
+                >= T::BlockNumber::from(ROLE_PARAMETERS_STARTUP_GRACE_PERIOD_MIN_VALUE),
             Error::InvalidStorageRoleParameterStartupGracePeriod
         );
 
         ensure!(
-            role_parameters.startup_grace_period <= T::BlockNumber::from(28800),
+            role_parameters.startup_grace_period
+                <= T::BlockNumber::from(ROLE_PARAMETERS_STARTUP_GRACE_PERIOD_MAX_VALUE),
             Error::InvalidStorageRoleParameterStartupGracePeriod
         );
 
         ensure!(
-            role_parameters.min_stake > <BalanceOfGovernanceCurrency<T>>::from(0u32),
+            role_parameters.min_stake
+                > <BalanceOfGovernanceCurrency<T>>::from(ROLE_PARAMETERS_MIN_STAKE_MIN_VALUE),
             Error::InvalidStorageRoleParameterMinStake
         );
 
-        let max_min_stake: u32 = get_required_stake_by_fraction::<T>(1, 100)
-            .try_into()
-            .unwrap_or_default() as u32;
-
         ensure!(
-            role_parameters.min_stake < <BalanceOfGovernanceCurrency<T>>::from(max_min_stake),
+            role_parameters.min_stake
+                <= <BalanceOfGovernanceCurrency<T>>::from(ROLE_PARAMETERS_MIN_STAKE_MAX_VALUE),
             Error::InvalidStorageRoleParameterMinStake
         );
 
         ensure!(
-            role_parameters.entry_request_fee > <BalanceOfGovernanceCurrency<T>>::from(0u32),
+            role_parameters.entry_request_fee
+                > <BalanceOfGovernanceCurrency<T>>::from(
+                    ROLE_PARAMETERS_ENTRY_REQUEST_FEE_MIN_VALUE
+                ),
             Error::InvalidStorageRoleParameterEntryRequestFee
         );
 
-        let max_entry_request_fee: u32 = get_required_stake_by_fraction::<T>(1, 100)
-            .try_into()
-            .unwrap_or_default() as u32;
-
         ensure!(
             role_parameters.entry_request_fee
-                < <BalanceOfGovernanceCurrency<T>>::from(max_entry_request_fee),
+                <= <BalanceOfGovernanceCurrency<T>>::from(
+                    ROLE_PARAMETERS_ENTRY_REQUEST_FEE_MAX_VALUE
+                ),
             Error::InvalidStorageRoleParameterEntryRequestFee
         );
 
         ensure!(
-            role_parameters.reward > <BalanceOfGovernanceCurrency<T>>::from(0u32),
+            role_parameters.reward
+                > <BalanceOfGovernanceCurrency<T>>::from(ROLE_PARAMETERS_REWARD_MIN_VALUE),
             Error::InvalidStorageRoleParameterReward
         );
 
-        let max_reward: u32 = get_required_stake_by_fraction::<T>(1, 1000)
-            .try_into()
-            .unwrap_or_default() as u32;
-
         ensure!(
-            role_parameters.reward < <BalanceOfGovernanceCurrency<T>>::from(max_reward),
+            role_parameters.reward
+                < <BalanceOfGovernanceCurrency<T>>::from(ROLE_PARAMETERS_REWARD_MAX_VALUE),
             Error::InvalidStorageRoleParameterReward
         );
 
@@ -862,84 +933,98 @@ impl<T: Trait> Module<T> {
         election_parameters: &ElectionParameters<BalanceOfGovernanceCurrency<T>, T::BlockNumber>,
     ) -> Result<(), Error> {
         ensure!(
-            election_parameters.council_size >= 4,
+            election_parameters.council_size >= ELECTION_PARAMETERS_COUNCIL_SIZE_MIN_VALUE,
             Error::InvalidCouncilElectionParameterCouncilSize
         );
 
         ensure!(
-            election_parameters.council_size <= 20,
+            election_parameters.council_size <= ELECTION_PARAMETERS_COUNCIL_SIZE_MAX_VALUE,
             Error::InvalidCouncilElectionParameterCouncilSize
         );
 
         ensure!(
-            election_parameters.candidacy_limit >= 25,
+            election_parameters.candidacy_limit >= ELECTION_PARAMETERS_CANDIDACY_LIMIT_MIN_VALUE,
             Error::InvalidCouncilElectionParameterCandidacyLimit
         );
 
         ensure!(
-            election_parameters.candidacy_limit <= 100,
+            election_parameters.candidacy_limit <= ELECTION_PARAMETERS_CANDIDACY_LIMIT_MAX_VALUE,
             Error::InvalidCouncilElectionParameterCandidacyLimit
         );
 
         ensure!(
-            election_parameters.min_voting_stake >= <BalanceOfGovernanceCurrency<T>>::one(),
+            election_parameters.min_voting_stake
+                >= <BalanceOfGovernanceCurrency<T>>::from(ELECTION_PARAMETERS_MIN_STAKE_MIN_VALUE),
             Error::InvalidCouncilElectionParameterMinVotingStake
         );
 
         ensure!(
             election_parameters.min_voting_stake
-                <= <BalanceOfGovernanceCurrency<T>>::from(100_000_u32),
+                <= <BalanceOfGovernanceCurrency<T>>::from(ELECTION_PARAMETERS_MIN_STAKE_MAX_VALUE),
             Error::InvalidCouncilElectionParameterMinVotingStake
         );
 
         ensure!(
-            election_parameters.new_term_duration >= T::BlockNumber::from(14400),
+            election_parameters.new_term_duration
+                >= T::BlockNumber::from(ELECTION_PARAMETERS_NEW_TERM_DURATION_MIN_VALUE),
             Error::InvalidCouncilElectionParameterNewTermDuration
         );
 
         ensure!(
-            election_parameters.new_term_duration <= T::BlockNumber::from(432_000),
+            election_parameters.new_term_duration
+                <= T::BlockNumber::from(ELECTION_PARAMETERS_NEW_TERM_DURATION_MAX_VALUE),
             Error::InvalidCouncilElectionParameterNewTermDuration
         );
 
         ensure!(
-            election_parameters.revealing_period >= T::BlockNumber::from(14400),
+            election_parameters.revealing_period
+                >= T::BlockNumber::from(ELECTION_PARAMETERS_REVEALING_PERIOD_MIN_VALUE),
             Error::InvalidCouncilElectionParameterRevealingPeriod
         );
 
         ensure!(
-            election_parameters.revealing_period <= T::BlockNumber::from(43200),
+            election_parameters.revealing_period
+                <= T::BlockNumber::from(ELECTION_PARAMETERS_REVEALING_PERIOD_MAX_VALUE),
             Error::InvalidCouncilElectionParameterRevealingPeriod
         );
 
         ensure!(
-            election_parameters.voting_period >= T::BlockNumber::from(14400),
+            election_parameters.voting_period
+                >= T::BlockNumber::from(ELECTION_PARAMETERS_VOTING_PERIOD_MIN_VALUE),
             Error::InvalidCouncilElectionParameterVotingPeriod
         );
 
         ensure!(
-            election_parameters.voting_period <= T::BlockNumber::from(43200),
+            election_parameters.voting_period
+                <= T::BlockNumber::from(ELECTION_PARAMETERS_VOTING_PERIOD_MAX_VALUE),
             Error::InvalidCouncilElectionParameterVotingPeriod
         );
 
         ensure!(
-            election_parameters.announcing_period >= T::BlockNumber::from(14400),
+            election_parameters.announcing_period
+                >= T::BlockNumber::from(ELECTION_PARAMETERS_ANNOUNCING_PERIOD_MIN_VALUE),
             Error::InvalidCouncilElectionParameterAnnouncingPeriod
         );
 
         ensure!(
-            election_parameters.announcing_period <= T::BlockNumber::from(43200),
+            election_parameters.announcing_period
+                <= T::BlockNumber::from(ELECTION_PARAMETERS_ANNOUNCING_PERIOD_MAX_VALUE),
             Error::InvalidCouncilElectionParameterAnnouncingPeriod
         );
 
         ensure!(
-            election_parameters.min_council_stake >= <BalanceOfGovernanceCurrency<T>>::one(),
+            election_parameters.min_council_stake
+                >= <BalanceOfGovernanceCurrency<T>>::from(
+                    ELECTION_PARAMETERS_MIN_COUNCIL_STAKE_MIN_VALUE
+                ),
             Error::InvalidCouncilElectionParameterMinCouncilStake
         );
 
         ensure!(
             election_parameters.min_council_stake
-                <= <BalanceOfGovernanceCurrency<T>>::from(100_000_u32),
+                <= <BalanceOfGovernanceCurrency<T>>::from(
+                    ELECTION_PARAMETERS_MIN_COUNCIL_STAKE_MAX_VALUE
+                ),
             Error::InvalidCouncilElectionParameterMinCouncilStake
         );
 
@@ -1003,18 +1088,3 @@ impl<T: Trait> Module<T> {
         ));
     }
 }
-
-// calculates required stake value using total issuance value and stake percentage. Truncates to
-// lowest integer value. Value fraction is defined by numerator and denominator.
-pub(crate) fn get_required_stake_by_fraction<T: crate::Trait>(
-    numerator: u32,
-    denominator: u32,
-) -> BalanceOf<T> {
-    let total_issuance: u128 = <CurrencyOf<T>>::total_issuance().try_into().unwrap_or(0) as u128;
-    let required_stake =
-        Perbill::from_rational_approximation(numerator, denominator) * total_issuance;
-
-    let balance: BalanceOf<T> = required_stake.saturated_into();
-
-    balance
-}

+ 16 - 46
runtime-modules/proposals/codex/src/proposal_types/parameters.rs

@@ -1,4 +1,4 @@
-use crate::{get_required_stake_by_fraction, BalanceOf, Module, ProposalParameters};
+use crate::{BalanceOf, Module, ProposalParameters};
 
 // Proposal parameters for the 'Set validator count' proposal
 pub(crate) fn set_validator_count_proposal<T: crate::Trait>(
@@ -10,7 +10,7 @@ pub(crate) fn set_validator_count_proposal<T: crate::Trait>(
         approval_threshold_percentage: 80,
         slashing_quorum_percentage: 60,
         slashing_threshold_percentage: 80,
-        required_stake: Some(get_required_stake_by_fraction::<T>(25, 10000)),
+        required_stake: Some(<BalanceOf<T>>::from(100_000_u32)),
     }
 }
 
@@ -24,7 +24,7 @@ pub(crate) fn runtime_upgrade_proposal<T: crate::Trait>(
         approval_threshold_percentage: 100,
         slashing_quorum_percentage: 60,
         slashing_threshold_percentage: 80,
-        required_stake: Some(get_required_stake_by_fraction::<T>(1, 100)),
+        required_stake: Some(<BalanceOf<T>>::from(1_000_000_u32)),
     }
 }
 
@@ -33,11 +33,11 @@ pub(crate) fn text_proposal<T: crate::Trait>() -> ProposalParameters<T::BlockNum
     ProposalParameters {
         voting_period: <Module<T>>::text_proposal_voting_period(),
         grace_period: <Module<T>>::text_proposal_grace_period(),
-        approval_quorum_percentage: 66,
+        approval_quorum_percentage: 60,
         approval_threshold_percentage: 80,
         slashing_quorum_percentage: 60,
         slashing_threshold_percentage: 80,
-        required_stake: Some(get_required_stake_by_fraction::<T>(25, 10000)),
+        required_stake: Some(<BalanceOf<T>>::from(25000u32)),
     }
 }
 
@@ -51,7 +51,7 @@ pub(crate) fn set_election_parameters_proposal<T: crate::Trait>(
         approval_threshold_percentage: 80,
         slashing_quorum_percentage: 60,
         slashing_threshold_percentage: 80,
-        required_stake: Some(get_required_stake_by_fraction::<T>(75, 10000)),
+        required_stake: Some(<BalanceOf<T>>::from(200_000_u32)),
     }
 }
 
@@ -62,11 +62,11 @@ pub(crate) fn set_content_working_group_mint_capacity_proposal<T: crate::Trait>(
         voting_period: <Module<T>>::set_content_working_group_mint_capacity_proposal_voting_period(
         ),
         grace_period: <Module<T>>::set_content_working_group_mint_capacity_proposal_grace_period(),
-        approval_quorum_percentage: 50,
+        approval_quorum_percentage: 60,
         approval_threshold_percentage: 75,
         slashing_quorum_percentage: 60,
         slashing_threshold_percentage: 80,
-        required_stake: Some(get_required_stake_by_fraction::<T>(25, 10000)),
+        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
     }
 }
 
@@ -76,11 +76,11 @@ pub(crate) fn spending_proposal<T: crate::Trait>(
     ProposalParameters {
         voting_period: <Module<T>>::spending_proposal_voting_period(),
         grace_period: <Module<T>>::spending_proposal_grace_period(),
-        approval_quorum_percentage: 66,
+        approval_quorum_percentage: 60,
         approval_threshold_percentage: 80,
         slashing_quorum_percentage: 60,
         slashing_threshold_percentage: 80,
-        required_stake: Some(get_required_stake_by_fraction::<T>(25, 10000)),
+        required_stake: Some(<BalanceOf<T>>::from(25000u32)),
     }
 }
 
@@ -90,11 +90,11 @@ pub(crate) fn set_lead_proposal<T: crate::Trait>(
     ProposalParameters {
         voting_period: <Module<T>>::set_lead_proposal_voting_period(),
         grace_period: <Module<T>>::set_lead_proposal_grace_period(),
-        approval_quorum_percentage: 66,
-        approval_threshold_percentage: 80,
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 75,
         slashing_quorum_percentage: 60,
         slashing_threshold_percentage: 80,
-        required_stake: Some(get_required_stake_by_fraction::<T>(25, 10000)),
+        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
     }
 }
 
@@ -108,7 +108,7 @@ pub(crate) fn evict_storage_provider_proposal<T: crate::Trait>(
         approval_threshold_percentage: 75,
         slashing_quorum_percentage: 60,
         slashing_threshold_percentage: 80,
-        required_stake: Some(get_required_stake_by_fraction::<T>(1, 1000)),
+        required_stake: Some(<BalanceOf<T>>::from(25000u32)),
     }
 }
 
@@ -118,40 +118,10 @@ pub(crate) fn set_storage_role_parameters_proposal<T: crate::Trait>(
     ProposalParameters {
         voting_period: <Module<T>>::set_storage_role_parameters_proposal_voting_period(),
         grace_period: <Module<T>>::set_storage_role_parameters_proposal_grace_period(),
-        approval_quorum_percentage: 75,
+        approval_quorum_percentage: 66,
         approval_threshold_percentage: 80,
         slashing_quorum_percentage: 60,
         slashing_threshold_percentage: 80,
-        required_stake: Some(get_required_stake_by_fraction::<T>(25, 10000)),
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use crate::proposal_types::parameters::get_required_stake_by_fraction;
-    use crate::tests::{increase_total_balance_issuance, initial_test_ext, Test};
-
-    pub use sr_primitives::Perbill;
-
-    #[test]
-    fn calculate_get_required_stake_by_fraction_with_zero_issuance() {
-        initial_test_ext()
-            .execute_with(|| assert_eq!(get_required_stake_by_fraction::<Test>(5, 7), 0));
-    }
-
-    #[test]
-    fn calculate_stake_by_percentage_for_defined_issuance_succeeds() {
-        initial_test_ext().execute_with(|| {
-            increase_total_balance_issuance(50000);
-            assert_eq!(get_required_stake_by_fraction::<Test>(1, 1000), 50)
-        });
-    }
-
-    #[test]
-    fn calculate_stake_by_percentage_for_defined_issuance_with_fraction_loss() {
-        initial_test_ext().execute_with(|| {
-            increase_total_balance_issuance(1111);
-            assert_eq!(get_required_stake_by_fraction::<Test>(3, 1000), 3);
-        });
+        required_stake: Some(<BalanceOf<T>>::from(100_000_u32)),
     }
 }

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

@@ -141,8 +141,8 @@ parameter_types! {
 impl proposal_discussion::Trait for Test {
     type Event = ();
     type PostAuthorOriginValidator = ();
-    type ThreadId = u32;
-    type PostId = u32;
+    type ThreadId = u64;
+    type PostId = u64;
     type MaxPostEditionNumber = MaxPostEditionNumber;
     type ThreadTitleLengthLimit = ThreadTitleLengthLimit;
     type PostLengthLimit = PostLengthLimit;

+ 55 - 48
runtime-modules/proposals/codex/src/tests/mod.rs

@@ -134,7 +134,7 @@ fn create_text_proposal_common_checks_succeed() {
                     1,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(1250u32)),
+                    Some(<BalanceOf<Test>>::from(25000u32)),
                     b"text".to_vec(),
                 )
             },
@@ -180,7 +180,7 @@ fn create_text_proposal_codex_call_fails_with_incorrect_text_size() {
 #[test]
 fn create_runtime_upgrade_common_checks_succeed() {
     initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance(500000);
+        increase_total_balance_issuance_using_account_id(1, 5000000);
 
         let proposal_fixture = ProposalTestFixture {
             insufficient_rights_call: || {
@@ -219,7 +219,7 @@ fn create_runtime_upgrade_common_checks_succeed() {
                     1,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(5000u32)),
+                    Some(<BalanceOf<Test>>::from(1_000_000_u32)),
                     b"wasm".to_vec(),
                 )
             },
@@ -265,7 +265,7 @@ fn create_upgrade_runtime_proposal_codex_call_fails_with_incorrect_wasm_size() {
 #[test]
 fn create_set_election_parameters_proposal_common_checks_succeed() {
     initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance(500000);
+        increase_total_balance_issuance_using_account_id(1, 500000);
 
         let proposal_fixture = ProposalTestFixture {
             insufficient_rights_call: || {
@@ -304,7 +304,7 @@ fn create_set_election_parameters_proposal_common_checks_succeed() {
                     1,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(3750u32)),
+                    Some(<BalanceOf<Test>>::from(200_000_u32)),
                     get_valid_election_parameters(),
                 )
             },
@@ -469,7 +469,7 @@ fn create_set_election_parameters_call_fails_with_incorrect_parameters() {
 #[test]
 fn create_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
     initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance(500000);
+        increase_total_balance_issuance_using_account_id(1, 500000);
 
         assert_eq!(
             ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
@@ -477,8 +477,8 @@ fn create_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
                 1,
                 b"title".to_vec(),
                 b"body".to_vec(),
-                Some(<BalanceOf<Test>>::from(1250u32)),
-                5001,
+                Some(<BalanceOf<Test>>::from(50000u32)),
+                (crate::CONTENT_WORKING_GROUP_MINT_CAPACITY_MAX_VALUE + 1) as u64,
             ),
             Err(Error::InvalidStorageWorkingGroupMintCapacity)
         );
@@ -527,7 +527,7 @@ fn create_set_content_working_group_mint_capacity_proposal_common_checks_succeed
                     1,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(1250u32)),
+                    Some(<BalanceOf<Test>>::from(50000u32)),
                     10,
                 )
             },
@@ -583,7 +583,7 @@ fn create_spending_proposal_common_checks_succeed() {
                     1,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(1250u32)),
+                    Some(<BalanceOf<Test>>::from(25000u32)),
                     100,
                     2,
                 )
@@ -620,7 +620,7 @@ fn create_spending_proposal_call_fails_with_incorrect_balance() {
                 b"title".to_vec(),
                 b"body".to_vec(),
                 Some(<BalanceOf<Test>>::from(1250u32)),
-                1001,
+                2000001,
                 2,
             ),
             Err(Error::InvalidSpendingProposalBalance)
@@ -696,7 +696,7 @@ fn create_set_lead_proposal_common_checks_succeed() {
                     1,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(1250u32)),
+                    Some(<BalanceOf<Test>>::from(50000u32)),
                     Some((20, 10)),
                 )
             },
@@ -749,7 +749,7 @@ fn create_evict_storage_provider_proposal_common_checks_succeed() {
                     1,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(500u32)),
+                    Some(<BalanceOf<Test>>::from(25000u32)),
                     1,
                 )
             },
@@ -763,7 +763,7 @@ fn create_evict_storage_provider_proposal_common_checks_succeed() {
 #[test]
 fn create_set_validator_count_proposal_common_checks_succeed() {
     initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance(500000);
+        increase_total_balance_issuance_using_account_id(1, 500000);
 
         let proposal_fixture = ProposalTestFixture {
             insufficient_rights_call: || {
@@ -802,7 +802,7 @@ fn create_set_validator_count_proposal_common_checks_succeed() {
                     1,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(1250u32)),
+                    Some(<BalanceOf<Test>>::from(100_000_u32)),
                     4,
                 )
             },
@@ -847,8 +847,11 @@ fn create_set_validator_count_proposal_failed_with_invalid_validator_count() {
 #[test]
 fn create_set_storage_role_parameters_proposal_common_checks_succeed() {
     initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance(500000);
-
+        increase_total_balance_issuance_using_account_id(1, 500000);
+        let role_parameters = RoleParameters {
+            min_actors: 1,
+            ..RoleParameters::default()
+        };
         let proposal_fixture = ProposalTestFixture {
             insufficient_rights_call: || {
                 ProposalCodex::create_set_storage_role_parameters_proposal(
@@ -857,7 +860,7 @@ fn create_set_storage_role_parameters_proposal_common_checks_succeed() {
                     b"title".to_vec(),
                     b"body".to_vec(),
                     None,
-                    RoleParameters::default(),
+                    role_parameters.clone(),
                 )
             },
             empty_stake_call: || {
@@ -867,7 +870,7 @@ fn create_set_storage_role_parameters_proposal_common_checks_succeed() {
                     b"title".to_vec(),
                     b"body".to_vec(),
                     None,
-                    RoleParameters::default(),
+                    role_parameters.clone(),
                 )
             },
             invalid_stake_call: || {
@@ -877,7 +880,7 @@ fn create_set_storage_role_parameters_proposal_common_checks_succeed() {
                     b"title".to_vec(),
                     b"body".to_vec(),
                     Some(<BalanceOf<Test>>::from(5000u32)),
-                    RoleParameters::default(),
+                    role_parameters.clone(),
                 )
             },
             successful_call: || {
@@ -886,13 +889,13 @@ fn create_set_storage_role_parameters_proposal_common_checks_succeed() {
                     1,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(1250u32)),
-                    RoleParameters::default(),
+                    Some(<BalanceOf<Test>>::from(100_000_u32)),
+                    role_parameters.clone(),
                 )
             },
             proposal_parameters:
                 crate::proposal_types::parameters::set_storage_role_parameters_proposal::<Test>(),
-            proposal_details: ProposalDetails::SetStorageRoleParameters(RoleParameters::default()),
+            proposal_details: ProposalDetails::SetStorageRoleParameters(role_parameters),
         };
         proposal_fixture.check_all();
     });
@@ -908,7 +911,7 @@ fn assert_failed_set_storage_parameters_call(
             1,
             b"title".to_vec(),
             b"body".to_vec(),
-            Some(<BalanceOf<Test>>::from(500u32)),
+            Some(<BalanceOf<Test>>::from(100_000_u32)),
             role_parameters,
         ),
         Err(error)
@@ -918,30 +921,34 @@ fn assert_failed_set_storage_parameters_call(
 #[test]
 fn create_set_storage_role_parameters_proposal_fails_with_invalid_parameters() {
     initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance(500000);
+        increase_total_balance_issuance_using_account_id(1, 500000);
 
-        let mut role_parameters = RoleParameters::default();
-        role_parameters.min_actors = 6;
+        let working_role_parameters = RoleParameters {
+            min_actors: 1,
+            ..RoleParameters::default()
+        };
+        let mut role_parameters = working_role_parameters.clone();
+        role_parameters.min_actors = 2;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterMinActors,
         );
 
-        role_parameters = RoleParameters::default();
-        role_parameters.max_actors = 4;
+        role_parameters = working_role_parameters.clone();
+        role_parameters.max_actors = 1;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterMaxActors,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.max_actors = 100;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterMaxActors,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.reward_period = 599;
         assert_failed_set_storage_parameters_call(
             role_parameters,
@@ -954,99 +961,99 @@ fn create_set_storage_role_parameters_proposal_fails_with_invalid_parameters() {
             Error::InvalidStorageRoleParameterRewardPeriod,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.bonding_period = 599;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterBondingPeriod,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.bonding_period = 28801;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterBondingPeriod,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.unbonding_period = 599;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterUnbondingPeriod,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.unbonding_period = 28801;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterUnbondingPeriod,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.min_service_period = 599;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterMinServicePeriod,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.min_service_period = 28801;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterMinServicePeriod,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.startup_grace_period = 599;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterStartupGracePeriod,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.startup_grace_period = 28801;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterStartupGracePeriod,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.min_stake = 0;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterMinStake,
         );
 
-        role_parameters = RoleParameters::default();
-        role_parameters.min_stake = 5001;
+        role_parameters = working_role_parameters.clone();
+        role_parameters.min_stake = 10000001;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterMinStake,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.entry_request_fee = 0;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterEntryRequestFee,
         );
 
-        role_parameters = RoleParameters::default();
-        role_parameters.entry_request_fee = 5001;
+        role_parameters = working_role_parameters.clone();
+        role_parameters.entry_request_fee = 100001;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterEntryRequestFee,
         );
 
-        role_parameters = RoleParameters::default();
+        role_parameters = working_role_parameters.clone();
         role_parameters.reward = 0;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterReward,
         );
 
-        role_parameters = RoleParameters::default();
-        role_parameters.reward = 501;
+        role_parameters = working_role_parameters;
+        role_parameters.reward = 1001;
         assert_failed_set_storage_parameters_call(
             role_parameters,
             Error::InvalidStorageRoleParameterReward,

+ 16 - 4
runtime-modules/proposals/discussion/src/lib.rs

@@ -93,10 +93,10 @@ pub trait Trait: system::Trait + membership::members::Trait {
     >;
 
     /// Discussion thread Id type
-    type ThreadId: From<u32> + Into<u32> + Parameter + Default + Copy;
+    type ThreadId: From<u64> + Into<u64> + Parameter + Default + Copy;
 
     /// Post Id type
-    type PostId: From<u32> + Parameter + Default + Copy;
+    type PostId: From<u64> + Parameter + Default + Copy;
 
     /// Defines post edition number limit.
     type MaxPostEditionNumber: Get<u32>;
@@ -164,14 +164,14 @@ decl_storage! {
             Thread<MemberId<T>, T::BlockNumber>;
 
         /// Count of all threads that have been created.
-        pub ThreadCount get(fn thread_count): u32;
+        pub ThreadCount get(fn thread_count): u64;
 
         /// Map thread id and post id to corresponding post.
         pub PostThreadIdByPostId: double_map T::ThreadId, twox_128(T::PostId) =>
              Post<MemberId<T>, T::BlockNumber, T::ThreadId>;
 
         /// Count of all posts that have been created.
-        pub PostCount get(fn post_count): u32;
+        pub PostCount get(fn post_count): u64;
 
         /// Last author thread counter (part of the antispam mechanism)
         pub LastThreadAuthorCounter get(fn last_thread_author_counter):
@@ -188,6 +188,18 @@ decl_module! {
         /// Emits an event. Default substrate implementation.
         fn deposit_event() = default;
 
+        /// Exports post edition number limit const.
+        const MaxPostEditionNumber: u32 = T::MaxPostEditionNumber::get();
+
+        /// Exports thread title length limit const.
+        const ThreadTitleLengthLimit: u32 = T::ThreadTitleLengthLimit::get();
+
+        /// Exports post length limit const.
+        const PostLengthLimit: u32 = T::PostLengthLimit::get();
+
+        /// Exports max thread by same author in a row number limit const.
+        const MaxThreadInARowNumber: u32 = T::MaxThreadInARowNumber::get();
+
         /// Adds a post with author origin check.
         pub fn add_post(
             origin,

+ 2 - 2
runtime-modules/proposals/discussion/src/tests/mock.rs

@@ -87,8 +87,8 @@ impl membership::members::Trait for Test {
 impl crate::Trait for Test {
     type Event = TestEvent;
     type PostAuthorOriginValidator = ();
-    type ThreadId = u32;
-    type PostId = u32;
+    type ThreadId = u64;
+    type PostId = u64;
     type MaxPostEditionNumber = MaxPostEditionNumber;
     type ThreadTitleLengthLimit = ThreadTitleLengthLimit;
     type PostLengthLimit = PostLengthLimit;

+ 10 - 10
runtime-modules/proposals/discussion/src/tests/mod.rs

@@ -8,7 +8,7 @@ use system::{EventRecord, Phase};
 
 struct EventFixture;
 impl EventFixture {
-    fn assert_events(expected_raw_events: Vec<RawEvent<u32, u64, u32>>) {
+    fn assert_events(expected_raw_events: Vec<RawEvent<u64, u64, u64>>) {
         let expected_events = expected_raw_events
             .iter()
             .map(|ev| EventRecord {
@@ -23,13 +23,13 @@ impl EventFixture {
 }
 
 struct TestPostEntry {
-    pub post_id: u32,
+    pub post_id: u64,
     pub text: Vec<u8>,
     pub edition_number: u32,
 }
 
 struct TestThreadEntry {
-    pub thread_id: u32,
+    pub thread_id: u64,
     pub title: Vec<u8>,
 }
 
@@ -81,7 +81,7 @@ impl DiscussionFixture {
         DiscussionFixture { title, ..self }
     }
 
-    fn create_discussion_and_assert(&self, result: Result<u32, Error>) -> Option<u32> {
+    fn create_discussion_and_assert(&self, result: Result<u64, Error>) -> Option<u64> {
         let create_discussion_result =
             Discussions::create_thread(self.author_id, self.title.clone());
 
@@ -94,13 +94,13 @@ impl DiscussionFixture {
 struct PostFixture {
     pub text: Vec<u8>,
     pub origin: RawOrigin<u64>,
-    pub thread_id: u32,
-    pub post_id: Option<u32>,
+    pub thread_id: u64,
+    pub post_id: Option<u64>,
     pub author_id: u64,
 }
 
 impl PostFixture {
-    fn default_for_thread(thread_id: u32) -> Self {
+    fn default_for_thread(thread_id: u64) -> Self {
         PostFixture {
             text: b"text".to_vec(),
             author_id: 1,
@@ -122,18 +122,18 @@ impl PostFixture {
         PostFixture { author_id, ..self }
     }
 
-    fn change_thread_id(self, thread_id: u32) -> Self {
+    fn change_thread_id(self, thread_id: u64) -> Self {
         PostFixture { thread_id, ..self }
     }
 
-    fn change_post_id(self, post_id: u32) -> Self {
+    fn change_post_id(self, post_id: u64) -> Self {
         PostFixture {
             post_id: Some(post_id),
             ..self
         }
     }
 
-    fn add_post_and_assert(&mut self, result: Result<(), Error>) -> Option<u32> {
+    fn add_post_and_assert(&mut self, result: Result<(), Error>) -> Option<u64> {
         let add_post_result = Discussions::add_post(
             self.origin.clone().into(),
             self.author_id,

+ 15 - 0
runtime-modules/proposals/engine/src/lib.rs

@@ -315,6 +315,21 @@ decl_module! {
         /// Emits an event. Default substrate implementation.
         fn deposit_event() = default;
 
+        /// Exports const - the fee is applied when cancel the proposal. A fee would be slashed (burned).
+        const CancellationFee: BalanceOf<T> = T::CancellationFee::get();
+
+        /// Exports const -  the fee is applied when the proposal gets rejected. A fee would be slashed (burned).
+        const RejectionFee: BalanceOf<T> = T::RejectionFee::get();
+
+        /// Exports const -  max allowed proposal title length.
+        const TitleMaxLength: u32 = T::TitleMaxLength::get();
+
+        /// Exports const -  max allowed proposal description length.
+        const DescriptionMaxLength: u32 = T::DescriptionMaxLength::get();
+
+        /// Exports const -  max simultaneous active proposals number.
+        const MaxActiveProposalLimit: u32 = T::MaxActiveProposalLimit::get();
+
         /// Vote extrinsic. Conditions:  origin must allow votes.
         pub fn vote(origin, voter_id: MemberId<T>, proposal_id: T::ProposalId, vote: VoteKind)  {
             T::VoterOriginValidator::ensure_actor_origin(

+ 1 - 1
runtime-modules/roles/src/actors.rs

@@ -67,7 +67,7 @@ impl<Balance: From<u32>, BlockNumber: From<u32>> Default for RoleParameters<Bala
             entry_request_fee: Balance::from(50),
 
             // not currently used
-            min_actors: 5,
+            min_actors: 1,
             bonding_period: BlockNumber::from(600),
             min_service_period: BlockNumber::from(600),
             startup_grace_period: BlockNumber::from(600),

+ 3 - 4
runtime/CHANGELOG.md

@@ -1,5 +1,6 @@
-### Version 6.9.1
-...
+### Version 6.13.0 - (Constantinople) runtime upgrade - May 20th 2020
+- New proposal system that strengthens the governance structure of the platform
+- Adjusted inflation curve to better reflect a new realistic economic system for the platform
 
 ### Version 6.8.0 (Rome release) - March 9th 2020
 - New versioned and permissioned content mangement system that powers a new media experience.
@@ -35,5 +36,3 @@
   - Council Runtime upgrade proposal
   - Simple PoS validator staking
   - Memo (account status message)
-
-Used in genesis block for first testnet (Sparta)

+ 2 - 2
runtime/Cargo.toml

@@ -5,7 +5,7 @@ edition = '2018'
 name = 'joystream-node-runtime'
 # Follow convention: https://github.com/Joystream/substrate-runtime-joystream/issues/1
 # {Authoring}.{Spec}.{Impl} of the RuntimeVersion
-version = '6.12.2'
+version = '6.13.0'
 
 [features]
 default = ['std']
@@ -273,7 +273,7 @@ rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [build-dependencies.wasm-builder-runner]
 package = 'substrate-wasm-builder-runner'
-version = '1.0.4'
+version = '1.0.5'
 
 [dependencies.forum]
 default_features = false

+ 1 - 1
runtime/build.rs

@@ -24,7 +24,7 @@ fn main() {
     }
 
     let file_name = "wasm_binary.rs";
-    let wasm_builder_source = WasmBuilderSource::Crates("1.0.8");
+    let wasm_builder_source = WasmBuilderSource::Crates("1.0.9");
     // This instructs LLD to export __heap_base as a global variable, which is used by the
     // external memory allocator.
     let default_rust_flags = "-Clink-arg=--export=__heap_base";

+ 30 - 12
runtime/src/lib.rs

@@ -94,6 +94,22 @@ pub type Moment = u64;
 /// Credential type
 pub type Credential = u64;
 
+/// Represents a thread identifier for both Forum and Proposals Discussion
+///
+/// Note: Both modules expose type names ThreadId and PostId (which are defined on their Trait) and
+/// used in state storage and dispatchable method's argument types,
+/// and are therefore part of the public API/metadata of the runtime.
+/// In the currenlty version the polkadot-js/api that is used and is compatible with the runtime,
+/// the type registry has flat namespace and its not possible
+/// to register identically named types from different modules, separately. And so we MUST configure
+/// the underlying types to be identicaly to avoid issues with encoding/decoding these types on the client side.
+pub type ThreadId = u64;
+
+/// Represents a post identifier for both Forum and Proposals Discussion
+///
+/// See the Note about ThreadId
+pub type PostId = u64;
+
 /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
 /// the specifics of the runtime. They can then be made to be agnostic over specific formats
 /// of data like extrinsics, allowing for them to continue syncing the network through upgrades
@@ -126,8 +142,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
     spec_name: create_runtime_str!("joystream-node"),
     impl_name: create_runtime_str!("joystream-node"),
     authoring_version: 6,
-    spec_version: 12,
-    impl_version: 1,
+    spec_version: 13,
+    impl_version: 0,
     apis: RUNTIME_API_VERSIONS,
 };
 
@@ -176,7 +192,7 @@ pub fn native_version() -> NativeVersion {
 
 parameter_types! {
     pub const BlockHashCount: BlockNumber = 250;
-    pub const MaximumBlockWeight: Weight = 1_000_000;
+    pub const MaximumBlockWeight: Weight = 1_000_000_000;
     pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
     pub const MaximumBlockLength: u32 = 5 * 1024 * 1024;
     pub const Version: RuntimeVersion = VERSION;
@@ -342,17 +358,17 @@ impl session::historical::Trait for Runtime {
 srml_staking_reward_curve::build! {
     const REWARD_CURVE: PiecewiseLinear<'static> = curve!(
         min_inflation: 0_025_000,
-        max_inflation: 0_100_000,
-        ideal_stake: 0_500_000,
+        max_inflation: 0_300_000,
+        ideal_stake: 0_300_000,
         falloff: 0_050_000,
-        max_piece_count: 40,
+        max_piece_count: 100,
         test_precision: 0_005_000,
     );
 }
 
 parameter_types! {
     pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 6;
-    pub const BondingDuration: staking::EraIndex = 24 * 28;
+    pub const BondingDuration: staking::EraIndex = 24;
     pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
 }
 
@@ -785,6 +801,8 @@ impl forum::Trait for Runtime {
     type Event = Event;
     type MembershipRegistry = ShimMembershipRegistry;
     type EnsureForumLeader = bureaucracy::Module<Runtime, bureaucracy::Instance1>;
+    type ThreadId = ThreadId;
+    type PostId = PostId;
 }
 
 impl migration::Trait for Runtime {
@@ -814,8 +832,8 @@ impl discovery::Trait for Runtime {
 }
 
 parameter_types! {
-    pub const ProposalCancellationFee: u64 = 5;
-    pub const ProposalRejectionFee: u64 = 3;
+    pub const ProposalCancellationFee: u64 = 10000;
+    pub const ProposalRejectionFee: u64 = 5000;
     pub const ProposalTitleMaxLength: u32 = 40;
     pub const ProposalDescriptionMaxLength: u32 = 3000;
     pub const ProposalMaxActiveProposalLimit: u32 = 5;
@@ -851,8 +869,8 @@ parameter_types! {
 impl proposals_discussion::Trait for Runtime {
     type Event = Event;
     type PostAuthorOriginValidator = MembershipOriginValidator<Self>;
-    type ThreadId = u32;
-    type PostId = u32;
+    type ThreadId = ThreadId;
+    type PostId = PostId;
     type MaxPostEditionNumber = ProposalMaxPostEditionNumber;
     type ThreadTitleLengthLimit = ProposalThreadTitleLengthLimit;
     type PostLengthLimit = ProposalPostLengthLimit;
@@ -895,12 +913,12 @@ construct_runtime!(
         RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage},
         Sudo: sudo,
         // Joystream
+        Migration: migration::{Module, Call, Storage, Event<T>, Config},
         CouncilElection: election::{Module, Call, Storage, Event<T>, Config<T>},
         Council: council::{Module, Call, Storage, Event<T>, Config<T>},
         Memo: memo::{Module, Call, Storage, Event<T>},
         Members: members::{Module, Call, Storage, Event<T>, Config<T>},
         Forum: forum::{Module, Call, Storage, Event<T>, Config<T>},
-        Migration: migration::{Module, Call, Storage, Event<T>},
         Actors: actors::{Module, Call, Storage, Event<T>, Config},
         DataObjectTypeRegistry: data_object_type_registry::{Module, Call, Storage, Event<T>, Config<T>},
         DataDirectory: data_directory::{Module, Call, Storage, Event<T>},

+ 26 - 33
runtime/src/migration.rs

@@ -2,50 +2,38 @@
 #![allow(clippy::redundant_closure_call)] // disable it because of the substrate lib design
 
 use crate::VERSION;
-use sr_primitives::{print, traits::Zero};
-use srml_support::{decl_event, decl_module, decl_storage};
+use rstd::prelude::*;
+// use sr_primitives::{print, traits::Zero};
+use srml_support::{debug, decl_event, decl_module, decl_storage};
 
 impl<T: Trait> Module<T> {
+    /// This method is called from on_initialize() when a runtime upgrade is detected. This
+    /// happens when the runtime spec version is found to be higher than the stored value.
+    /// Important to note this method should be carefully maintained, because it runs on every runtime
+    /// upgrade.
     fn runtime_upgraded() {
-        print("running runtime initializers...");
+        debug::print!("Running runtime upgraded handler");
 
-        // ...
-        // add initialization of modules introduced in new runtime release. This
+        // Add initialization of modules introduced in new runtime release. Typically this
         // would be any new storage values that need an initial value which would not
-        // have been initialized with config() or build() mechanism.
-        // ...
-
-        // Create the Council mint. If it fails, we can't do anything about it here.
-        let _ = governance::council::Module::<T>::create_new_council_mint(
-            minting::BalanceOf::<T>::zero(),
-        );
-
-        proposals_codex::Module::<T>::set_default_config_values();
-
-        Self::deposit_event(RawEvent::Migrated(
-            <system::Module<T>>::block_number(),
-            VERSION.spec_version,
-        ));
+        // have been initialized with config() or build() chainspec construction mechanism.
+        // Other tasks like resetting values, migrating values etc.
     }
 }
 
-pub trait Trait:
-    system::Trait
-    + storage::data_directory::Trait
-    + storage::data_object_storage_registry::Trait
-    + forum::Trait
-    + sudo::Trait
-    + governance::council::Trait
-    + proposals_codex::Trait
-{
+pub trait Trait: system::Trait {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 }
 
 decl_storage! {
     trait Store for Module<T: Trait> as Migration {
-        /// Records at what runtime spec version the store was initialized. This allows the runtime
-        /// to know when to run initialize code if it was installed as an update.
-        pub SpecVersion get(spec_version) build(|_| VERSION.spec_version) : Option<u32>;
+        /// Records at what runtime spec version the store was initialized. At genesis this will be
+        /// initialized to Some(VERSION.spec_version). It is an Option because the first time the module
+        /// was introduced was as a runtime upgrade and type was never changed.
+        /// When the runtime is upgraded the spec version be updated.
+        pub SpecVersion get(spec_version) build(|_config: &GenesisConfig| {
+            VERSION.spec_version
+        }) : Option<u32>;
     }
 }
 
@@ -61,11 +49,16 @@ decl_module! {
 
         fn on_initialize(_now: T::BlockNumber) {
             if Self::spec_version().map_or(true, |spec_version| VERSION.spec_version > spec_version) {
-                // mark store version with current version of the runtime
+                // Mark store version with current version of the runtime
                 SpecVersion::put(VERSION.spec_version);
 
-                // run migrations and store initializers
+                // Run migrations and store initializers
                 Self::runtime_upgraded();
+
+                Self::deposit_event(RawEvent::Migrated(
+                    <system::Module<T>>::block_number(),
+                    VERSION.spec_version,
+                ));
             }
         }
     }

+ 17 - 12
runtime/src/test/proposals_integration.rs

@@ -269,7 +269,7 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
         setup_members(2);
         let member_id = 0; // newly created member_id
 
-        let stake_amount = 200u128;
+        let stake_amount = 20000u128;
         let parameters = ProposalParameters {
             voting_period: 3,
             approval_quorum_percentage: 50,
@@ -285,7 +285,7 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
             .with_stake(stake_amount)
             .with_proposer(member_id);
 
-        let account_balance = 500;
+        let account_balance = 500000;
         let _imbalance =
             <Runtime as stake::Trait>::Currency::deposit_creating(&account_id, account_balance);
 
@@ -462,7 +462,7 @@ fn text_proposal_execution_succeeds() {
                     member_id as u64,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    Some(<BalanceOf<Runtime>>::from(25000u32)),
                     b"text".to_vec(),
                 )
             },
@@ -486,7 +486,7 @@ fn set_lead_proposal_execution_succeeds() {
                     member_id as u64,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    Some(<BalanceOf<Runtime>>::from(50000u32)),
                     Some((member_id as u64, account_id.into())),
                 )
             },
@@ -519,7 +519,7 @@ fn spending_proposal_execution_succeeds() {
                     member_id as u64,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    Some(<BalanceOf<Runtime>>::from(25_000_u32)),
                     new_balance,
                     target_account_id.clone().into(),
                 )
@@ -561,7 +561,7 @@ fn set_content_working_group_mint_capacity_execution_succeeds() {
                     member_id as u64,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    Some(<BalanceOf<Runtime>>::from(50000u32)),
                     new_balance,
                 )
             },
@@ -599,7 +599,7 @@ fn set_election_parameters_proposal_execution_succeeds() {
                     member_id as u64,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(3750u32)),
+                    Some(<BalanceOf<Runtime>>::from(200_000_u32)),
                     election_parameters,
                 )
             },
@@ -648,7 +648,7 @@ fn evict_storage_provider_proposal_execution_succeeds() {
                     member_id as u64,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(500u32)),
+                    Some(<BalanceOf<Runtime>>::from(25000u32)),
                     target_account.into(),
                 )
             },
@@ -668,14 +668,19 @@ fn set_storage_role_parameters_proposal_execution_succeeds() {
         let member_id = 1;
         let account_id: [u8; 32] = [member_id; 32];
 
+        let default_role_parameters = RoleParameters {
+            min_actors: 1,
+            ..RoleParameters::default()
+        };
+
         <roles::actors::Parameters<Runtime>>::insert(
             Role::StorageProvider,
-            RoleParameters::default(),
+            default_role_parameters.clone(),
         );
 
         let target_role_parameters = RoleParameters {
             startup_grace_period: 700,
-            ..RoleParameters::default()
+            ..default_role_parameters
         };
 
         let codex_extrinsic_test_fixture = CodexProposalTestFixture {
@@ -686,7 +691,7 @@ fn set_storage_role_parameters_proposal_execution_succeeds() {
                     member_id as u64,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    Some(<BalanceOf<Runtime>>::from(100_000_u32)),
                     target_role_parameters.clone(),
                 )
             },
@@ -717,7 +722,7 @@ fn set_validator_count_proposal_execution_succeeds() {
                     member_id as u64,
                     b"title".to_vec(),
                     b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    Some(<BalanceOf<Runtime>>::from(100_000_u32)),
                     new_validator_count,
                 )
             },

+ 3 - 0
scripts/cargo-build.sh

@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cargo build --release

+ 4 - 0
scripts/compute-runtime-blob-hash.sh

@@ -1,5 +1,8 @@
 #!/usr/bin/env bash
 
+# The script computes the b2sum of the wasm blob in a pre-built joystream/node image
+# Assumes b2sum is already instally on the host machine.
+
 # Create a non running container from joystream/node
 docker create --name temp-container-joystream-node joystream/node
 
@@ -13,4 +16,5 @@ docker rm temp-container-joystream-node
 # ubuntu 17.0+ with: apt-get install coreutils; b2sum -l 256 joystream_runtime.wasm
 # TODO: add install of b2sum to setup.sh
 b2sum -l 256 joystream_runtime.wasm
+b2sum -l 512 joystream_runtime.wasm
 rm joystream_runtime.wasm

+ 0 - 1
scripts/raspberry-cross-build.sh

@@ -7,7 +7,6 @@
 
 # joystream/rust-raspberry image was built from:
 # https://github.com/mnaamani/rust-on-raspberry-docker/commit/8536508b743d55c8573043c4082c62da3b4fd3e2
-docker pull joystream/rust-raspberry
 
 docker run \
     --volume ${PWD}/:/home/cross/project \

+ 11 - 3
setup.sh

@@ -7,8 +7,16 @@ set -e
 #  - rustup - rust insaller
 #  - rust compiler and toolchains
 #  - skips installing substrate and subkey
-curl https://getsubstrate.io -sSf | bash -s -- --fast \
-    && rustup component add rustfmt
+curl https://getsubstrate.io -sSf | bash -s -- --fast
+
+source ~/.cargo/env
+
+rustup component add rustfmt clippy
 
 # TODO: Install additional tools...
-# - b2sum
+
+# - b2sum
+# - nodejs
+# - npm
+# - yarn
+# .... ?

+ 8 - 2
tests/network-tests/.env

@@ -12,5 +12,11 @@ COUNCIL_STAKE_GREATER_AMOUNT = 1500
 COUNCIL_STAKE_LESSER_AMOUNT = 1000
 # Number of members with greater stake in council election test.
 COUNCIL_ELECTION_K = 2
-# Stake for runtime upgrade proposal test
-RUNTIME_UPGRADE_PROPOSAL_STAKE = 200
+# Balance to spend using spending proposal
+SPENDING_BALANCE = 1000
+# Minting capacity for content working group minting capacity test.
+MINTING_CAPACITY = 100020
+# Stake amount for Rome runtime upgrade proposal
+RUNTIME_UPGRADE_PROPOSAL_STAKE = 100000
+# Constantinople runtime path
+RUNTIME_WASM_PATH = ../../target/release/wbuild/joystream-node-runtime/joystream_node_runtime.compact.wasm

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

@@ -4,17 +4,20 @@
   "license": "GPL-3.0-only",
   "scripts": {
     "build": "tsc --build tsconfig.json",
-    "test": "mocha -r ts-node/register src/tests/*",
+    "test": "mocha -r ts-node/register src/tests/constantinople/*",
+    "test-migration": "mocha -r ts-node/register src/tests/rome/* && mocha -r ts-node/register src/tests/constantinople/*",
     "lint": "tslint --project tsconfig.json",
     "prettier": "prettier --write ./src"
   },
   "dependencies": {
-    "@joystream/types": "^0.7.0",
+    "@joystream/types": "",
+    "@rome/types@npm:@joystream/types": "^0.7.0",
     "@polkadot/api": "^0.96.1",
     "@polkadot/keyring": "^1.7.0-beta.5",
     "@types/bn.js": "^4.11.5",
     "bn.js": "^4.11.8",
     "dotenv": "^8.2.0",
+    "fs": "^0.0.1-security",
     "uuid": "^7.0.3"
   },
   "devDependencies": {

+ 4 - 4
tests/network-tests/src/tests/electingCouncilTest.ts → tests/network-tests/src/tests/constantinople/electingCouncilTest.ts

@@ -1,13 +1,13 @@
 import { membershipTest } from './membershipCreationTest';
 import { KeyringPair } from '@polkadot/keyring/types';
-import { ApiWrapper } from '../utils/apiWrapper';
+import { ApiWrapper } from './utils/apiWrapper';
 import { WsProvider, Keyring } from '@polkadot/api';
-import { initConfig } from '../utils/config';
+import { initConfig } from './utils/config';
 import BN = require('bn.js');
 import { registerJoystreamTypes, Seat } from '@joystream/types';
 import { assert } from 'chai';
 import { v4 as uuid } from 'uuid';
-import { Utils } from '../utils/utils';
+import { Utils } from './utils/utils';
 
 export function councilTest(m1KeyPairs: KeyringPair[], m2KeyPairs: KeyringPair[]) {
   initConfig();
@@ -118,7 +118,7 @@ export function councilTest(m1KeyPairs: KeyringPair[], m2KeyPairs: KeyringPair[]
   });
 }
 
-describe.skip('Council integration tests', () => {
+describe('Council integration tests', () => {
   const m1KeyPairs: KeyringPair[] = new Array();
   const m2KeyPairs: KeyringPair[] = new Array();
   membershipTest(m1KeyPairs);

+ 9 - 7
tests/network-tests/src/tests/membershipCreationTest.ts → tests/network-tests/src/tests/constantinople/membershipCreationTest.ts

@@ -4,8 +4,8 @@ import { Keyring } from '@polkadot/keyring';
 import { assert } from 'chai';
 import { KeyringPair } from '@polkadot/keyring/types';
 import BN = require('bn.js');
-import { ApiWrapper } from '../utils/apiWrapper';
-import { initConfig } from '../utils/config';
+import { ApiWrapper } from './utils/apiWrapper';
+import { initConfig } from './utils/config';
 import { v4 as uuid } from 'uuid';
 
 export function membershipTest(nKeyPairs: KeyringPair[]) {
@@ -50,8 +50,8 @@ export function membershipTest(nKeyPairs: KeyringPair[]) {
     );
     nKeyPairs.forEach((keyPair, index) =>
       apiWrapper
-        .getMembership(keyPair.address)
-        .then(membership => assert(!membership.isEmpty, `Account ${keyPair.address} is not a member`))
+        .getMemberIds(keyPair.address)
+        .then(membership => assert(membership.length > 0, `Account ${keyPair.address} is not a member`))
     );
   }).timeout(defaultTimeout);
 
@@ -65,7 +65,9 @@ export function membershipTest(nKeyPairs: KeyringPair[]) {
         )
       );
     await apiWrapper.buyMembership(aKeyPair, paidTerms, `late_member_${aKeyPair.address.substring(0, 8)}`, true);
-    apiWrapper.getMembership(aKeyPair.address).then(membership => assert(membership.isEmpty, 'Account A is a member'));
+    apiWrapper
+      .getMemberIds(aKeyPair.address)
+      .then(membership => assert(membership.length === 0, 'Account A is a member'));
   }).timeout(defaultTimeout);
 
   it('Account A was able to buy the membership with sufficient funds', async () => {
@@ -77,8 +79,8 @@ export function membershipTest(nKeyPairs: KeyringPair[]) {
       );
     await apiWrapper.buyMembership(aKeyPair, paidTerms, `late_member_${aKeyPair.address.substring(0, 8)}`);
     apiWrapper
-      .getMembership(aKeyPair.address)
-      .then(membership => assert(!membership.isEmpty, 'Account A is a not member'));
+      .getMemberIds(aKeyPair.address)
+      .then(membership => assert(membership.length > 0, 'Account A is a not member'));
   }).timeout(defaultTimeout);
 
   after(() => {

+ 371 - 0
tests/network-tests/src/tests/constantinople/utils/apiWrapper.ts

@@ -0,0 +1,371 @@
+import { ApiPromise, WsProvider } from '@polkadot/api';
+import { Option, Vec, Bytes, u32 } from '@polkadot/types';
+import { Codec } from '@polkadot/types/types';
+import { KeyringPair } from '@polkadot/keyring/types';
+import { UserInfo, PaidMembershipTerms, MemberId } from '@joystream/types/lib/members';
+import { Seat, VoteKind } from '@joystream/types';
+import { Balance, EventRecord } from '@polkadot/types/interfaces';
+import BN = require('bn.js');
+import { SubmittableExtrinsic } from '@polkadot/api/types';
+import { Sender } from './sender';
+import { Utils } from './utils';
+
+export class ApiWrapper {
+  private readonly api: ApiPromise;
+  private readonly sender: Sender;
+
+  public static async create(provider: WsProvider): Promise<ApiWrapper> {
+    const api = await ApiPromise.create({ provider });
+    return new ApiWrapper(api);
+  }
+
+  constructor(api: ApiPromise) {
+    this.api = api;
+    this.sender = new Sender(api);
+  }
+
+  public close() {
+    this.api.disconnect();
+  }
+
+  public async buyMembership(
+    account: KeyringPair,
+    paidTermsId: number,
+    name: string,
+    expectFailure = false
+  ): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.members.buyMembership(paidTermsId, new UserInfo({ handle: name, avatar_uri: '', about: '' })),
+      account,
+      expectFailure
+    );
+  }
+
+  public getMemberIds(address: string): Promise<MemberId[]> {
+    return this.api.query.members.memberIdsByControllerAccountId<Vec<MemberId>>(address);
+  }
+
+  public getBalance(address: string): Promise<Balance> {
+    return this.api.query.balances.freeBalance<Balance>(address);
+  }
+
+  public async transferBalance(from: KeyringPair, to: string, amount: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.balances.transfer(to, amount), from);
+  }
+
+  public getPaidMembershipTerms(paidTermsId: number): Promise<Option<PaidMembershipTerms>> {
+    return this.api.query.members.paidMembershipTermsById<Option<PaidMembershipTerms>>(paidTermsId);
+  }
+
+  public getMembershipFee(paidTermsId: number): Promise<BN> {
+    return this.getPaidMembershipTerms(paidTermsId).then(terms => terms.unwrap().fee.toBn());
+  }
+
+  public async transferBalanceToAccounts(from: KeyringPair, to: KeyringPair[], amount: BN): Promise<void> {
+    for (const keyPair of to) {
+      await this.transferBalance(from, keyPair.address, amount);
+    }
+    return;
+  }
+
+  private getBaseTxFee(): BN {
+    return this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionBaseFee).toBn();
+  }
+
+  private estimateTxFee(tx: SubmittableExtrinsic<'promise'>): BN {
+    const baseFee: BN = this.getBaseTxFee();
+    const byteFee: BN = this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionByteFee).toBn();
+    return Utils.calcTxLength(tx).mul(byteFee).add(baseFee);
+  }
+
+  public estimateBuyMembershipFee(account: KeyringPair, paidTermsId: number, name: string): BN {
+    return this.estimateTxFee(
+      this.api.tx.members.buyMembership(paidTermsId, new UserInfo({ handle: name, avatar_uri: '', about: '' }))
+    );
+  }
+
+  public estimateApplyForCouncilFee(amount: BN): BN {
+    return this.estimateTxFee(this.api.tx.councilElection.apply(amount));
+  }
+
+  public estimateVoteForCouncilFee(nominee: string, salt: string, stake: BN): BN {
+    const hashedVote: string = Utils.hashVote(nominee, salt);
+    return this.estimateTxFee(this.api.tx.councilElection.vote(hashedVote, stake));
+  }
+
+  public estimateRevealVoteFee(nominee: string, salt: string): BN {
+    const hashedVote: string = Utils.hashVote(nominee, salt);
+    return this.estimateTxFee(this.api.tx.councilElection.reveal(hashedVote, nominee, salt));
+  }
+
+  public estimateProposeRuntimeUpgradeFee(stake: BN, name: string, description: string, runtime: Bytes | string): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(stake, name, description, stake, runtime)
+    );
+  }
+
+  public estimateProposeTextFee(stake: BN, name: string, description: string, text: string): BN {
+    return this.estimateTxFee(this.api.tx.proposalsCodex.createTextProposal(stake, name, description, stake, text));
+  }
+
+  public estimateProposeSpendingFee(
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN,
+    destination: string
+  ): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSpendingProposal(stake, title, description, stake, balance, destination)
+    );
+  }
+
+  public estimateProposeWorkingGroupMintCapacityFee(title: string, description: string, stake: BN, balance: BN): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal(
+        stake,
+        title,
+        description,
+        stake,
+        balance
+      )
+    );
+  }
+
+  public estimateVoteForProposalFee(): BN {
+    return this.estimateTxFee(this.api.tx.proposalsEngine.vote(0, 0, 'Approve'));
+  }
+
+  private applyForCouncilElection(account: KeyringPair, amount: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.councilElection.apply(amount), account, false);
+  }
+
+  public batchApplyForCouncilElection(accounts: KeyringPair[], amount: BN): Promise<void[]> {
+    return Promise.all(
+      accounts.map(async keyPair => {
+        await this.applyForCouncilElection(keyPair, amount);
+      })
+    );
+  }
+
+  public async getCouncilElectionStake(address: string): Promise<BN> {
+    // TODO alter then `applicantStake` type will be introduced
+    return this.api.query.councilElection.applicantStakes(address).then(stake => {
+      const parsed = JSON.parse(stake.toString());
+      return new BN(parsed.new);
+    });
+  }
+
+  private voteForCouncilMember(account: KeyringPair, nominee: string, salt: string, stake: BN): Promise<void> {
+    const hashedVote: string = Utils.hashVote(nominee, salt);
+    return this.sender.signAndSend(this.api.tx.councilElection.vote(hashedVote, stake), account, false);
+  }
+
+  public batchVoteForCouncilMember(
+    accounts: KeyringPair[],
+    nominees: KeyringPair[],
+    salt: string[],
+    stake: BN
+  ): Promise<void[]> {
+    return Promise.all(
+      accounts.map(async (keyPair, index) => {
+        await this.voteForCouncilMember(keyPair, nominees[index].address, salt[index], stake);
+      })
+    );
+  }
+
+  private revealVote(account: KeyringPair, commitment: string, nominee: string, salt: string): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.councilElection.reveal(commitment, nominee, salt), account, false);
+  }
+
+  public batchRevealVote(accounts: KeyringPair[], nominees: KeyringPair[], salt: string[]): Promise<void[]> {
+    return Promise.all(
+      accounts.map(async (keyPair, index) => {
+        const commitment = Utils.hashVote(nominees[index].address, salt[index]);
+        await this.revealVote(keyPair, commitment, nominees[index].address, salt[index]);
+      })
+    );
+  }
+
+  // TODO consider using configurable genesis instead
+  public sudoStartAnnouncingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageAnnouncing(endsAtBlock)),
+      sudo,
+      false
+    );
+  }
+
+  public sudoStartVotingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageVoting(endsAtBlock)),
+      sudo,
+      false
+    );
+  }
+
+  public sudoStartRevealingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageRevealing(endsAtBlock)),
+      sudo,
+      false
+    );
+  }
+
+  public getBestBlock(): Promise<BN> {
+    return this.api.derive.chain.bestNumber();
+  }
+
+  public getCouncil(): Promise<Seat[]> {
+    return this.api.query.council.activeCouncil<Vec<Codec>>().then(seats => {
+      return (seats as unknown) as Seat[];
+    });
+  }
+
+  public getRuntime(): Promise<Bytes> {
+    return this.api.query.substrate.code<Bytes>();
+  }
+
+  public async proposeRuntime(
+    account: KeyringPair,
+    stake: BN,
+    name: string,
+    description: string,
+    runtime: Bytes | string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(memberId, name, description, stake, runtime),
+      account,
+      false
+    );
+  }
+
+  public async proposeText(
+    account: KeyringPair,
+    stake: BN,
+    name: string,
+    description: string,
+    text: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createTextProposal(memberId, name, description, stake, text),
+      account,
+      false
+    );
+  }
+
+  public async proposeSpending(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN,
+    destination: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSpendingProposal(memberId, title, description, stake, balance, destination),
+      account,
+      false
+    );
+  }
+
+  public async proposeWorkingGroupMintCapacity(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal(
+        memberId,
+        title,
+        description,
+        stake,
+        balance
+      ),
+      account,
+      false
+    );
+  }
+
+  public approveProposal(account: KeyringPair, memberId: BN, proposal: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.proposalsEngine.vote(memberId, proposal, 'Approve'), account, false);
+  }
+
+  public batchApproveProposal(council: KeyringPair[], proposal: BN): Promise<void[]> {
+    return Promise.all(
+      council.map(async keyPair => {
+        const memberId: BN = (await this.getMemberIds(keyPair.address))[0].toBn();
+        await this.approveProposal(keyPair, memberId, proposal);
+      })
+    );
+  }
+
+  public getBlockDuration(): BN {
+    return this.api.createType('Moment', this.api.consts.babe.expectedBlockTime).toBn();
+  }
+
+  public expectProposalCreated(): Promise<BN> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method && record.event.method.toString() === 'ProposalCreated') {
+            resolve(new BN(record.event.data[1].toString()));
+          }
+        });
+      });
+    });
+  }
+
+  public expectRuntimeUpgraded(): Promise<void> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method.toString() === 'RuntimeUpdated') {
+            resolve();
+          }
+        });
+      });
+    });
+  }
+
+  public expectProposalFinalized(): Promise<void> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (
+            record.event.method &&
+            record.event.method.toString() === 'ProposalStatusUpdated' &&
+            record.event.data[1].toString().includes('Executed')
+          ) {
+            resolve();
+          }
+        });
+      });
+    });
+  }
+
+  public getTotalIssuance(): Promise<BN> {
+    return this.api.query.balances.totalIssuance<Balance>();
+  }
+
+  public async getProposal(id: BN) {
+    const proposal = await this.api.query.proposalsEngine.proposals(id);
+    return;
+  }
+
+  public async getRequiredProposalStake(numerator: number, denominator: number): Promise<BN> {
+    const issuance: number = await (await this.getTotalIssuance()).toNumber();
+    const stake = (issuance * numerator) / denominator;
+    return new BN(stake.toFixed(0));
+  }
+
+  public getProposalCount(): Promise<BN> {
+    return this.api.query.proposalsEngine.proposalCount<u32>();
+  }
+}

+ 0 - 0
tests/network-tests/src/utils/config.ts → tests/network-tests/src/tests/constantinople/utils/config.ts


+ 0 - 1
tests/network-tests/src/utils/sender.ts → tests/network-tests/src/tests/constantinople/utils/sender.ts

@@ -37,7 +37,6 @@ export class Sender {
   ): Promise<void> {
     return new Promise(async (resolve, reject) => {
       const nonce: BN = await this.getNonce(account.address);
-      // console.log('sending transaction from ' + account.address + ' with nonce ' + nonce);
       const signedTx = tx.sign(account, { nonce });
       await signedTx
         .send(async result => {

+ 6 - 0
tests/network-tests/src/utils/utils.ts → tests/network-tests/src/tests/constantinople/utils/utils.ts

@@ -1,7 +1,9 @@
 import { IExtrinsic } from '@polkadot/types/types';
+import { Bytes } from '@polkadot/types';
 import { compactToU8a, stringToU8a } from '@polkadot/util';
 import { blake2AsHex } from '@polkadot/util-crypto';
 import BN = require('bn.js');
+import fs = require('fs');
 import { decodeAddress } from '@polkadot/keyring';
 import { Seat } from '@joystream/types';
 
@@ -42,4 +44,8 @@ export class Utils {
   public static getTotalStake(seat: Seat): BN {
     return new BN(+seat.stake.toString() + seat.backers.reduce((a, baker) => a + +baker.stake.toString(), 0));
   }
+
+  public static readRuntimeFromFile(path: string): string {
+    return '0x' + fs.readFileSync(path).toString('hex');
+  }
 }

+ 127 - 0
tests/network-tests/src/tests/rome/electingCouncilTest.ts

@@ -0,0 +1,127 @@
+import { membershipTest } from './membershipCreationTest';
+import { KeyringPair } from '@polkadot/keyring/types';
+import { ApiWrapper } from './utils/apiWrapper';
+import { WsProvider, Keyring } from '@polkadot/api';
+import { initConfig } from './utils/config';
+import BN = require('bn.js');
+import { registerJoystreamTypes, Seat } from '@rome/types';
+import { assert } from 'chai';
+import { v4 as uuid } from 'uuid';
+import { Utils } from './utils/utils';
+
+export function councilTest(m1KeyPairs: KeyringPair[], m2KeyPairs: KeyringPair[]) {
+  initConfig();
+  const keyring = new Keyring({ type: 'sr25519' });
+  const nodeUrl: string = process.env.NODE_URL!;
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
+  const K: number = +process.env.COUNCIL_ELECTION_K!;
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!);
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!);
+  const defaultTimeout: number = 120000;
+  let sudo: KeyringPair;
+  let apiWrapper: ApiWrapper;
+
+  before(async function () {
+    this.timeout(defaultTimeout);
+    registerJoystreamTypes();
+    const provider = new WsProvider(nodeUrl);
+    apiWrapper = await ApiWrapper.create(provider);
+  });
+
+  it('Electing a council test', async () => {
+    // Setup goes here because M keypairs are generated after before() function
+    sudo = keyring.addFromUri(sudoUri);
+    let now = await apiWrapper.getBestBlock();
+    const applyForCouncilFee: BN = apiWrapper.estimateApplyForCouncilFee(greaterStake);
+    const voteForCouncilFee: BN = apiWrapper.estimateVoteForCouncilFee(sudo.address, sudo.address, greaterStake);
+    const salt: string[] = new Array();
+    m1KeyPairs.forEach(() => {
+      salt.push(''.concat(uuid().replace(/-/g, '')));
+    });
+    const revealVoteFee: BN = apiWrapper.estimateRevealVoteFee(sudo.address, salt[0]);
+
+    // Topping the balances
+    await apiWrapper.transferBalanceToAccounts(sudo, m2KeyPairs, applyForCouncilFee.add(greaterStake));
+    await apiWrapper.transferBalanceToAccounts(
+      sudo,
+      m1KeyPairs,
+      voteForCouncilFee.add(revealVoteFee).add(greaterStake)
+    );
+
+    // First K members stake more
+    await apiWrapper.sudoStartAnnouncingPerion(sudo, now.addn(100));
+    await apiWrapper.batchApplyForCouncilElection(m2KeyPairs.slice(0, K), greaterStake);
+    m2KeyPairs.slice(0, K).forEach(keyPair =>
+      apiWrapper.getCouncilElectionStake(keyPair.address).then(stake => {
+        assert(
+          stake.eq(greaterStake),
+          `${keyPair.address} not applied correctrly for council election with stake ${stake} versus expected ${greaterStake}`
+        );
+      })
+    );
+
+    // Last members stake less
+    await apiWrapper.batchApplyForCouncilElection(m2KeyPairs.slice(K), lesserStake);
+    m2KeyPairs.slice(K).forEach(keyPair =>
+      apiWrapper.getCouncilElectionStake(keyPair.address).then(stake => {
+        assert(
+          stake.eq(lesserStake),
+          `${keyPair.address} not applied correctrly for council election with stake ${stake} versus expected ${lesserStake}`
+        );
+      })
+    );
+
+    // Voting
+    await apiWrapper.sudoStartVotingPerion(sudo, now.addn(100));
+    await apiWrapper.batchVoteForCouncilMember(
+      m1KeyPairs.slice(0, K),
+      m2KeyPairs.slice(0, K),
+      salt.slice(0, K),
+      lesserStake
+    );
+    await apiWrapper.batchVoteForCouncilMember(m1KeyPairs.slice(K), m2KeyPairs.slice(K), salt.slice(K), greaterStake);
+
+    // Revealing
+    await apiWrapper.sudoStartRevealingPerion(sudo, now.addn(100));
+    await apiWrapper.batchRevealVote(m1KeyPairs.slice(0, K), m2KeyPairs.slice(0, K), salt.slice(0, K));
+    await apiWrapper.batchRevealVote(m1KeyPairs.slice(K), m2KeyPairs.slice(K), salt.slice(K));
+    now = await apiWrapper.getBestBlock();
+
+    // Resolving election
+    // 3 is to ensure the revealing block is in future
+    await apiWrapper.sudoStartRevealingPerion(sudo, now.addn(3));
+    await Utils.wait(apiWrapper.getBlockDuration().muln(2.5).toNumber());
+    const seats: Seat[] = await apiWrapper.getCouncil();
+
+    // Preparing collections to increase assertion readability
+    const m2addresses: string[] = m2KeyPairs.map(keyPair => keyPair.address);
+    const m1addresses: string[] = m1KeyPairs.map(keyPair => keyPair.address);
+    const members: string[] = seats.map(seat => seat.member.toString());
+    const bakers: string[] = seats.reduce(
+      (array, seat) => array.concat(seat.backers.map(baker => baker.member.toString())),
+      new Array()
+    );
+
+    // Assertions
+    m2addresses.forEach(address => assert(members.includes(address), `Account ${address} is not in the council`));
+    m1addresses.forEach(address => assert(bakers.includes(address), `Account ${address} is not in the voters`));
+    seats.forEach(seat =>
+      assert(
+        Utils.getTotalStake(seat).eq(greaterStake.add(lesserStake)),
+        `Member ${seat.member} has unexpected stake ${Utils.getTotalStake(seat)}`
+      )
+    );
+  }).timeout(defaultTimeout);
+
+  after(() => {
+    apiWrapper.close();
+  });
+}
+
+describe.skip('Council integration tests', () => {
+  const m1KeyPairs: KeyringPair[] = new Array();
+  const m2KeyPairs: KeyringPair[] = new Array();
+  membershipTest(m1KeyPairs);
+  membershipTest(m2KeyPairs);
+  councilTest(m1KeyPairs, m2KeyPairs);
+});

+ 94 - 0
tests/network-tests/src/tests/rome/membershipCreationTest.ts

@@ -0,0 +1,94 @@
+import { WsProvider } from '@polkadot/api';
+import { registerJoystreamTypes } from '@rome/types';
+import { Keyring } from '@polkadot/keyring';
+import { assert } from 'chai';
+import { KeyringPair } from '@polkadot/keyring/types';
+import BN = require('bn.js');
+import { ApiWrapper } from './utils/apiWrapper';
+import { initConfig } from './utils/config';
+import { v4 as uuid } from 'uuid';
+
+export function membershipTest(nKeyPairs: KeyringPair[]) {
+  initConfig();
+  const keyring = new Keyring({ type: 'sr25519' });
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!;
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!;
+  const nodeUrl: string = process.env.NODE_URL!;
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
+  const defaultTimeout: number = 30000;
+  let apiWrapper: ApiWrapper;
+  let sudo: KeyringPair;
+  let aKeyPair: KeyringPair;
+  let membershipFee: BN;
+  let membershipTransactionFee: BN;
+
+  before(async function () {
+    this.timeout(defaultTimeout);
+    registerJoystreamTypes();
+    const provider = new WsProvider(nodeUrl);
+    apiWrapper = await ApiWrapper.create(provider);
+    sudo = keyring.addFromUri(sudoUri);
+    for (let i = 0; i < N; i++) {
+      nKeyPairs.push(keyring.addFromUri(i + uuid().substring(0, 8)));
+    }
+    aKeyPair = keyring.addFromUri(uuid().substring(0, 8));
+    membershipFee = await apiWrapper.getMembershipFee(paidTerms);
+    membershipTransactionFee = apiWrapper.estimateBuyMembershipFee(
+      sudo,
+      paidTerms,
+      'member_name_which_is_longer_than_expected'
+    );
+    await apiWrapper.transferBalanceToAccounts(sudo, nKeyPairs, membershipTransactionFee.add(new BN(membershipFee)));
+    await apiWrapper.transferBalance(sudo, aKeyPair.address, membershipTransactionFee);
+  });
+
+  it('Buy membeship is accepted with sufficient funds', async () => {
+    await Promise.all(
+      nKeyPairs.map(async (keyPair, index) => {
+        await apiWrapper.buyMembership(keyPair, paidTerms, `new_member_${index}${keyPair.address.substring(0, 8)}`);
+      })
+    );
+    nKeyPairs.forEach((keyPair, index) =>
+      apiWrapper
+        .getMemberIds(keyPair.address)
+        .then(membership => assert(membership.length > 0, `Account ${keyPair.address} is not a member`))
+    );
+  }).timeout(defaultTimeout);
+
+  it('Account A can not buy the membership with insufficient funds', async () => {
+    await apiWrapper
+      .getBalance(aKeyPair.address)
+      .then(balance =>
+        assert(
+          balance.toBn() < membershipFee.add(membershipTransactionFee),
+          'Account A already have sufficient balance to purchase membership'
+        )
+      );
+    await apiWrapper.buyMembership(aKeyPair, paidTerms, `late_member_${aKeyPair.address.substring(0, 8)}`, true);
+    apiWrapper
+      .getMemberIds(aKeyPair.address)
+      .then(membership => assert(membership.length === 0, 'Account A is a member'));
+  }).timeout(defaultTimeout);
+
+  it('Account A was able to buy the membership with sufficient funds', async () => {
+    await apiWrapper.transferBalance(sudo, aKeyPair.address, membershipFee.add(membershipTransactionFee));
+    apiWrapper
+      .getBalance(aKeyPair.address)
+      .then(balance =>
+        assert(balance.toBn() >= membershipFee, 'The account balance is insufficient to purchase membership')
+      );
+    await apiWrapper.buyMembership(aKeyPair, paidTerms, `late_member_${aKeyPair.address.substring(0, 8)}`);
+    apiWrapper
+      .getMemberIds(aKeyPair.address)
+      .then(membership => assert(membership.length > 0, 'Account A is a not member'));
+  }).timeout(defaultTimeout);
+
+  after(() => {
+    apiWrapper.close();
+  });
+}
+
+describe.skip('Membership integration tests', () => {
+  const nKeyPairs: KeyringPair[] = new Array();
+  membershipTest(nKeyPairs);
+});

+ 10 - 10
tests/network-tests/src/tests/updateRuntimeTest.ts → tests/network-tests/src/tests/rome/romeRuntimeUpgradeTest.ts

@@ -1,12 +1,12 @@
-import { initConfig } from '../utils/config';
+import { initConfig } from './utils/config';
 import { Keyring, WsProvider } from '@polkadot/api';
-import { Bytes } from '@polkadot/types';
 import { KeyringPair } from '@polkadot/keyring/types';
 import { membershipTest } from './membershipCreationTest';
 import { councilTest } from './electingCouncilTest';
-import { registerJoystreamTypes } from '@joystream/types';
-import { ApiWrapper } from '../utils/apiWrapper';
+import { registerJoystreamTypes } from '@rome/types';
+import { ApiWrapper } from './utils/apiWrapper';
 import BN = require('bn.js');
+import { Utils } from './utils/utils';
 
 describe('Runtime upgrade integration tests', () => {
   initConfig();
@@ -14,18 +14,20 @@ describe('Runtime upgrade integration tests', () => {
   const nodeUrl: string = process.env.NODE_URL!;
   const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
   const proposalStake: BN = new BN(+process.env.RUNTIME_UPGRADE_PROPOSAL_STAKE!);
-  const defaultTimeout: number = 120000;
+  const runtimePath: string = process.env.RUNTIME_WASM_PATH!;
+  const defaultTimeout: number = 180000;
 
   const m1KeyPairs: KeyringPair[] = new Array();
   const m2KeyPairs: KeyringPair[] = new Array();
 
   let apiWrapper: ApiWrapper;
   let sudo: KeyringPair;
+  let provider: WsProvider;
 
   before(async function () {
     this.timeout(defaultTimeout);
     registerJoystreamTypes();
-    const provider = new WsProvider(nodeUrl);
+    provider = new WsProvider(nodeUrl);
     apiWrapper = await ApiWrapper.create(provider);
   });
 
@@ -36,7 +38,7 @@ describe('Runtime upgrade integration tests', () => {
   it('Upgrading the runtime test', async () => {
     // Setup
     sudo = keyring.addFromUri(sudoUri);
-    const runtime: Bytes = await apiWrapper.getRuntime();
+    const runtime: string = Utils.readRuntimeFromFile(runtimePath);
     const description: string = 'runtime upgrade proposal which is used for API integration testing';
     const runtimeProposalFee: BN = apiWrapper.estimateProposeRuntimeUpgradeFee(
       proposalStake,
@@ -44,7 +46,7 @@ describe('Runtime upgrade integration tests', () => {
       description,
       runtime
     );
-    const runtimeVoteFee: BN = apiWrapper.estimateVoteForProposalFee();
+    const runtimeVoteFee: BN = apiWrapper.estimateVoteForRuntimeProposalFee();
 
     // Topping the balances
     await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, runtimeProposalFee.add(proposalStake));
@@ -67,8 +69,6 @@ describe('Runtime upgrade integration tests', () => {
     await runtimePromise;
   }).timeout(defaultTimeout);
 
-  membershipTest(new Array<KeyringPair>());
-
   after(() => {
     apiWrapper.close();
   });

+ 121 - 8
tests/network-tests/src/utils/apiWrapper.ts → tests/network-tests/src/tests/rome/utils/apiWrapper.ts

@@ -1,9 +1,9 @@
 import { ApiPromise, WsProvider } from '@polkadot/api';
-import { Option, Vec, Bytes } from '@polkadot/types';
+import { Option, Vec, Bytes, u32 } from '@polkadot/types';
 import { Codec } from '@polkadot/types/types';
 import { KeyringPair } from '@polkadot/keyring/types';
-import { UserInfo, PaidMembershipTerms } from '@joystream/types/lib/members';
-import { Seat, VoteKind } from '@joystream/types';
+import { UserInfo, PaidMembershipTerms, MemberId } from '@rome/types/lib/members';
+import { Seat, VoteKind } from '@rome/types';
 import { Balance, EventRecord } from '@polkadot/types/interfaces';
 import BN = require('bn.js');
 import { SubmittableExtrinsic } from '@polkadot/api/types';
@@ -41,8 +41,8 @@ export class ApiWrapper {
     );
   }
 
-  public getMembership(address: string): Promise<any> {
-    return this.api.query.members.memberIdsByControllerAccountId(address);
+  public getMemberIds(address: string): Promise<MemberId[]> {
+    return this.api.query.members.memberIdsByControllerAccountId<Vec<MemberId>>(address);
   }
 
   public getBalance(address: string): Promise<Balance> {
@@ -99,14 +99,46 @@ export class ApiWrapper {
     return this.estimateTxFee(this.api.tx.councilElection.reveal(hashedVote, nominee, salt));
   }
 
-  public estimateProposeRuntimeUpgradeFee(stake: BN, name: string, description: string, runtime: Bytes): BN {
+  public estimateProposeRuntimeUpgradeFee(stake: BN, name: string, description: string, runtime: string): BN {
     return this.estimateTxFee(this.api.tx.proposals.createProposal(stake, name, description, runtime));
   }
 
-  public estimateVoteForProposalFee(): BN {
+  public estimateProposeTextFee(stake: BN, name: string, description: string, text: string): BN {
+    return this.estimateTxFee(this.api.tx.proposalsCodex.createTextProposal(stake, name, description, stake, text));
+  }
+
+  public estimateProposeSpendingFee(
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN,
+    destination: string
+  ): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSpendingProposal(stake, title, description, stake, balance, destination)
+    );
+  }
+
+  public estimateProposeWorkingGroupMintCapacityFee(title: string, description: string, stake: BN, balance: BN): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal(
+        stake,
+        title,
+        description,
+        stake,
+        balance
+      )
+    );
+  }
+
+  public estimateVoteForRuntimeProposalFee(): BN {
     return this.estimateTxFee(this.api.tx.proposals.voteOnProposal(0, 'Approve'));
   }
 
+  public newEstimate(): BN {
+    return new BN(100);
+  }
+
   private applyForCouncilElection(account: KeyringPair, amount: BN): Promise<void> {
     return this.sender.signAndSend(this.api.tx.councilElection.apply(amount), account, false);
   }
@@ -202,7 +234,7 @@ export class ApiWrapper {
     stake: BN,
     name: string,
     description: string,
-    runtime: Bytes
+    runtime: string
   ): Promise<void> {
     return this.sender.signAndSend(
       this.api.tx.proposals.createProposal(stake, name, description, runtime),
@@ -211,6 +243,58 @@ export class ApiWrapper {
     );
   }
 
+  public async proposeText(
+    account: KeyringPair,
+    stake: BN,
+    name: string,
+    description: string,
+    text: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createTextProposal(memberId, name, description, stake, text),
+      account,
+      false
+    );
+  }
+
+  public async proposeSpending(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN,
+    destination: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSpendingProposal(memberId, title, description, stake, balance, destination),
+      account,
+      false
+    );
+  }
+
+  public async proposeWorkingGroupMintCapacity(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal(
+        memberId,
+        title,
+        description,
+        stake,
+        balance
+      ),
+      account,
+      false
+    );
+  }
+
   public approveProposal(account: KeyringPair, proposal: BN): Promise<void> {
     return this.sender.signAndSend(
       this.api.tx.proposals.voteOnProposal(proposal, new VoteKind('Approve')),
@@ -254,4 +338,33 @@ export class ApiWrapper {
       });
     });
   }
+
+  public expectProposalFinalized(): Promise<void> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (
+            record.event.method.toString() === 'ProposalStatusUpdated' &&
+            record.event.data[1].toString().includes('Finalized')
+          ) {
+            resolve();
+          }
+        });
+      });
+    });
+  }
+
+  public getTotalIssuance(): Promise<BN> {
+    return this.api.query.balances.totalIssuance<Balance>();
+  }
+
+  public async getRequiredProposalStake(numerator: number, denominator: number): Promise<BN> {
+    const issuance: number = await (await this.getTotalIssuance()).toNumber();
+    const stake = (issuance * numerator) / denominator;
+    return new BN(stake.toFixed(0));
+  }
+
+  public getProposalCount(): Promise<BN> {
+    return this.api.query.proposalsEngine.proposalCount<u32>();
+  }
 }

+ 5 - 0
tests/network-tests/src/tests/rome/utils/config.ts

@@ -0,0 +1,5 @@
+import { config } from 'dotenv';
+
+export function initConfig() {
+  config();
+}

+ 66 - 0
tests/network-tests/src/tests/rome/utils/sender.ts

@@ -0,0 +1,66 @@
+import BN = require('bn.js');
+import { ApiPromise } from '@polkadot/api';
+import { Index } from '@polkadot/types/interfaces';
+import { SubmittableExtrinsic } from '@polkadot/api/types';
+import { KeyringPair } from '@polkadot/keyring/types';
+
+export class Sender {
+  private readonly api: ApiPromise;
+  private static nonceMap: Map<string, BN> = new Map();
+
+  constructor(api: ApiPromise) {
+    this.api = api;
+  }
+
+  private async getNonce(address: string): Promise<BN> {
+    let oncahinNonce: BN = new BN(0);
+    if (!Sender.nonceMap.get(address)) {
+      oncahinNonce = await this.api.query.system.accountNonce<Index>(address);
+    }
+    let nonce: BN | undefined = Sender.nonceMap.get(address);
+    if (!nonce) {
+      nonce = oncahinNonce;
+    }
+    const nextNonce: BN = nonce.addn(1);
+    Sender.nonceMap.set(address, nextNonce);
+    return nonce;
+  }
+
+  private clearNonce(address: string): void {
+    Sender.nonceMap.delete(address);
+  }
+
+  public async signAndSend(
+    tx: SubmittableExtrinsic<'promise'>,
+    account: KeyringPair,
+    expectFailure = false
+  ): Promise<void> {
+    return new Promise(async (resolve, reject) => {
+      const nonce: BN = await this.getNonce(account.address);
+      const signedTx = tx.sign(account, { nonce });
+      await signedTx
+        .send(async result => {
+          if (result.status.isFinalized === true && result.events !== undefined) {
+            result.events.forEach(event => {
+              if (event.event.method === 'ExtrinsicFailed') {
+                if (expectFailure) {
+                  resolve();
+                } else {
+                  reject(new Error('Extrinsic failed unexpectedly'));
+                }
+              }
+            });
+            resolve();
+          }
+          if (result.status.isFuture) {
+            console.log('nonce ' + nonce + ' for account ' + account.address + ' is in future');
+            this.clearNonce(account.address);
+            reject(new Error('Extrinsic nonce is in future'));
+          }
+        })
+        .catch(error => {
+          reject(error);
+        });
+    });
+  }
+}

+ 50 - 0
tests/network-tests/src/tests/rome/utils/utils.ts

@@ -0,0 +1,50 @@
+import { IExtrinsic } from '@polkadot/types/types';
+import { compactToU8a, stringToU8a, u8aToHex } from '@polkadot/util';
+import { blake2AsHex } from '@polkadot/util-crypto';
+import BN = require('bn.js');
+import fs = require('fs');
+import { decodeAddress } from '@polkadot/keyring';
+import { Seat } from '@rome/types';
+
+export class Utils {
+  private static LENGTH_ADDRESS = 32 + 1; // publicKey + prefix
+  private static LENGTH_ERA = 2; // assuming mortals
+  private static LENGTH_SIGNATURE = 64; // assuming ed25519 or sr25519
+  private static LENGTH_VERSION = 1; // 0x80 & version
+
+  public static calcTxLength = (extrinsic?: IExtrinsic | null, nonce?: BN): BN => {
+    return new BN(
+      Utils.LENGTH_VERSION +
+        Utils.LENGTH_ADDRESS +
+        Utils.LENGTH_SIGNATURE +
+        Utils.LENGTH_ERA +
+        compactToU8a(nonce || 0).length +
+        (extrinsic ? extrinsic.encodedLength : 0)
+    );
+  };
+
+  /** hash(accountId + salt) */
+  public static hashVote(accountId: string, salt: string): string {
+    const accountU8a = decodeAddress(accountId);
+    const saltU8a = stringToU8a(salt);
+    const voteU8a = new Uint8Array(accountU8a.length + saltU8a.length);
+    voteU8a.set(accountU8a);
+    voteU8a.set(saltU8a, accountU8a.length);
+
+    const hash = blake2AsHex(voteU8a, 256);
+    // console.log('Vote hash:', hash, 'for', { accountId, salt });
+    return hash;
+  }
+
+  public static wait(ms: number): Promise<void> {
+    return new Promise(resolve => setTimeout(resolve, ms));
+  }
+
+  public static getTotalStake(seat: Seat): BN {
+    return new BN(+seat.stake.toString() + seat.backers.reduce((a, baker) => a + +baker.stake.toString(), 0));
+  }
+
+  public static readRuntimeFromFile(path: string): string {
+    return u8aToHex(fs.readFileSync(path));
+  }
+}