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

Merge branch 'cli-olympia-initial' into cli-query-node

Leszek Wiesner 3 лет назад
Родитель
Сommit
9781109ba6
100 измененных файлов с 26654 добавлено и 1458 удалено
  1. 2 2
      .github/workflows/joystream-node-benchmarks.yml
  2. 28 2
      Cargo.lock
  3. 5 1
      Cargo.toml
  4. 1 0
      analyses/bench/.gitignore
  5. 8319 0
      analyses/bench/Cargo.lock
  6. 49 0
      analyses/bench/Cargo.toml
  7. 60 0
      analyses/bench/src/common.rs
  8. 296 0
      analyses/bench/src/construct.rs
  9. 161 0
      analyses/bench/src/core.rs
  10. 70 0
      analyses/bench/src/generator.rs
  11. 204 0
      analyses/bench/src/main.rs
  12. 167 0
      analyses/bench/src/plots.rs
  13. 72 0
      analyses/bench/src/simple_trie.rs
  14. 4758 0
      analyses/bench/src/state_sizes.rs
  15. 131 0
      analyses/bench/src/tempdb.rs
  16. 355 0
      analyses/bench/src/trie.rs
  17. 370 0
      analyses/bench/src/trie_series.rs
  18. 58 3
      analyses/fee-analysis/README.md
  19. 1 1
      cli/package.json
  20. 19 7
      cli/src/Api.ts
  21. 1 0
      cli/src/Types.ts
  22. 5 1
      cli/src/base/AccountsCommandBase.ts
  23. 25 0
      cli/src/commands/working-groups/createOpening.ts
  24. 1 1
      node/Cargo.toml
  25. 6 5
      node/src/chain_spec/forum_config.rs
  26. 1 1
      node/src/service.rs
  27. 4 4
      runtime-modules/blog/Cargo.toml
  28. 74 18
      runtime-modules/blog/src/benchmarking.rs
  29. 7 3
      runtime-modules/blog/src/errors.rs
  30. 207 47
      runtime-modules/blog/src/lib.rs
  31. 56 22
      runtime-modules/blog/src/mock.rs
  32. 461 54
      runtime-modules/blog/src/tests.rs
  33. 53 0
      runtime-modules/bounty/Cargo.toml
  34. 171 0
      runtime-modules/bounty/src/actors.rs
  35. 1087 0
      runtime-modules/bounty/src/benchmarking.rs
  36. 1731 0
      runtime-modules/bounty/src/lib.rs
  37. 182 0
      runtime-modules/bounty/src/stages.rs
  38. 816 0
      runtime-modules/bounty/src/tests/fixtures.rs
  39. 559 0
      runtime-modules/bounty/src/tests/mocks.rs
  40. 3419 0
      runtime-modules/bounty/src/tests/mod.rs
  41. 16 0
      runtime-modules/common/src/council.rs
  42. 4 41
      runtime-modules/common/src/lib.rs
  43. 60 0
      runtime-modules/common/src/membership.rs
  44. 0 19
      runtime-modules/common/src/origin.rs
  45. 1 1
      runtime-modules/common/src/working_group.rs
  46. 1 1
      runtime-modules/constitution/src/lib.rs
  47. 9 5
      runtime-modules/content-directory/src/lib.rs
  48. 6 6
      runtime-modules/content-directory/src/mock.rs
  49. 24 20
      runtime-modules/content-directory/src/schema/property.rs
  50. 6 2
      runtime-modules/council/src/benchmarking.rs
  51. 26 12
      runtime-modules/council/src/lib.rs
  52. 9 7
      runtime-modules/council/src/mock.rs
  53. 27 2
      runtime-modules/council/src/tests.rs
  54. 223 268
      runtime-modules/forum/src/benchmarking.rs
  55. 321 180
      runtime-modules/forum/src/lib.rs
  56. 134 77
      runtime-modules/forum/src/mock.rs
  57. 558 343
      runtime-modules/forum/src/tests.rs
  58. 10 5
      runtime-modules/membership/src/benchmarking.rs
  59. 63 16
      runtime-modules/membership/src/lib.rs
  60. 6 2
      runtime-modules/membership/src/tests/fixtures.rs
  61. 22 16
      runtime-modules/membership/src/tests/mock.rs
  62. 69 8
      runtime-modules/membership/src/tests/mod.rs
  63. 1 1
      runtime-modules/proposals/codex/src/benchmarking.rs
  64. 2 3
      runtime-modules/proposals/codex/src/lib.rs
  65. 31 14
      runtime-modules/proposals/codex/src/tests/mock.rs
  66. 1 7
      runtime-modules/proposals/codex/src/tests/mod.rs
  67. 1 1
      runtime-modules/proposals/codex/src/types.rs
  68. 4 5
      runtime-modules/proposals/discussion/Cargo.toml
  69. 36 6
      runtime-modules/proposals/discussion/src/benchmarking.rs
  70. 165 21
      runtime-modules/proposals/discussion/src/lib.rs
  71. 29 13
      runtime-modules/proposals/discussion/src/tests/mock.rs
  72. 232 9
      runtime-modules/proposals/discussion/src/tests/mod.rs
  73. 7 1
      runtime-modules/proposals/discussion/src/types.rs
  74. 4 3
      runtime-modules/proposals/engine/src/lib.rs
  75. 9 7
      runtime-modules/proposals/engine/src/tests/mock/mod.rs
  76. 3 3
      runtime-modules/proposals/engine/src/tests/mod.rs
  77. 1 1
      runtime-modules/proposals/engine/src/types/proposal_statuses.rs
  78. 19 8
      runtime-modules/referendum/src/benchmarking.rs
  79. 17 13
      runtime-modules/referendum/src/lib.rs
  80. 17 12
      runtime-modules/referendum/src/mock.rs
  81. 12 8
      runtime-modules/service-discovery/src/mock.rs
  82. 3 3
      runtime-modules/staking-handler/src/lib.rs
  83. 2 2
      runtime-modules/staking-handler/src/mock.rs
  84. 1 1
      runtime-modules/storage/src/data_directory.rs
  85. 1 1
      runtime-modules/storage/src/data_object_type_registry.rs
  86. 12 8
      runtime-modules/storage/src/tests/mock.rs
  87. 6 5
      runtime-modules/utility/src/benchmarking.rs
  88. 19 12
      runtime-modules/utility/src/tests/mocks.rs
  89. 3 3
      runtime-modules/working-group/src/benchmarking.rs
  90. 26 1
      runtime-modules/working-group/src/checks.rs
  91. 114 20
      runtime-modules/working-group/src/lib.rs
  92. 44 8
      runtime-modules/working-group/src/tests/fixtures.rs
  93. 6 4
      runtime-modules/working-group/src/tests/hiring_workflow.rs
  94. 9 5
      runtime-modules/working-group/src/tests/mock.rs
  95. 119 14
      runtime-modules/working-group/src/tests/mod.rs
  96. 17 3
      runtime-modules/working-group/src/types.rs
  97. 7 4
      runtime/Cargo.toml
  98. 11 0
      runtime/src/constants.rs
  99. 65 19
      runtime/src/lib.rs
  100. 11 6
      runtime/src/runtime_api.rs

+ 2 - 2
.github/workflows/joystream-node-benchmarks.yml

@@ -41,7 +41,7 @@ jobs:
 
       - name: Generate Weights
         run: |
-          ./scripts/generate-weights.sh
+          ./scripts/generate-weights.sh 5 2
           # Show any changes in computed weights
           git diff
-        if: env.GIT_DIFF
+        if: env.GIT_DIFF

+ 28 - 2
Cargo.lock

@@ -1,5 +1,7 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+version = 3
+
 [[package]]
 name = "Inflector"
 version = "0.11.4"
@@ -2275,7 +2277,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node"
-version = "4.0.2"
+version = "4.1.0"
 dependencies = [
  "frame-benchmarking",
  "frame-benchmarking-cli",
@@ -2336,7 +2338,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node-runtime"
-version = "8.0.1"
+version = "8.1.0"
 dependencies = [
  "frame-benchmarking",
  "frame-executive",
@@ -2352,6 +2354,7 @@ dependencies = [
  "pallet-babe",
  "pallet-balances",
  "pallet-blog",
+ "pallet-bounty",
  "pallet-common",
  "pallet-constitution",
  "pallet-content-directory",
@@ -3756,6 +3759,29 @@ dependencies = [
  "sp-std",
 ]
 
+[[package]]
+name = "pallet-bounty"
+version = "1.0.1"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "pallet-common",
+ "pallet-council",
+ "pallet-membership",
+ "pallet-referendum",
+ "pallet-staking-handler",
+ "pallet-timestamp",
+ "parity-scale-codec",
+ "serde",
+ "sp-arithmetic",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 name = "pallet-common"
 version = "4.0.0"

+ 5 - 1
Cargo.toml

@@ -16,10 +16,14 @@ members = [
 	"runtime-modules/content-directory",
 	"runtime-modules/constitution",
 	"runtime-modules/staking-handler",
+	"runtime-modules/bounty",
     "runtime-modules/blog",
     "runtime-modules/utility",
 	"node",
-	"utils/chain-spec-builder/"
+	"utils/chain-spec-builder/",
+]
+exclude = [
+    "analyses/bench"
 ]
 
 [profile.release]

+ 1 - 0
analyses/bench/.gitignore

@@ -0,0 +1 @@
+*.png

+ 8319 - 0
analyses/bench/Cargo.lock

@@ -0,0 +1,8319 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "Inflector"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
+[[package]]
+name = "addr2line"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
+
+[[package]]
+name = "adler32"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
+
+[[package]]
+name = "aead"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "aes"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd2bc6d3f370b5666245ff421e231cba4353df936e26986d2918e61a8fd6aef6"
+dependencies = [
+ "aes-soft",
+ "aesni",
+ "block-cipher",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0301c9e9c443494d970a07885e8cf3e587bae8356a1d5abd0999068413f7205f"
+dependencies = [
+ "aead",
+ "aes",
+ "block-cipher",
+ "ghash",
+ "subtle 2.3.0",
+]
+
+[[package]]
+name = "aes-soft"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63dd91889c49327ad7ef3b500fd1109dbd3c509a03db0d4a9ce413b79f575cb6"
+dependencies = [
+ "block-cipher",
+ "byteorder 1.3.4",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "aesni"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6fe808308bb07d393e2ea47780043ec47683fcf19cf5efc8ca51c50cc8c68a"
+dependencies = [
+ "block-cipher",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "ahash"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29661b60bec623f0586702976ff4d0c9942dcb6723161c2df0eea78455cfedfb"
+dependencies = [
+ "const-random",
+]
+
+[[package]]
+name = "ahash"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
+
+[[package]]
+name = "ahash"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6789e291be47ace86a60303502173d84af8327e3627ecf334356ee0f87a164c"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alga"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2"
+dependencies = [
+ "approx",
+ "num-complex",
+ "num-traits",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
+
+[[package]]
+name = "approx"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "arc-swap"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
+
+[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
+name = "arrayvec"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
+dependencies = [
+ "nodrop",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "asn1_der"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fce6b6a0ffdafebd82c87e79e3f40e8d2c523e5fea5566ff6b90509bf98d638"
+dependencies = [
+ "asn1_der_derive",
+]
+
+[[package]]
+name = "asn1_der_derive"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d0864d84b8e07b145449be9a8537db86bf9de5ce03b913214694643b4743502"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-channel"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "once_cell 1.5.2",
+ "vec-arena",
+]
+
+[[package]]
+name = "async-global-executor"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73079b49cd26b8fd5a15f68fc7707fc78698dc2a3d61430f2a7a9430230dfa04"
+dependencies = [
+ "async-executor",
+ "async-io",
+ "futures-lite",
+ "num_cpus",
+ "once_cell 1.5.2",
+]
+
+[[package]]
+name = "async-io"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd"
+dependencies = [
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "libc",
+ "log",
+ "nb-connect",
+ "once_cell 1.5.2",
+ "parking",
+ "polling",
+ "vec-arena",
+ "waker-fn",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "async-mutex"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-std"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7e82538bc65a25dbdff70e4c5439d52f068048ab97cdea0acd73f131594caa1"
+dependencies = [
+ "async-global-executor",
+ "async-io",
+ "async-mutex",
+ "blocking",
+ "crossbeam-utils 0.8.1",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-lite",
+ "gloo-timers",
+ "kv-log-macro",
+ "log",
+ "memchr",
+ "num_cpus",
+ "once_cell 1.5.2",
+ "pin-project-lite 0.1.11",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "async-task"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
+
+[[package]]
+name = "async-tls"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df097e3f506bec0e1a24f06bb3c962c228f36671de841ff579cb99f371772634"
+dependencies = [
+ "futures 0.3.8",
+ "rustls",
+ "webpki",
+ "webpki-roots 0.19.0",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atomic"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64f46ca51dca4837f1520754d1c8c36636356b81553d928dc9c177025369a06e"
+
+[[package]]
+name = "atomic-waker"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "autocfg"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "backtrace"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
+dependencies = [
+ "addr2line",
+ "cfg-if 1.0.0",
+ "libc",
+ "miniz_oxide 0.4.3",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base58"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83"
+
+[[package]]
+name = "base64"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
+
+[[package]]
+name = "base64"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bindgen"
+version = "0.54.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "cfg-if 0.1.10",
+ "clang-sys",
+ "clap",
+ "env_logger",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "which",
+]
+
+[[package]]
+name = "bip39"
+version = "0.6.0-beta.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7059804e226b3ac116519a252d7f5fb985a5ccc0e93255e036a5f7e7283323f4"
+dependencies = [
+ "failure",
+ "hashbrown 0.1.8",
+ "hmac",
+ "once_cell 0.1.8",
+ "pbkdf2",
+ "rand 0.6.5",
+ "sha2 0.8.2",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "bitmask"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5da9b3d9f6f585199287a473f4f8dfab6566cf827d15c00c219f53c645687ead"
+
+[[package]]
+name = "bitvec"
+version = "0.17.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c"
+dependencies = [
+ "either",
+ "radium",
+]
+
+[[package]]
+name = "blake2"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a5720225ef5daecf08657f23791354e1685a8c91a4c60c7f3d3b2892f978f4"
+dependencies = [
+ "crypto-mac 0.8.0",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "blake2-rfc"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
+dependencies = [
+ "arrayvec 0.4.12",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "blake2b_simd"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
+dependencies = [
+ "arrayref",
+ "arrayvec 0.5.2",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "blake2s_simd"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2"
+dependencies = [
+ "arrayref",
+ "arrayvec 0.5.2",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding 0.1.5",
+ "byte-tools",
+ "byteorder 1.3.4",
+ "generic-array 0.12.3",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "block-padding 0.2.1",
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "block-cipher"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f337a3e6da609650eb74e02bc9fac7b735049f7623ab12f2e4c719316fcc7e80"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
+
+[[package]]
+name = "blocking"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+ "once_cell 1.5.2",
+]
+
+[[package]]
+name = "bs58"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb"
+
+[[package]]
+name = "bs58"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
+
+[[package]]
+name = "bstr"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
+
+[[package]]
+name = "byte-slice-cast"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3"
+
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
+[[package]]
+name = "bytemuck"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
+
+[[package]]
+name = "byteorder"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855"
+
+[[package]]
+name = "byteorder"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+
+[[package]]
+name = "bytes"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
+dependencies = [
+ "byteorder 1.3.4",
+ "either",
+ "iovec",
+]
+
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
+[[package]]
+name = "c_linked_list"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b"
+
+[[package]]
+name = "cache-padded"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
+
+[[package]]
+name = "cc"
+version = "1.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cexpr"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chacha20"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "244fbce0d47e97e8ef2f63b81d5e05882cb518c68531eb33194990d7b7e85845"
+dependencies = [
+ "stream-cipher",
+ "zeroize",
+]
+
+[[package]]
+name = "chacha20poly1305"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bf18d374d66df0c05cdddd528a7db98f78c28e2519b120855c4f84c5027b1f5"
+dependencies = [
+ "aead",
+ "chacha20",
+ "poly1305",
+ "stream-cipher",
+ "zeroize",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "clang-sys"
+version = "0.29.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term 0.11.0",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "cloudabi"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "concurrent-queue"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
+dependencies = [
+ "cache-padded",
+]
+
+[[package]]
+name = "const-random"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "486d435a7351580347279f374cb8a3c16937485441db80181357b7c4d70f17ed"
+dependencies = [
+ "const-random-macro",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a84d8ff70e3ec52311109b019c27672b4c1929e4cf7c18bcf0cd9fb5e230be"
+dependencies = [
+ "getrandom 0.2.0",
+ "lazy_static",
+ "proc-macro-hack",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "const_fn"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
+name = "core-foundation"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
+
+[[package]]
+name = "core-graphics"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-text"
+version = "15.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "131b3fd1f8bd5db9f2b398fa4fdb6008c64afc04d447c306ac2c7e98fba2a61d"
+dependencies = [
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "cpuid-bool"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
+
+[[package]]
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils 0.8.1",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
+dependencies = [
+ "crossbeam-epoch 0.8.2",
+ "crossbeam-utils 0.7.2",
+ "maybe-uninit",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-epoch 0.9.1",
+ "crossbeam-utils 0.8.1",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
+dependencies = [
+ "autocfg 1.0.1",
+ "cfg-if 0.1.10",
+ "crossbeam-utils 0.7.2",
+ "lazy_static",
+ "maybe-uninit",
+ "memoffset 0.5.6",
+ "scopeguard 1.1.0",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
+dependencies = [
+ "cfg-if 1.0.0",
+ "const_fn",
+ "crossbeam-utils 0.8.1",
+ "lazy_static",
+ "memoffset 0.6.1",
+ "scopeguard 1.1.0",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
+dependencies = [
+ "cfg-if 0.1.10",
+ "crossbeam-utils 0.7.2",
+ "maybe-uninit",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
+dependencies = [
+ "autocfg 1.0.1",
+ "cfg-if 0.1.10",
+ "lazy_static",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
+dependencies = [
+ "autocfg 1.0.1",
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "crypto-mac"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
+dependencies = [
+ "generic-array 0.12.3",
+ "subtle 1.0.0",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array 0.14.4",
+ "subtle 2.3.0",
+]
+
+[[package]]
+name = "ct-logs"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c8e13110a84b6315df212c045be706af261fd364791cad863285439ebba672e"
+dependencies = [
+ "sct",
+]
+
+[[package]]
+name = "cuckoofilter"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd43f7cfaffe0a386636a10baea2ee05cc50df3b77bea4a456c9572a939bf1f"
+dependencies = [
+ "byteorder 0.5.3",
+ "rand 0.3.23",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
+dependencies = [
+ "byteorder 1.3.4",
+ "digest 0.8.1",
+ "rand_core 0.5.1",
+ "subtle 2.3.0",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8492de420e9e60bc9a1d66e2dbb91825390b738a388606600663fc529b4b307"
+dependencies = [
+ "byteorder 1.3.4",
+ "digest 0.9.0",
+ "rand_core 0.5.1",
+ "subtle 2.3.0",
+ "zeroize",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "993a608597367c6377b258c25d7120740f00ed23a2252b729b1932dd7866f908"
+
+[[package]]
+name = "deflate"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
+dependencies = [
+ "adler32",
+ "byteorder 1.3.4",
+]
+
+[[package]]
+name = "deflate"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
+dependencies = [
+ "adler32",
+ "byteorder 1.3.4",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.3",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "directories"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c"
+dependencies = [
+ "cfg-if 0.1.10",
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
+dependencies = [
+ "cfg-if 0.1.10",
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "dns-parser"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea"
+dependencies = [
+ "byteorder 1.3.4",
+ "quick-error",
+]
+
+[[package]]
+name = "dwrote"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "winapi 0.3.9",
+ "wio",
+]
+
+[[package]]
+name = "dyn-clonable"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4"
+dependencies = [
+ "dyn-clonable-impl",
+ "dyn-clone",
+]
+
+[[package]]
+name = "dyn-clonable-impl"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d55796afa1b20c2945ca8eabfc421839f2b766619209f1ede813cf2484f31804"
+
+[[package]]
+name = "ed25519"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37c66a534cbb46ab4ea03477eae19d5c22c01da8258030280b7bd9d8433fb6ef"
+dependencies = [
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
+dependencies = [
+ "curve25519-dalek 3.0.0",
+ "ed25519",
+ "rand 0.7.3",
+ "serde",
+ "sha2 0.9.2",
+ "zeroize",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "enumflags2"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0"
+dependencies = [
+ "enumflags2_derive",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "environmental"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6576a1755ddffd988788025e75bce9e74b018f7cc226198fe931d077911c6d7e"
+
+[[package]]
+name = "erased-serde"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ca8b296792113e1500fd935ae487be6e00ce318952a6880555554824d6ebf38"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
+
+[[package]]
+name = "exit-future"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e43f2f1833d64e33f15592464d6fdd70f349dda7b1a53088eb83cd94014008c5"
+dependencies = [
+ "futures 0.3.8",
+]
+
+[[package]]
+name = "expat-sys"
+version = "2.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa"
+dependencies = [
+ "cmake",
+ "pkg-config",
+]
+
+[[package]]
+name = "failure"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
+dependencies = [
+ "backtrace",
+ "failure_derive",
+]
+
+[[package]]
+name = "failure_derive"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
+[[package]]
+name = "fastrand"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fdlimit"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4c9e43643f5a3be4ca5b67d26b98031ff9db6806c3440ae32e02e3ceac3f1b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "finality-grandpa"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8feb87a63249689640ac9c011742c33139204e3c134293d3054022276869133b"
+dependencies = [
+ "either",
+ "futures 0.3.8",
+ "futures-timer 2.0.2",
+ "log",
+ "num-traits",
+ "parity-scale-codec",
+ "parking_lot 0.9.0",
+]
+
+[[package]]
+name = "fixed-hash"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11498d382790b7a8f2fd211780bec78619bba81cdad3a283997c0c41f836759c"
+dependencies = [
+ "byteorder 1.3.4",
+ "rand 0.7.3",
+ "rustc-hex",
+ "static_assertions",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
+
+[[package]]
+name = "flate2"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crc32fast",
+ "libc",
+ "libz-sys",
+ "miniz_oxide 0.4.3",
+]
+
+[[package]]
+name = "float-ord"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "font-kit"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ab2cdc792b545c49acb23aafa1cdc9381ea96140741f10bac596fd1bd1aa4f"
+dependencies = [
+ "bitflags",
+ "byteorder 1.3.4",
+ "core-foundation",
+ "core-graphics",
+ "core-text",
+ "dirs",
+ "dwrote",
+ "float-ord",
+ "freetype",
+ "lazy_static",
+ "libc",
+ "log",
+ "pathfinder_geometry",
+ "pathfinder_simd",
+ "servo-fontconfig",
+ "walkdir",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "fork-tree"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
+dependencies = [
+ "matches",
+ "percent-encoding 2.1.0",
+]
+
+[[package]]
+name = "frame-benchmarking"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "linregress 0.1.7",
+ "parity-scale-codec",
+ "paste",
+ "sp-api",
+ "sp-io",
+ "sp-runtime",
+ "sp-runtime-interface",
+ "sp-std",
+ "sp-storage",
+]
+
+[[package]]
+name = "frame-executive"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+ "sp-tracing",
+]
+
+[[package]]
+name = "frame-metadata"
+version = "12.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-std",
+]
+
+[[package]]
+name = "frame-support"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "bitmask",
+ "frame-metadata",
+ "frame-support-procedural",
+ "impl-trait-for-tuples",
+ "log",
+ "once_cell 1.5.2",
+ "parity-scale-codec",
+ "paste",
+ "serde",
+ "smallvec 1.6.0",
+ "sp-arithmetic",
+ "sp-core",
+ "sp-inherents",
+ "sp-io",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-std",
+ "sp-tracing",
+]
+
+[[package]]
+name = "frame-support-procedural"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support-procedural-tools",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "frame-support-procedural-tools"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support-procedural-tools-derive",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "frame-support-procedural-tools-derive"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "frame-system"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "impl-trait-for-tuples",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+ "sp-version",
+]
+
+[[package]]
+name = "frame-system-rpc-runtime-api"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-api",
+]
+
+[[package]]
+name = "freetype"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11926b2b410b469d0e9399eca4cbbe237a9ef02176c485803b29216307e8e028"
+dependencies = [
+ "libc",
+ "servo-freetype-sys",
+]
+
+[[package]]
+name = "fs-swap"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5839fda247e24ca4919c87c71dd5ca658f1f39e4f06829f80e3f15c3bafcfc2c"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "libloading",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
+
+[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+dependencies = [
+ "bitflags",
+ "fuchsia-zircon-sys",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+
+[[package]]
+name = "futures"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed"
+
+[[package]]
+name = "futures"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-channel-preview"
+version = "0.3.0-alpha.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5e5f4df964fa9c1c2f8bddeb5c3611631cacd93baf810fc8bb2fb4b495c263a"
+dependencies = [
+ "futures-core-preview",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
+
+[[package]]
+name = "futures-core-preview"
+version = "0.3.0-alpha.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b35b6263fb1ef523c3056565fa67b1d16f0a8604ff12b11b08c25f28a734c60a"
+
+[[package]]
+name = "futures-cpupool"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
+dependencies = [
+ "futures 0.1.30",
+ "num_cpus",
+]
+
+[[package]]
+name = "futures-diagnose"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdcef58a173af8148b182684c9f2d5250875adbcaff7b5794073894f9d8634a9"
+dependencies = [
+ "futures 0.1.30",
+ "futures 0.3.8",
+ "lazy_static",
+ "log",
+ "parking_lot 0.9.0",
+ "pin-project 0.4.27",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "futures-executor"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+ "num_cpus",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
+
+[[package]]
+name = "futures-lite"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite 0.1.11",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
+
+[[package]]
+name = "futures-task"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
+dependencies = [
+ "once_cell 1.5.2",
+]
+
+[[package]]
+name = "futures-timer"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6"
+
+[[package]]
+name = "futures-timer"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
+
+[[package]]
+name = "futures-util"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
+dependencies = [
+ "futures 0.1.30",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project 1.0.2",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
+]
+
+[[package]]
+name = "futures-util-preview"
+version = "0.3.0-alpha.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ce968633c17e5f97936bd2797b6e38fb56cf16a7422319f7ec2e30d3c470e8d"
+dependencies = [
+ "futures-channel-preview",
+ "futures-core-preview",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "futures_codec"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce54d63f8b0c75023ed920d46fd71d0cbbb830b0ee012726b5b4f506fb6dea5b"
+dependencies = [
+ "bytes 0.5.6",
+ "futures 0.3.8",
+ "memchr",
+ "pin-project 0.4.27",
+]
+
+[[package]]
+name = "gcc"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
+
+[[package]]
+name = "generator"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cdc09201b2e8ca1b19290cf7e65de2246b8e91fb6874279722189c4de7b94dc"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustc_version",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "get_if_addrs"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abddb55a898d32925f3148bd281174a68eeb68bbfd9a5938a57b18f506ee4ef7"
+dependencies = [
+ "c_linked_list",
+ "get_if_addrs-sys",
+ "libc",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "get_if_addrs-sys"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d04f9fb746cf36b191c00f3ede8bde9c8e64f9f4b05ae2694a9ccf5e3f5ab48"
+dependencies = [
+ "gcc",
+ "libc",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "ghash"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6e27f0689a6e15944bdce7e45425efb87eaa8ab0c6e87f11d0987a9133e2531"
+dependencies = [
+ "polyval",
+]
+
+[[package]]
+name = "gif"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af"
+dependencies = [
+ "color_quant",
+ "lzw",
+]
+
+[[package]]
+name = "gimli"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "globset"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "fnv",
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "gloo-timers"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "h2"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
+dependencies = [
+ "byteorder 1.3.4",
+ "bytes 0.4.12",
+ "fnv",
+ "futures 0.1.30",
+ "http 0.1.21",
+ "indexmap",
+ "log",
+ "slab",
+ "string",
+ "tokio-io",
+]
+
+[[package]]
+name = "h2"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
+dependencies = [
+ "bytes 0.5.6",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 0.2.1",
+ "indexmap",
+ "slab",
+ "tokio 0.2.23",
+ "tokio-util",
+ "tracing",
+ "tracing-futures",
+]
+
+[[package]]
+name = "hash-db"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a"
+
+[[package]]
+name = "hash256-std-hasher"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da"
+dependencies = [
+ "byteorder 1.3.4",
+ "scopeguard 0.3.3",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e6073d0ca812575946eb5f35ff68dbe519907b25c42530389ff946dc84c6ead"
+dependencies = [
+ "ahash 0.2.19",
+ "autocfg 0.1.7",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25"
+dependencies = [
+ "ahash 0.3.8",
+ "autocfg 1.0.1",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+dependencies = [
+ "ahash 0.4.6",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
+
+[[package]]
+name = "hex-literal"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5af1f635ef1bc545d78392b136bfe1c9809e029023c84a3638a864a10b8819c8"
+
+[[package]]
+name = "hex_fmt"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
+
+[[package]]
+name = "hmac"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
+dependencies = [
+ "crypto-mac 0.7.0",
+ "digest 0.8.1",
+]
+
+[[package]]
+name = "hmac-drbg"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b"
+dependencies = [
+ "digest 0.8.1",
+ "generic-array 0.12.3",
+ "hmac",
+]
+
+[[package]]
+name = "http"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0"
+dependencies = [
+ "bytes 0.4.12",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
+dependencies = [
+ "bytes 0.5.6",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "http 0.1.21",
+ "tokio-buf",
+]
+
+[[package]]
+name = "http-body"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
+dependencies = [
+ "bytes 0.5.6",
+ "http 0.2.1",
+]
+
+[[package]]
+name = "httparse"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
+
+[[package]]
+name = "httpdate"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
+
+[[package]]
+name = "humantime"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
+dependencies = [
+ "quick-error",
+]
+
+[[package]]
+name = "hyper"
+version = "0.12.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dbe6ed1438e1f8ad955a4701e9a944938e9519f6888d12d8558b645e247d5f6"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "futures-cpupool",
+ "h2 0.1.26",
+ "http 0.1.21",
+ "http-body 0.1.0",
+ "httparse",
+ "iovec",
+ "itoa",
+ "log",
+ "net2",
+ "rustc_version",
+ "time",
+ "tokio 0.1.22",
+ "tokio-buf",
+ "tokio-executor 0.1.10",
+ "tokio-io",
+ "tokio-reactor",
+ "tokio-tcp",
+ "tokio-threadpool",
+ "tokio-timer",
+ "want 0.2.0",
+]
+
+[[package]]
+name = "hyper"
+version = "0.13.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ad767baac13b44d4529fcf58ba2cd0995e36e7b435bc5b039de6f47e880dbf"
+dependencies = [
+ "bytes 0.5.6",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2 0.2.7",
+ "http 0.2.1",
+ "http-body 0.3.1",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project 1.0.2",
+ "socket2",
+ "tokio 0.2.23",
+ "tower-service",
+ "tracing",
+ "want 0.3.0",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37743cc83e8ee85eacfce90f2f4102030d9ff0a95244098d781e9bee4a90abb6"
+dependencies = [
+ "bytes 0.5.6",
+ "ct-logs",
+ "futures-util",
+ "hyper 0.13.9",
+ "log",
+ "rustls",
+ "rustls-native-certs",
+ "tokio 0.2.23",
+ "tokio-rustls",
+ "webpki",
+]
+
+[[package]]
+name = "idna"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "image"
+version = "0.22.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08ed2ada878397b045454ac7cfb011d73132c59f31a955d230bd1f1c2e68eb4a"
+dependencies = [
+ "byteorder 1.3.4",
+ "jpeg-decoder",
+ "num-iter",
+ "num-rational 0.2.4",
+ "num-traits",
+ "png 0.15.3",
+]
+
+[[package]]
+name = "image"
+version = "0.23.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
+dependencies = [
+ "bytemuck",
+ "byteorder 1.3.4",
+ "color_quant",
+ "jpeg-decoder",
+ "num-iter",
+ "num-rational 0.3.2",
+ "num-traits",
+ "png 0.16.8",
+]
+
+[[package]]
+name = "impl-codec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53"
+dependencies = [
+ "parity-scale-codec",
+]
+
+[[package]]
+name = "impl-serde"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "impl-trait-for-tuples"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef5550a42e3740a0e71f909d4c861056a284060af885ae7aa6242820f920d9d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
+dependencies = [
+ "autocfg 1.0.1",
+ "hashbrown 0.9.1",
+]
+
+[[package]]
+name = "inflate"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
+dependencies = [
+ "adler32",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "integer-sqrt"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "intervalier"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64fa110ec7b8f493f416eed552740d10e7030ad5f63b2308f82c9608ec2df275"
+dependencies = [
+ "futures 0.3.8",
+ "futures-timer 2.0.2",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "ip_network"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee15951c035f79eddbef745611ec962f63f4558f1dadf98ab723cc603487c6f"
+
+[[package]]
+name = "ipnet"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
+
+[[package]]
+name = "itertools"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
+
+[[package]]
+name = "jobserver"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "jpeg-decoder"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
+
+[[package]]
+name = "js-sys"
+version = "0.3.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "jsonrpc-client-transports"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "489b9c612e60c766f751ab40fcb43cbb55a1e10bb44a9b4307ed510ca598cbd7"
+dependencies = [
+ "failure",
+ "futures 0.1.30",
+ "jsonrpc-core",
+ "jsonrpc-pubsub",
+ "log",
+ "serde",
+ "serde_json",
+ "url 1.7.2",
+]
+
+[[package]]
+name = "jsonrpc-core"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0745a6379e3edc893c84ec203589790774e4247420033e71a76d3ab4687991fa"
+dependencies = [
+ "futures 0.1.30",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "jsonrpc-core-client"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f764902d7b891344a0acb65625f32f6f7c6db006952143bd650209fbe7d94db"
+dependencies = [
+ "jsonrpc-client-transports",
+]
+
+[[package]]
+name = "jsonrpc-derive"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99a847f9ec7bb52149b2786a17c9cb260d6effc6b8eeb8c16b343a487a7563a3"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "jsonrpc-http-server"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb5c4513b7b542f42da107942b7b759f27120b5cc894729f88254b28dff44b7"
+dependencies = [
+ "hyper 0.12.35",
+ "jsonrpc-core",
+ "jsonrpc-server-utils",
+ "log",
+ "net2",
+ "parking_lot 0.10.2",
+ "unicase",
+]
+
+[[package]]
+name = "jsonrpc-ipc-server"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf50e53e4eea8f421a7316c5f63e395f7bc7c4e786a6dc54d76fab6ff7aa7ce7"
+dependencies = [
+ "jsonrpc-core",
+ "jsonrpc-server-utils",
+ "log",
+ "parity-tokio-ipc",
+ "parking_lot 0.10.2",
+ "tokio-service",
+]
+
+[[package]]
+name = "jsonrpc-pubsub"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "639558e0604013be9787ae52f798506ae42bf4220fe587bdc5625871cc8b9c77"
+dependencies = [
+ "jsonrpc-core",
+ "log",
+ "parking_lot 0.10.2",
+ "rand 0.7.3",
+ "serde",
+]
+
+[[package]]
+name = "jsonrpc-server-utils"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f1f3990650c033bd8f6bd46deac76d990f9bbfb5f8dc8c4767bf0a00392176"
+dependencies = [
+ "bytes 0.4.12",
+ "globset",
+ "jsonrpc-core",
+ "lazy_static",
+ "log",
+ "tokio 0.1.22",
+ "tokio-codec",
+ "unicase",
+]
+
+[[package]]
+name = "jsonrpc-ws-server"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6596fe75209b73a2a75ebe1dce4e60e03b88a2b25e8807b667597f6315150d22"
+dependencies = [
+ "jsonrpc-core",
+ "jsonrpc-server-utils",
+ "log",
+ "parity-ws",
+ "parking_lot 0.10.2",
+ "slab",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "kv-log-macro"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "kvdb"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0315ef2f688e33844400b31f11c263f2b3dc21d8b9355c6891c5f185fae43f9a"
+dependencies = [
+ "parity-util-mem",
+ "smallvec 1.6.0",
+]
+
+[[package]]
+name = "kvdb-memorydb"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73de822b260a3bdfb889dbbb65bb2d473eee2253973d6fa4a5d149a2a4a7c66e"
+dependencies = [
+ "kvdb",
+ "parity-util-mem",
+ "parking_lot 0.10.2",
+]
+
+[[package]]
+name = "kvdb-rocksdb"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44947dd392f09475af614d740fe0320b66d01cb5b977f664bbbb5e45a70ea4c1"
+dependencies = [
+ "fs-swap",
+ "kvdb",
+ "log",
+ "num_cpus",
+ "owning_ref",
+ "parity-util-mem",
+ "parking_lot 0.10.2",
+ "regex",
+ "rocksdb",
+ "smallvec 1.6.0",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
+
+[[package]]
+name = "libloading"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753"
+dependencies = [
+ "cc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
+
+[[package]]
+name = "libp2p"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "571f5a4604c1a40d75651da141dfde29ad15329f537a779528803297d2220274"
+dependencies = [
+ "atomic",
+ "bytes 0.5.6",
+ "futures 0.3.8",
+ "lazy_static",
+ "libp2p-core",
+ "libp2p-core-derive",
+ "libp2p-deflate",
+ "libp2p-dns",
+ "libp2p-floodsub",
+ "libp2p-gossipsub",
+ "libp2p-identify",
+ "libp2p-kad",
+ "libp2p-mdns",
+ "libp2p-mplex",
+ "libp2p-noise",
+ "libp2p-ping",
+ "libp2p-plaintext",
+ "libp2p-pnet",
+ "libp2p-request-response",
+ "libp2p-swarm",
+ "libp2p-tcp",
+ "libp2p-uds",
+ "libp2p-wasm-ext",
+ "libp2p-websocket",
+ "libp2p-yamux",
+ "multihash",
+ "parity-multiaddr",
+ "parking_lot 0.10.2",
+ "pin-project 0.4.27",
+ "smallvec 1.6.0",
+ "wasm-timer",
+]
+
+[[package]]
+name = "libp2p-core"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52f13ba8c7df0768af2eb391696d562c7de88cc3a35122531aaa6a7d77754d25"
+dependencies = [
+ "asn1_der",
+ "bs58 0.3.1",
+ "ed25519-dalek",
+ "either",
+ "fnv",
+ "futures 0.3.8",
+ "futures-timer 3.0.2",
+ "lazy_static",
+ "libsecp256k1",
+ "log",
+ "multihash",
+ "multistream-select",
+ "parity-multiaddr",
+ "parking_lot 0.10.2",
+ "pin-project 0.4.27",
+ "prost",
+ "prost-build",
+ "rand 0.7.3",
+ "ring",
+ "rw-stream-sink",
+ "sha2 0.8.2",
+ "smallvec 1.6.0",
+ "thiserror",
+ "unsigned-varint 0.4.0",
+ "void",
+ "zeroize",
+]
+
+[[package]]
+name = "libp2p-core-derive"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f753d9324cd3ec14bf04b8a8cd0d269c87f294153d6bf2a84497a63a5ad22213"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "libp2p-deflate"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74029ae187f35f4b8ddf26b9779a68b340045d708528a103917cdca49a296db5"
+dependencies = [
+ "flate2",
+ "futures 0.3.8",
+ "libp2p-core",
+]
+
+[[package]]
+name = "libp2p-dns"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cf319822e08dd65c8e060d2354e9f952895bbc433f5706c75ed010c152aee5e"
+dependencies = [
+ "futures 0.3.8",
+ "libp2p-core",
+ "log",
+]
+
+[[package]]
+name = "libp2p-floodsub"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a9acb43a3e4a4e413e0c4abe0fa49308df7c6335c88534757b647199cb8a51"
+dependencies = [
+ "cuckoofilter",
+ "fnv",
+ "futures 0.3.8",
+ "libp2p-core",
+ "libp2p-swarm",
+ "prost",
+ "prost-build",
+ "rand 0.7.3",
+ "smallvec 1.6.0",
+]
+
+[[package]]
+name = "libp2p-gossipsub"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab20fcb60edebe3173bbb708c6ac3444afdf1e3152dc2866b10c4f5497f17467"
+dependencies = [
+ "base64 0.11.0",
+ "byteorder 1.3.4",
+ "bytes 0.5.6",
+ "fnv",
+ "futures 0.3.8",
+ "futures_codec",
+ "hex_fmt",
+ "libp2p-core",
+ "libp2p-swarm",
+ "log",
+ "lru_time_cache",
+ "prost",
+ "prost-build",
+ "rand 0.7.3",
+ "sha2 0.8.2",
+ "smallvec 1.6.0",
+ "unsigned-varint 0.4.0",
+ "wasm-timer",
+]
+
+[[package]]
+name = "libp2p-identify"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56396ee63aa9164eacf40c2c5d2bda8c4133c2f57e1b0425d51d3a4e362583b1"
+dependencies = [
+ "futures 0.3.8",
+ "libp2p-core",
+ "libp2p-swarm",
+ "log",
+ "prost",
+ "prost-build",
+ "smallvec 1.6.0",
+ "wasm-timer",
+]
+
+[[package]]
+name = "libp2p-kad"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7fa9047f8b8f544278a35c2d9d45d3b2c1785f2d86d4e1629d6edf97be3955"
+dependencies = [
+ "arrayvec 0.5.2",
+ "bytes 0.5.6",
+ "either",
+ "fnv",
+ "futures 0.3.8",
+ "futures_codec",
+ "libp2p-core",
+ "libp2p-swarm",
+ "log",
+ "multihash",
+ "prost",
+ "prost-build",
+ "rand 0.7.3",
+ "sha2 0.8.2",
+ "smallvec 1.6.0",
+ "uint",
+ "unsigned-varint 0.4.0",
+ "void",
+ "wasm-timer",
+]
+
+[[package]]
+name = "libp2p-mdns"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3173b5a6b2f690c29ae07798d85b9441a131ac76ddae9015ef22905b623d0c69"
+dependencies = [
+ "async-std",
+ "data-encoding",
+ "dns-parser",
+ "either",
+ "futures 0.3.8",
+ "lazy_static",
+ "libp2p-core",
+ "libp2p-swarm",
+ "log",
+ "net2",
+ "rand 0.7.3",
+ "smallvec 1.6.0",
+ "void",
+ "wasm-timer",
+]
+
+[[package]]
+name = "libp2p-mplex"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a73a799cc8410b36e40b8f4c4b6babbcb9efd3727111bf517876e4acfa612d3"
+dependencies = [
+ "bytes 0.5.6",
+ "fnv",
+ "futures 0.3.8",
+ "futures_codec",
+ "libp2p-core",
+ "log",
+ "parking_lot 0.10.2",
+ "unsigned-varint 0.4.0",
+]
+
+[[package]]
+name = "libp2p-noise"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ef6c490042f549fb1025f2892dfe6083d97a77558f450c1feebe748ca9eb15a"
+dependencies = [
+ "bytes 0.5.6",
+ "curve25519-dalek 2.1.0",
+ "futures 0.3.8",
+ "lazy_static",
+ "libp2p-core",
+ "log",
+ "prost",
+ "prost-build",
+ "rand 0.7.3",
+ "sha2 0.8.2",
+ "snow",
+ "static_assertions",
+ "x25519-dalek 0.6.0",
+ "zeroize",
+]
+
+[[package]]
+name = "libp2p-ping"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad063c21dfcea4518ac9e8bd4119d33a5b26c41e674f602f41f05617a368a5c8"
+dependencies = [
+ "futures 0.3.8",
+ "libp2p-core",
+ "libp2p-swarm",
+ "log",
+ "rand 0.7.3",
+ "void",
+ "wasm-timer",
+]
+
+[[package]]
+name = "libp2p-plaintext"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "903a12e99c72dbebefea258de887982adeacc7025baa1ceb10b7fa9928f54791"
+dependencies = [
+ "bytes 0.5.6",
+ "futures 0.3.8",
+ "futures_codec",
+ "libp2p-core",
+ "log",
+ "prost",
+ "prost-build",
+ "rw-stream-sink",
+ "unsigned-varint 0.4.0",
+ "void",
+]
+
+[[package]]
+name = "libp2p-pnet"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96b3c2d5d26a9500e959a0e19743897239a6c4be78dadf99b70414301a70c006"
+dependencies = [
+ "futures 0.3.8",
+ "log",
+ "pin-project 0.4.27",
+ "rand 0.7.3",
+ "salsa20",
+ "sha3",
+]
+
+[[package]]
+name = "libp2p-request-response"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c0c9e8a4cd69d97e9646c54313d007512f411aba8c5226cfcda16df6a6e84a3"
+dependencies = [
+ "async-trait",
+ "bytes 0.5.6",
+ "futures 0.3.8",
+ "libp2p-core",
+ "libp2p-swarm",
+ "log",
+ "lru 0.6.1",
+ "minicbor",
+ "rand 0.7.3",
+ "smallvec 1.6.0",
+ "unsigned-varint 0.5.1",
+ "wasm-timer",
+]
+
+[[package]]
+name = "libp2p-swarm"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7193e444210132237b81b755ec7fe53f1c4bd2f53cf719729b94c0c72eb6eaa1"
+dependencies = [
+ "either",
+ "futures 0.3.8",
+ "libp2p-core",
+ "log",
+ "rand 0.7.3",
+ "smallvec 1.6.0",
+ "void",
+ "wasm-timer",
+]
+
+[[package]]
+name = "libp2p-tcp"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44f42ec130d7a37a7e47bf4398026b7ad9185c08ed26972e2720f8b94112796f"
+dependencies = [
+ "async-std",
+ "futures 0.3.8",
+ "futures-timer 3.0.2",
+ "get_if_addrs",
+ "ipnet",
+ "libp2p-core",
+ "log",
+ "socket2",
+]
+
+[[package]]
+name = "libp2p-uds"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dea7acb0a034f70d7db94c300eba3f65c0f6298820105624088a9609c9974d77"
+dependencies = [
+ "async-std",
+ "futures 0.3.8",
+ "libp2p-core",
+ "log",
+]
+
+[[package]]
+name = "libp2p-wasm-ext"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34c1faac6f92c21fbe155417957863ea822fba9e9fd5eb24c0912336a100e63f"
+dependencies = [
+ "futures 0.3.8",
+ "js-sys",
+ "libp2p-core",
+ "parity-send-wrapper",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "libp2p-websocket"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d650534ebd99f48f6fa292ed5db10d30df2444943afde4407ceeddab8e513fca"
+dependencies = [
+ "async-tls",
+ "either",
+ "futures 0.3.8",
+ "libp2p-core",
+ "log",
+ "quicksink",
+ "rustls",
+ "rw-stream-sink",
+ "soketto",
+ "url 2.2.0",
+ "webpki",
+ "webpki-roots 0.18.0",
+]
+
+[[package]]
+name = "libp2p-yamux"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "781d9b9f043dcdabc40640807125368596b849fd4d96cdca2dcf052fdf6f33fd"
+dependencies = [
+ "futures 0.3.8",
+ "libp2p-core",
+ "parking_lot 0.11.1",
+ "thiserror",
+ "yamux",
+]
+
+[[package]]
+name = "librocksdb-sys"
+version = "6.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb5b56f651c204634b936be2f92dbb42c36867e00ff7fe2405591f3b9fa66f09"
+dependencies = [
+ "bindgen",
+ "cc",
+ "glob",
+ "libc",
+]
+
+[[package]]
+name = "libsecp256k1"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962"
+dependencies = [
+ "arrayref",
+ "crunchy",
+ "digest 0.8.1",
+ "hmac-drbg",
+ "rand 0.7.3",
+ "sha2 0.8.2",
+ "subtle 2.3.0",
+ "typenum",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655"
+dependencies = [
+ "cc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
+
+[[package]]
+name = "linked_hash_set"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "linregress"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9290cf6f928576eeb9c096c6fad9d8d452a0a1a70a2bbffa6e36064eedc0aac9"
+dependencies = [
+ "failure",
+ "nalgebra 0.18.1",
+ "statrs 0.10.0",
+]
+
+[[package]]
+name = "linregress"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d0ad4b5cc8385a881c561fac3501353d63d2a2b7a357b5064d71815c9a92724"
+dependencies = [
+ "nalgebra 0.21.1",
+ "statrs 0.12.0",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
+dependencies = [
+ "scopeguard 0.3.3",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
+dependencies = [
+ "scopeguard 1.1.0",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
+dependencies = [
+ "scopeguard 1.1.0",
+]
+
+[[package]]
+name = "log"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
+dependencies = [
+ "cfg-if 0.1.10",
+]
+
+[[package]]
+name = "loom"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0e8460f2f2121162705187214720353c517b97bdfb3494c0b1e33d83ebe4bed"
+dependencies = [
+ "cfg-if 0.1.10",
+ "generator",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "lru"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0609345ddee5badacf857d4f547e0e5a2e987db77085c24cd887f73573a04237"
+dependencies = [
+ "hashbrown 0.6.3",
+]
+
+[[package]]
+name = "lru"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be716eb6878ca2263eb5d00a781aa13264a794f519fe6af4fbb2668b2d5441c0"
+dependencies = [
+ "hashbrown 0.9.1",
+]
+
+[[package]]
+name = "lru_time_cache"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb241df5c4caeb888755363fc95f8a896618dc0d435e9e775f7930cb099beab"
+
+[[package]]
+name = "lzw"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
+
+[[package]]
+name = "matchers"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
+
+[[package]]
+name = "matrixmultiply"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4f7ec66360130972f34830bfad9ef05c6610a43938a467bcc9ab9369ab3478f"
+dependencies = [
+ "rawpointer",
+]
+
+[[package]]
+name = "maybe-uninit"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "memmap"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
+dependencies = [
+ "autocfg 1.0.1",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
+dependencies = [
+ "autocfg 1.0.1",
+]
+
+[[package]]
+name = "memory-db"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f36ddb0b2cdc25d38babba472108798e3477f02be5165f038c5e393e50c57a"
+dependencies = [
+ "hash-db",
+ "hashbrown 0.8.2",
+ "parity-util-mem",
+]
+
+[[package]]
+name = "memory_units"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882"
+
+[[package]]
+name = "merlin"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6feca46f4fa3443a01769d768727f10c10a20fdb65e52dc16a81f0c8269bb78"
+dependencies = [
+ "byteorder 1.3.4",
+ "keccak",
+ "rand_core 0.5.1",
+ "zeroize",
+]
+
+[[package]]
+name = "minicbor"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fc03ad6f8f548db7194a5ff5a6f96342ecae4e3ef67d2bf18bacc0e245cd041"
+dependencies = [
+ "minicbor-derive",
+]
+
+[[package]]
+name = "minicbor-derive"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c214bf3d90099b52f3e4b328ae0fe34837fd0fab683ad1e10fceb4629106df48"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
+dependencies = [
+ "adler32",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
+dependencies = [
+ "adler",
+ "autocfg 1.0.1",
+]
+
+[[package]]
+name = "mio"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
+dependencies = [
+ "cfg-if 0.1.10",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log",
+ "miow 0.2.2",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "mio-extras"
+version = "2.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
+dependencies = [
+ "lazycell",
+ "log",
+ "mio",
+ "slab",
+]
+
+[[package]]
+name = "mio-named-pipes"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
+dependencies = [
+ "log",
+ "mio",
+ "miow 0.3.6",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "mio-uds"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
+dependencies = [
+ "iovec",
+ "libc",
+ "mio",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
+dependencies = [
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
+dependencies = [
+ "socket2",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "multihash"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567122ab6492f49b59def14ecc36e13e64dca4188196dd0cd41f9f3f979f3df6"
+dependencies = [
+ "blake2b_simd",
+ "blake2s_simd",
+ "digest 0.9.0",
+ "sha-1 0.9.2",
+ "sha2 0.9.2",
+ "sha3",
+ "unsigned-varint 0.5.1",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333"
+
+[[package]]
+name = "multistream-select"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93faf2e41f9ee62fb01680ed48f3cc26652352327aa2e59869070358f6b7dd75"
+dependencies = [
+ "bytes 0.5.6",
+ "futures 0.3.8",
+ "log",
+ "pin-project 1.0.2",
+ "smallvec 1.6.0",
+ "unsigned-varint 0.5.1",
+]
+
+[[package]]
+name = "nalgebra"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aaa9fddbc34c8c35dd2108515587b8ce0cab396f17977b8c738568e4edb521a2"
+dependencies = [
+ "alga",
+ "approx",
+ "generic-array 0.12.3",
+ "matrixmultiply",
+ "num-complex",
+ "num-rational 0.2.4",
+ "num-traits",
+ "rand 0.6.5",
+ "typenum",
+]
+
+[[package]]
+name = "nalgebra"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6b6147c3d50b4f3cdabfe2ecc94a0191fd3d6ad58aefd9664cf396285883486"
+dependencies = [
+ "approx",
+ "generic-array 0.13.3",
+ "matrixmultiply",
+ "num-complex",
+ "num-rational 0.2.4",
+ "num-traits",
+ "rand 0.7.3",
+ "rand_distr",
+ "simba",
+ "typenum",
+]
+
+[[package]]
+name = "names"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef320dab323286b50fb5cdda23f61c796a72a89998ab565ca32525c5c556f2da"
+dependencies = [
+ "rand 0.3.23",
+]
+
+[[package]]
+name = "nb-connect"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "nix"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if 0.1.10",
+ "libc",
+ "void",
+]
+
+[[package]]
+name = "node-bench"
+version = "0.8.0"
+dependencies = [
+ "derive_more",
+ "fs_extra",
+ "futures 0.3.8",
+ "hash-db",
+ "hex",
+ "kvdb",
+ "kvdb-rocksdb",
+ "lazy_static",
+ "linregress 0.4.0",
+ "log",
+ "node-primitives",
+ "node-runtime",
+ "parity-db",
+ "parity-util-mem",
+ "plotters",
+ "rand 0.7.3",
+ "sc-basic-authorship",
+ "sc-cli",
+ "sc-client-api",
+ "sc-transaction-pool",
+ "serde",
+ "serde_json",
+ "sp-consensus",
+ "sp-core",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-timestamp",
+ "sp-tracing",
+ "sp-transaction-pool",
+ "sp-trie",
+ "structopt",
+ "tempfile",
+]
+
+[[package]]
+name = "node-primitives"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-system",
+ "parity-scale-codec",
+ "sp-application-crypto",
+ "sp-core",
+ "sp-runtime",
+]
+
+[[package]]
+name = "node-runtime"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-benchmarking",
+ "frame-executive",
+ "frame-support",
+ "frame-system",
+ "frame-system-rpc-runtime-api",
+ "integer-sqrt",
+ "node-primitives",
+ "pallet-authority-discovery",
+ "pallet-authorship",
+ "pallet-babe",
+ "pallet-balances",
+ "pallet-collective",
+ "pallet-contracts",
+ "pallet-contracts-primitives",
+ "pallet-contracts-rpc-runtime-api",
+ "pallet-democracy",
+ "pallet-elections-phragmen",
+ "pallet-finality-tracker",
+ "pallet-grandpa",
+ "pallet-identity",
+ "pallet-im-online",
+ "pallet-indices",
+ "pallet-membership",
+ "pallet-multisig",
+ "pallet-offences",
+ "pallet-proxy",
+ "pallet-randomness-collective-flip",
+ "pallet-recovery",
+ "pallet-scheduler",
+ "pallet-session",
+ "pallet-society",
+ "pallet-staking",
+ "pallet-staking-reward-curve",
+ "pallet-sudo",
+ "pallet-timestamp",
+ "pallet-transaction-payment",
+ "pallet-transaction-payment-rpc-runtime-api",
+ "pallet-treasury",
+ "pallet-utility",
+ "pallet-vesting",
+ "parity-scale-codec",
+ "serde",
+ "sp-api",
+ "sp-authority-discovery",
+ "sp-block-builder",
+ "sp-consensus-babe",
+ "sp-core",
+ "sp-inherents",
+ "sp-keyring",
+ "sp-offchain",
+ "sp-runtime",
+ "sp-session",
+ "sp-staking",
+ "sp-std",
+ "sp-transaction-pool",
+ "sp-version",
+ "static_assertions",
+ "substrate-wasm-builder-runner",
+]
+
+[[package]]
+name = "nodrop"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
+
+[[package]]
+name = "nohash-hasher"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+
+[[package]]
+name = "nom"
+version = "5.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
+dependencies = [
+ "memchr",
+ "version_check",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg 1.0.1",
+ "libm",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
+
+[[package]]
+name = "once_cell"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "532c29a261168a45ce28948f9537ddd7a5dd272cc513b3017b1e82a88f962c37"
+dependencies = [
+ "parking_lot 0.7.1",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
+dependencies = [
+ "parking_lot 0.11.1",
+]
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
+
+[[package]]
+name = "ordered-float"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "owning_ref"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "pallet-authority-discovery"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-session",
+ "parity-scale-codec",
+ "serde",
+ "sp-application-crypto",
+ "sp-authority-discovery",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-authorship"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "impl-trait-for-tuples",
+ "parity-scale-codec",
+ "sp-authorship",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-babe"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "pallet-authorship",
+ "pallet-session",
+ "pallet-timestamp",
+ "parity-scale-codec",
+ "serde",
+ "sp-application-crypto",
+ "sp-consensus-babe",
+ "sp-consensus-vrf",
+ "sp-inherents",
+ "sp-io",
+ "sp-runtime",
+ "sp-session",
+ "sp-staking",
+ "sp-std",
+ "sp-timestamp",
+]
+
+[[package]]
+name = "pallet-balances"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-collective"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-contracts"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "bitflags",
+ "frame-support",
+ "frame-system",
+ "pallet-contracts-primitives",
+ "parity-scale-codec",
+ "parity-wasm",
+ "pwasm-utils",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-sandbox",
+ "sp-std",
+ "wasmi-validation",
+]
+
+[[package]]
+name = "pallet-contracts-primitives"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-contracts-rpc-runtime-api"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "pallet-contracts-primitives",
+ "parity-scale-codec",
+ "sp-api",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-democracy"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-elections-phragmen"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-npos-elections",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-finality-tracker"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "impl-trait-for-tuples",
+ "parity-scale-codec",
+ "serde",
+ "sp-finality-tracker",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-grandpa"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "pallet-authorship",
+ "pallet-finality-tracker",
+ "pallet-session",
+ "parity-scale-codec",
+ "serde",
+ "sp-application-crypto",
+ "sp-core",
+ "sp-finality-grandpa",
+ "sp-runtime",
+ "sp-session",
+ "sp-staking",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-identity"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "enumflags2",
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-im-online"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-authorship",
+ "pallet-session",
+ "parity-scale-codec",
+ "serde",
+ "sp-application-crypto",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-staking",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-indices"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-keyring",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-membership"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-multisig"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-offences"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "parity-scale-codec",
+ "serde",
+ "sp-runtime",
+ "sp-staking",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-proxy"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-randomness-collective-flip"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "safe-mix",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-recovery"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "enumflags2",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-scheduler"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-session"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "impl-trait-for-tuples",
+ "pallet-timestamp",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-session",
+ "sp-staking",
+ "sp-std",
+ "sp-trie",
+]
+
+[[package]]
+name = "pallet-society"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "rand_chacha 0.2.2",
+ "serde",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-staking"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-authorship",
+ "pallet-session",
+ "parity-scale-codec",
+ "serde",
+ "sp-application-crypto",
+ "sp-io",
+ "sp-npos-elections",
+ "sp-runtime",
+ "sp-staking",
+ "sp-std",
+ "static_assertions",
+]
+
+[[package]]
+name = "pallet-staking-reward-curve"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pallet-sudo"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-timestamp"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "impl-trait-for-tuples",
+ "parity-scale-codec",
+ "serde",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-std",
+ "sp-timestamp",
+]
+
+[[package]]
+name = "pallet-transaction-payment"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-transaction-payment-rpc-runtime-api",
+ "parity-scale-codec",
+ "serde",
+ "smallvec 1.6.0",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-transaction-payment-rpc-runtime-api"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "parity-scale-codec",
+ "serde",
+ "sp-api",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-treasury"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "parity-scale-codec",
+ "serde",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-utility"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-vesting"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "enumflags2",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "parity-db"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00d595e372d119261593297debbe4193811a4dc811d2a1ccbb8caaa6666ad7ab"
+dependencies = [
+ "blake2-rfc",
+ "crc32fast",
+ "libc",
+ "log",
+ "memmap",
+ "parking_lot 0.10.2",
+]
+
+[[package]]
+name = "parity-multiaddr"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43244a26dc1ddd3097216bb12eaa6cf8a07b060c72718d9ebd60fd297d6401df"
+dependencies = [
+ "arrayref",
+ "bs58 0.4.0",
+ "byteorder 1.3.4",
+ "data-encoding",
+ "multihash",
+ "percent-encoding 2.1.0",
+ "serde",
+ "static_assertions",
+ "unsigned-varint 0.5.1",
+ "url 2.2.0",
+]
+
+[[package]]
+name = "parity-scale-codec"
+version = "1.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c740e5fbcb6847058b40ac7e5574766c6388f585e184d769910fe0d3a2ca861"
+dependencies = [
+ "arrayvec 0.5.2",
+ "bitvec",
+ "byte-slice-cast",
+ "parity-scale-codec-derive",
+ "serde",
+]
+
+[[package]]
+name = "parity-scale-codec-derive"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "198db82bb1c18fc00176004462dd809b2a6d851669550aa17af6dacd21ae0c14"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "parity-send-wrapper"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f"
+
+[[package]]
+name = "parity-tokio-ipc"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e57fea504fea33f9fbb5f49f378359030e7e026a6ab849bb9e8f0787376f1bf"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "libc",
+ "log",
+ "mio-named-pipes",
+ "miow 0.3.6",
+ "rand 0.7.3",
+ "tokio 0.1.22",
+ "tokio-named-pipes",
+ "tokio-uds",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "parity-util-mem"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "297ff91fa36aec49ce183484b102f6b75b46776822bd81525bfc4cc9b0dd0f5c"
+dependencies = [
+ "cfg-if 0.1.10",
+ "hashbrown 0.8.2",
+ "impl-trait-for-tuples",
+ "parity-util-mem-derive",
+ "parking_lot 0.10.2",
+ "primitive-types",
+ "smallvec 1.6.0",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "parity-util-mem-derive"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2"
+dependencies = [
+ "proc-macro2",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "parity-wasm"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865"
+
+[[package]]
+name = "parity-ws"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e02a625dd75084c2a7024f07c575b61b782f729d18702dabb3cdbf31911dc61"
+dependencies = [
+ "byteorder 1.3.4",
+ "bytes 0.4.12",
+ "httparse",
+ "log",
+ "mio",
+ "mio-extras",
+ "rand 0.7.3",
+ "sha-1 0.8.2",
+ "slab",
+ "url 2.2.0",
+]
+
+[[package]]
+name = "parking"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
+
+[[package]]
+name = "parking_lot"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
+dependencies = [
+ "lock_api 0.1.5",
+ "parking_lot_core 0.4.0",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
+dependencies = [
+ "lock_api 0.3.4",
+ "parking_lot_core 0.6.2",
+ "rustc_version",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
+dependencies = [
+ "lock_api 0.3.4",
+ "parking_lot_core 0.7.2",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
+dependencies = [
+ "instant",
+ "lock_api 0.4.2",
+ "parking_lot_core 0.8.0",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
+dependencies = [
+ "libc",
+ "rand 0.6.5",
+ "rustc_version",
+ "smallvec 0.6.13",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
+dependencies = [
+ "cfg-if 0.1.10",
+ "cloudabi 0.0.3",
+ "libc",
+ "redox_syscall",
+ "rustc_version",
+ "smallvec 0.6.13",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
+dependencies = [
+ "cfg-if 0.1.10",
+ "cloudabi 0.0.3",
+ "libc",
+ "redox_syscall",
+ "smallvec 1.6.0",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
+dependencies = [
+ "cfg-if 0.1.10",
+ "cloudabi 0.1.0",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec 1.6.0",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "paste"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
+dependencies = [
+ "paste-impl",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "paste-impl"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
+dependencies = [
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "pathfinder_geometry"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
+dependencies = [
+ "log",
+ "pathfinder_simd",
+]
+
+[[package]]
+name = "pathfinder_simd"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b451513912d6b3440e443aa75a73ab22203afedc4a90df8526d008c0f86f7cb3"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9"
+dependencies = [
+ "byteorder 1.3.4",
+ "crypto-mac 0.7.0",
+ "rayon",
+]
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
+[[package]]
+name = "percent-encoding"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "petgraph"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "pin-project"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15"
+dependencies = [
+ "pin-project-internal 0.4.27",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7"
+dependencies = [
+ "pin-project-internal 1.0.2",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+
+[[package]]
+name = "plotters"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a"
+dependencies = [
+ "chrono",
+ "font-kit",
+ "image 0.23.14",
+ "lazy_static",
+ "num-traits",
+ "plotters-backend",
+ "plotters-bitmap",
+ "plotters-svg",
+ "rusttype",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590"
+
+[[package]]
+name = "plotters-bitmap"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59b0bdaf5194ad865c6225f5e82f5fdde700ce999d227471809551d6e0b1c557"
+dependencies = [
+ "gif",
+ "image 0.22.5",
+ "plotters-backend",
+]
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "png"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef859a23054bbfee7811284275ae522f0434a3c8e7f4b74bd4a35ae7e1c4a283"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "deflate 0.7.20",
+ "inflate",
+]
+
+[[package]]
+name = "png"
+version = "0.16.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "deflate 0.8.6",
+ "miniz_oxide 0.3.7",
+]
+
+[[package]]
+name = "polling"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "log",
+ "wepoll-sys",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "poly1305"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22ce46de8e53ee414ca4d02bfefac75d8c12fba948b76622a40b4be34dfce980"
+dependencies = [
+ "universal-hash",
+]
+
+[[package]]
+name = "polyval"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3fd900a291ceb8b99799cc8cd3d1d3403a51721e015bc533528b2ceafcc443c"
+dependencies = [
+ "cfg-if 1.0.0",
+ "universal-hash",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "primitive-types"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd39dcacf71411ba488570da7bbc89b717225e46478b30ba99b92db6b149809"
+dependencies = [
+ "fixed-hash",
+ "impl-codec",
+ "impl-serde",
+ "uint",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
+dependencies = [
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "prometheus"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d70cf4412832bcac9cffe27906f4a66e450d323525e977168c70d1b36120ae"
+dependencies = [
+ "cfg-if 0.1.10",
+ "fnv",
+ "lazy_static",
+ "parking_lot 0.11.1",
+ "regex",
+ "thiserror",
+]
+
+[[package]]
+name = "prost"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce49aefe0a6144a45de32927c77bd2859a5f7677b55f220ae5b744e87389c212"
+dependencies = [
+ "bytes 0.5.6",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b10678c913ecbd69350e8535c3aef91a8676c0773fc1d7b95cdd196d7f2f26"
+dependencies = [
+ "bytes 0.5.6",
+ "heck",
+ "itertools",
+ "log",
+ "multimap",
+ "petgraph",
+ "prost",
+ "prost-types",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537aa19b95acde10a12fec4301466386f757403de4cd4e5b4fa78fb5ecb18f72"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1834f67c0697c001304b75be76f67add9c89742eda3a085ad8ee0bb38c3417aa"
+dependencies = [
+ "bytes 0.5.6",
+ "prost",
+]
+
+[[package]]
+name = "pwasm-utils"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f53bc2558e8376358ebdc28301546471d67336584f6438ed4b7c7457a055fd7"
+dependencies = [
+ "byteorder 1.3.4",
+ "log",
+ "parity-wasm",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quicksink"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite 0.1.11",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "radium"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac"
+
+[[package]]
+name = "rand"
+version = "0.3.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
+dependencies = [
+ "libc",
+ "rand 0.4.6",
+]
+
+[[package]]
+name = "rand"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
+dependencies = [
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.3.1",
+ "rdrand",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rand"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
+dependencies = [
+ "cloudabi 0.0.3",
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.3.1",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rand"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
+dependencies = [
+ "autocfg 0.1.7",
+ "libc",
+ "rand_chacha 0.1.1",
+ "rand_core 0.4.2",
+ "rand_hc 0.1.0",
+ "rand_isaac",
+ "rand_jitter",
+ "rand_os",
+ "rand_pcg 0.1.2",
+ "rand_xorshift",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.15",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc 0.2.0",
+ "rand_pcg 0.2.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
+dependencies = [
+ "autocfg 0.1.7",
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+dependencies = [
+ "rand_core 0.4.2",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.15",
+]
+
+[[package]]
+name = "rand_distr"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2"
+dependencies = [
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_isaac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "rand_jitter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
+dependencies = [
+ "libc",
+ "rand_core 0.4.2",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rand_os"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
+dependencies = [
+ "cloudabi 0.0.3",
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.4.2",
+ "rdrand",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+dependencies = [
+ "autocfg 0.1.7",
+ "rand_core 0.4.2",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "rawpointer"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
+
+[[package]]
+name = "rayon"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
+dependencies = [
+ "autocfg 1.0.1",
+ "crossbeam-deque 0.8.0",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque 0.8.0",
+ "crossbeam-utils 0.8.1",
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "redox_users"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
+dependencies = [
+ "getrandom 0.1.15",
+ "redox_syscall",
+ "rust-argon2",
+]
+
+[[package]]
+name = "ref-cast"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17626b2f4bcf35b84bf379072a66e28cfe5c3c6ae58b38e4914bb8891dabece"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c523ccaed8ac4b0288948849a350b37d3035827413c458b6a40ddb614bb4f72"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "regex"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
+dependencies = [
+ "byteorder 1.3.4",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "retain_mut"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e005d658ad26eacc2b6c506dfde519f4e277e328d0eb3379ca61647d70a8f531"
+
+[[package]]
+name = "ring"
+version = "0.16.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70017ed5c555d79ee3538fc63ca09c70ad8f317dcadc1adc2c496b60c22bb24f"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell 1.5.2",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rocksdb"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d83c02c429044d58474eaf5ae31e062d0de894e21125b47437ec0edc1397e6"
+dependencies = [
+ "libc",
+ "librocksdb-sys",
+]
+
+[[package]]
+name = "rpassword"
+version = "4.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99371657d3c8e4d816fb6221db98fa408242b0b53bac08f8676a41f8554fe99f"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rust-argon2"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
+dependencies = [
+ "base64 0.13.0",
+ "blake2b_simd",
+ "constant_time_eq",
+ "crossbeam-utils 0.8.1",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc-hex"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustls"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81"
+dependencies = [
+ "base64 0.12.3",
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "629d439a7672da82dd955498445e496ee2096fe2117b9f796558a43fdb9e59b8"
+dependencies = [
+ "openssl-probe",
+ "rustls",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rusttype"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f61411055101f7b60ecf1041d87fb74205fb20b0c7a723f07ef39174cf6b4c0"
+dependencies = [
+ "approx",
+ "ordered-float",
+ "stb_truetype",
+]
+
+[[package]]
+name = "rw-stream-sink"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020"
+dependencies = [
+ "futures 0.3.8",
+ "pin-project 0.4.27",
+ "static_assertions",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "safe-mix"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d3d055a2582e6b00ed7a31c1524040aa391092bf636328350813f3a0605215c"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "salsa20"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f47b10fa80f6969bbbd9c8e7cc998f082979d402a9e10579e2303a87955395"
+dependencies = [
+ "stream-cipher",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "sc-basic-authorship"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "futures 0.3.8",
+ "futures-timer 3.0.2",
+ "log",
+ "parity-scale-codec",
+ "sc-block-builder",
+ "sc-client-api",
+ "sc-proposer-metrics",
+ "sc-telemetry",
+ "sp-api",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-core",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-transaction-pool",
+ "substrate-prometheus-endpoint",
+ "tokio-executor 0.2.0-alpha.6",
+]
+
+[[package]]
+name = "sc-block-builder"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sc-client-api",
+ "sp-api",
+ "sp-block-builder",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-core",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-state-machine",
+]
+
+[[package]]
+name = "sc-chain-spec"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "impl-trait-for-tuples",
+ "parity-scale-codec",
+ "sc-chain-spec-derive",
+ "sc-network",
+ "sc-telemetry",
+ "serde",
+ "serde_json",
+ "sp-chain-spec",
+ "sp-core",
+ "sp-runtime",
+]
+
+[[package]]
+name = "sc-chain-spec-derive"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sc-cli"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "ansi_term 0.12.1",
+ "atty",
+ "bip39",
+ "chrono",
+ "derive_more",
+ "fdlimit",
+ "futures 0.3.8",
+ "hex",
+ "lazy_static",
+ "libp2p",
+ "log",
+ "names",
+ "nix",
+ "parity-scale-codec",
+ "parity-util-mem",
+ "rand 0.7.3",
+ "regex",
+ "rpassword",
+ "sc-client-api",
+ "sc-informant",
+ "sc-keystore",
+ "sc-network",
+ "sc-service",
+ "sc-telemetry",
+ "sc-tracing",
+ "serde",
+ "serde_json",
+ "sp-blockchain",
+ "sp-core",
+ "sp-keyring",
+ "sp-panic-handler",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-utils",
+ "sp-version",
+ "structopt",
+ "substrate-prometheus-endpoint",
+ "time",
+ "tokio 0.2.23",
+ "tracing",
+ "tracing-log",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "sc-client-api"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "fnv",
+ "futures 0.3.8",
+ "hash-db",
+ "hex-literal",
+ "kvdb",
+ "lazy_static",
+ "log",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "sc-executor",
+ "sc-telemetry",
+ "sp-api",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-core",
+ "sp-database",
+ "sp-externalities",
+ "sp-inherents",
+ "sp-keyring",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-std",
+ "sp-storage",
+ "sp-transaction-pool",
+ "sp-trie",
+ "sp-utils",
+ "sp-version",
+ "substrate-prometheus-endpoint",
+]
+
+[[package]]
+name = "sc-client-db"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "blake2-rfc",
+ "hash-db",
+ "kvdb",
+ "kvdb-memorydb",
+ "linked-hash-map",
+ "log",
+ "parity-scale-codec",
+ "parity-util-mem",
+ "parking_lot 0.10.2",
+ "sc-client-api",
+ "sc-executor",
+ "sc-state-db",
+ "sp-arithmetic",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-core",
+ "sp-database",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-trie",
+ "substrate-prometheus-endpoint",
+]
+
+[[package]]
+name = "sc-executor"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "lazy_static",
+ "libsecp256k1",
+ "log",
+ "parity-scale-codec",
+ "parity-wasm",
+ "parking_lot 0.10.2",
+ "sc-executor-common",
+ "sc-executor-wasmi",
+ "sp-api",
+ "sp-core",
+ "sp-externalities",
+ "sp-io",
+ "sp-panic-handler",
+ "sp-runtime-interface",
+ "sp-serializer",
+ "sp-trie",
+ "sp-version",
+ "sp-wasm-interface",
+ "wasmi",
+]
+
+[[package]]
+name = "sc-executor-common"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "log",
+ "parity-scale-codec",
+ "parity-wasm",
+ "sp-allocator",
+ "sp-core",
+ "sp-runtime-interface",
+ "sp-serializer",
+ "sp-wasm-interface",
+ "wasmi",
+]
+
+[[package]]
+name = "sc-executor-wasmi"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "log",
+ "parity-scale-codec",
+ "sc-executor-common",
+ "sp-allocator",
+ "sp-core",
+ "sp-runtime-interface",
+ "sp-wasm-interface",
+ "wasmi",
+]
+
+[[package]]
+name = "sc-informant"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "ansi_term 0.12.1",
+ "futures 0.3.8",
+ "log",
+ "parity-util-mem",
+ "sc-client-api",
+ "sc-network",
+ "sp-blockchain",
+ "sp-runtime",
+ "sp-transaction-pool",
+ "sp-utils",
+ "wasm-timer",
+]
+
+[[package]]
+name = "sc-keystore"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "hex",
+ "merlin",
+ "parking_lot 0.10.2",
+ "rand 0.7.3",
+ "serde_json",
+ "sp-application-crypto",
+ "sp-core",
+ "subtle 2.3.0",
+]
+
+[[package]]
+name = "sc-light"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "hash-db",
+ "lazy_static",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "sc-client-api",
+ "sc-executor",
+ "sp-api",
+ "sp-blockchain",
+ "sp-core",
+ "sp-externalities",
+ "sp-runtime",
+ "sp-state-machine",
+]
+
+[[package]]
+name = "sc-network"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "async-std",
+ "async-trait",
+ "bitflags",
+ "bs58 0.3.1",
+ "bytes 0.5.6",
+ "derive_more",
+ "either",
+ "erased-serde",
+ "fnv",
+ "fork-tree",
+ "futures 0.3.8",
+ "futures-timer 3.0.2",
+ "futures_codec",
+ "hex",
+ "ip_network",
+ "libp2p",
+ "linked-hash-map",
+ "linked_hash_set",
+ "log",
+ "lru 0.4.3",
+ "nohash-hasher",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "pin-project 0.4.27",
+ "prost",
+ "prost-build",
+ "rand 0.7.3",
+ "sc-block-builder",
+ "sc-client-api",
+ "sc-peerset",
+ "serde",
+ "serde_json",
+ "slog",
+ "slog_derive",
+ "smallvec 0.6.13",
+ "sp-arithmetic",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-core",
+ "sp-runtime",
+ "sp-utils",
+ "substrate-prometheus-endpoint",
+ "thiserror",
+ "unsigned-varint 0.4.0",
+ "void",
+ "wasm-timer",
+ "zeroize",
+]
+
+[[package]]
+name = "sc-offchain"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "bytes 0.5.6",
+ "fnv",
+ "futures 0.3.8",
+ "futures-timer 3.0.2",
+ "hyper 0.13.9",
+ "hyper-rustls",
+ "log",
+ "num_cpus",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "rand 0.7.3",
+ "sc-client-api",
+ "sc-keystore",
+ "sc-network",
+ "sp-api",
+ "sp-core",
+ "sp-offchain",
+ "sp-runtime",
+ "sp-utils",
+ "threadpool",
+]
+
+[[package]]
+name = "sc-peerset"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "futures 0.3.8",
+ "libp2p",
+ "log",
+ "serde_json",
+ "sp-utils",
+ "wasm-timer",
+]
+
+[[package]]
+name = "sc-proposer-metrics"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "log",
+ "substrate-prometheus-endpoint",
+]
+
+[[package]]
+name = "sc-rpc"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "futures 0.3.8",
+ "hash-db",
+ "jsonrpc-core",
+ "jsonrpc-pubsub",
+ "log",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "sc-block-builder",
+ "sc-client-api",
+ "sc-executor",
+ "sc-keystore",
+ "sc-rpc-api",
+ "serde_json",
+ "sp-api",
+ "sp-blockchain",
+ "sp-chain-spec",
+ "sp-core",
+ "sp-offchain",
+ "sp-rpc",
+ "sp-runtime",
+ "sp-session",
+ "sp-state-machine",
+ "sp-transaction-pool",
+ "sp-utils",
+ "sp-version",
+]
+
+[[package]]
+name = "sc-rpc-api"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "futures 0.3.8",
+ "jsonrpc-core",
+ "jsonrpc-core-client",
+ "jsonrpc-derive",
+ "jsonrpc-pubsub",
+ "log",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "serde",
+ "serde_json",
+ "sp-chain-spec",
+ "sp-core",
+ "sp-rpc",
+ "sp-runtime",
+ "sp-transaction-pool",
+ "sp-version",
+]
+
+[[package]]
+name = "sc-rpc-server"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "futures 0.1.30",
+ "jsonrpc-core",
+ "jsonrpc-http-server",
+ "jsonrpc-ipc-server",
+ "jsonrpc-pubsub",
+ "jsonrpc-ws-server",
+ "log",
+ "serde",
+ "serde_json",
+ "sp-runtime",
+ "substrate-prometheus-endpoint",
+]
+
+[[package]]
+name = "sc-service"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "directories",
+ "exit-future",
+ "futures 0.1.30",
+ "futures 0.3.8",
+ "futures-timer 3.0.2",
+ "hash-db",
+ "jsonrpc-core",
+ "jsonrpc-pubsub",
+ "lazy_static",
+ "log",
+ "parity-scale-codec",
+ "parity-util-mem",
+ "parking_lot 0.10.2",
+ "pin-project 0.4.27",
+ "rand 0.7.3",
+ "sc-block-builder",
+ "sc-chain-spec",
+ "sc-client-api",
+ "sc-client-db",
+ "sc-executor",
+ "sc-informant",
+ "sc-keystore",
+ "sc-light",
+ "sc-network",
+ "sc-offchain",
+ "sc-rpc",
+ "sc-rpc-server",
+ "sc-telemetry",
+ "sc-tracing",
+ "sc-transaction-pool",
+ "serde",
+ "serde_json",
+ "slog",
+ "sp-api",
+ "sp-application-crypto",
+ "sp-block-builder",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-core",
+ "sp-externalities",
+ "sp-inherents",
+ "sp-io",
+ "sp-runtime",
+ "sp-session",
+ "sp-state-machine",
+ "sp-tracing",
+ "sp-transaction-pool",
+ "sp-trie",
+ "sp-utils",
+ "sp-version",
+ "substrate-prometheus-endpoint",
+ "tempfile",
+ "tracing",
+ "wasm-timer",
+]
+
+[[package]]
+name = "sc-state-db"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "log",
+ "parity-scale-codec",
+ "parity-util-mem",
+ "parity-util-mem-derive",
+ "parking_lot 0.10.2",
+ "sc-client-api",
+ "sp-core",
+]
+
+[[package]]
+name = "sc-telemetry"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "futures 0.3.8",
+ "futures-timer 3.0.2",
+ "libp2p",
+ "log",
+ "parking_lot 0.10.2",
+ "pin-project 0.4.27",
+ "rand 0.7.3",
+ "serde",
+ "slog",
+ "slog-json",
+ "slog-scope",
+ "take_mut",
+ "void",
+ "wasm-timer",
+]
+
+[[package]]
+name = "sc-tracing"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "erased-serde",
+ "log",
+ "parking_lot 0.10.2",
+ "rustc-hash",
+ "sc-telemetry",
+ "serde",
+ "serde_json",
+ "slog",
+ "sp-tracing",
+ "tracing",
+ "tracing-core",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "sc-transaction-graph"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "futures 0.3.8",
+ "linked-hash-map",
+ "log",
+ "parity-util-mem",
+ "parking_lot 0.10.2",
+ "retain_mut",
+ "serde",
+ "sp-blockchain",
+ "sp-core",
+ "sp-runtime",
+ "sp-transaction-pool",
+ "sp-utils",
+ "wasm-timer",
+]
+
+[[package]]
+name = "sc-transaction-pool"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "futures 0.3.8",
+ "futures-diagnose",
+ "intervalier",
+ "log",
+ "parity-scale-codec",
+ "parity-util-mem",
+ "parking_lot 0.10.2",
+ "sc-client-api",
+ "sc-transaction-graph",
+ "sp-api",
+ "sp-blockchain",
+ "sp-core",
+ "sp-runtime",
+ "sp-tracing",
+ "sp-transaction-pool",
+ "sp-utils",
+ "substrate-prometheus-endpoint",
+ "wasm-timer",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
+dependencies = [
+ "lazy_static",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "schnorrkel"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862"
+dependencies = [
+ "arrayref",
+ "arrayvec 0.5.2",
+ "curve25519-dalek 2.1.0",
+ "getrandom 0.1.15",
+ "merlin",
+ "rand 0.7.3",
+ "rand_core 0.5.1",
+ "sha2 0.8.2",
+ "subtle 2.3.0",
+ "zeroize",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "scopeguard"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "sct"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "secrecy"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9182278ed645df3477a9c27bfee0621c621aa16f6972635f7f795dae3d81070f"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "security-framework"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad502866817f0575705bd7be36e2b2535cc33262d493aa733a2ec862baa2bc2b"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51ceb04988b17b6d1dcd555390fa822ca5637b4a14e1f5099f13d351bed4d6c7"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "serde"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "servo-fontconfig"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a088f8d775a5c5314aae09bd77340bc9c67d72b9a45258be34c83548b4814cd9"
+dependencies = [
+ "libc",
+ "servo-fontconfig-sys",
+]
+
+[[package]]
+name = "servo-fontconfig-sys"
+version = "4.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b3e166450f523f4db06c14f02a2d39e76d49b5d8cbd224338d93e3595c156c"
+dependencies = [
+ "expat-sys",
+ "pkg-config",
+ "servo-freetype-sys",
+]
+
+[[package]]
+name = "servo-freetype-sys"
+version = "4.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4ccb6d0d32d277d3ef7dea86203d8210945eb7a45fba89dd445b3595dd0dfc"
+dependencies = [
+ "cmake",
+ "pkg-config",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug 0.2.3",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if 1.0.0",
+ "cpuid-bool",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "sha2"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug 0.2.3",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if 1.0.0",
+ "cpuid-bool",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "sha3"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
+dependencies = [
+ "block-buffer 0.9.0",
+ "digest 0.9.0",
+ "keccak",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4921be914e16899a80adefb821f8ddb7974e3f1250223575a44ed994882127"
+dependencies = [
+ "lazy_static",
+ "loom",
+]
+
+[[package]]
+name = "shlex"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210"
+
+[[package]]
+name = "simba"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb931b1367faadea6b1ab1c306a860ec17aaa5fa39f367d0c744e69d971a1fb2"
+dependencies = [
+ "approx",
+ "num-complex",
+ "num-traits",
+ "paste",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+
+[[package]]
+name = "slog"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06"
+dependencies = [
+ "erased-serde",
+]
+
+[[package]]
+name = "slog-json"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddc0d2aff1f8f325ef660d9a0eb6e6dcd20b30b3f581a5897f58bf42d061c37a"
+dependencies = [
+ "chrono",
+ "erased-serde",
+ "serde",
+ "serde_json",
+ "slog",
+]
+
+[[package]]
+name = "slog-scope"
+version = "4.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c44c89dd8b0ae4537d1ae318353eaf7840b4869c536e31c41e963d1ea523ee6"
+dependencies = [
+ "arc-swap",
+ "lazy_static",
+ "slog",
+]
+
+[[package]]
+name = "slog_derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a945ec7f7ce853e89ffa36be1e27dce9a43e82ff9093bf3461c30d5da74ed11b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "smallvec"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
+dependencies = [
+ "maybe-uninit",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0"
+
+[[package]]
+name = "snow"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "795dd7aeeee24468e5a32661f6d27f7b5cbed802031b2d7640c7b10f8fb2dd50"
+dependencies = [
+ "aes-gcm",
+ "blake2",
+ "chacha20poly1305",
+ "rand 0.7.3",
+ "rand_core 0.5.1",
+ "ring",
+ "rustc_version",
+ "sha2 0.9.2",
+ "subtle 2.3.0",
+ "x25519-dalek 1.1.0",
+]
+
+[[package]]
+name = "socket2"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "soketto"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5c71ed3d54db0a699f4948e1bb3e45b450fa31fe602621dee6680361d569c88"
+dependencies = [
+ "base64 0.12.3",
+ "bytes 0.5.6",
+ "flate2",
+ "futures 0.3.8",
+ "httparse",
+ "log",
+ "rand 0.7.3",
+ "sha-1 0.9.2",
+]
+
+[[package]]
+name = "sp-allocator"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "log",
+ "sp-core",
+ "sp-std",
+ "sp-wasm-interface",
+]
+
+[[package]]
+name = "sp-api"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "hash-db",
+ "parity-scale-codec",
+ "sp-api-proc-macro",
+ "sp-core",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-std",
+ "sp-version",
+]
+
+[[package]]
+name = "sp-api-proc-macro"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "blake2-rfc",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sp-application-crypto"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-arithmetic"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "integer-sqrt",
+ "num-traits",
+ "parity-scale-codec",
+ "serde",
+ "sp-debug-derive",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-authority-discovery"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-api",
+ "sp-application-crypto",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-authorship"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-block-builder"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-api",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-blockchain"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "log",
+ "lru 0.4.3",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "sp-block-builder",
+ "sp-consensus",
+ "sp-database",
+ "sp-runtime",
+ "sp-state-machine",
+]
+
+[[package]]
+name = "sp-chain-spec"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "sp-consensus"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "futures 0.3.8",
+ "futures-timer 3.0.2",
+ "libp2p",
+ "log",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "serde",
+ "sp-api",
+ "sp-core",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-std",
+ "sp-trie",
+ "sp-utils",
+ "sp-version",
+ "substrate-prometheus-endpoint",
+ "wasm-timer",
+]
+
+[[package]]
+name = "sp-consensus-babe"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "merlin",
+ "parity-scale-codec",
+ "sp-api",
+ "sp-application-crypto",
+ "sp-consensus",
+ "sp-consensus-slots",
+ "sp-consensus-vrf",
+ "sp-core",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-std",
+ "sp-timestamp",
+]
+
+[[package]]
+name = "sp-consensus-slots"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-runtime",
+]
+
+[[package]]
+name = "sp-consensus-vrf"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "schnorrkel",
+ "sp-core",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-core"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "base58",
+ "blake2-rfc",
+ "byteorder 1.3.4",
+ "derive_more",
+ "dyn-clonable",
+ "ed25519-dalek",
+ "futures 0.3.8",
+ "hash-db",
+ "hash256-std-hasher",
+ "hex",
+ "impl-serde",
+ "lazy_static",
+ "libsecp256k1",
+ "log",
+ "merlin",
+ "num-traits",
+ "parity-scale-codec",
+ "parity-util-mem",
+ "parking_lot 0.10.2",
+ "primitive-types",
+ "rand 0.7.3",
+ "regex",
+ "schnorrkel",
+ "secrecy",
+ "serde",
+ "sha2 0.8.2",
+ "sp-debug-derive",
+ "sp-externalities",
+ "sp-runtime-interface",
+ "sp-std",
+ "sp-storage",
+ "substrate-bip39",
+ "tiny-bip39",
+ "tiny-keccak",
+ "twox-hash",
+ "wasmi",
+ "zeroize",
+]
+
+[[package]]
+name = "sp-database"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "kvdb",
+ "parking_lot 0.10.2",
+]
+
+[[package]]
+name = "sp-debug-derive"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sp-externalities"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "environmental",
+ "parity-scale-codec",
+ "sp-std",
+ "sp-storage",
+]
+
+[[package]]
+name = "sp-finality-grandpa"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "finality-grandpa",
+ "log",
+ "parity-scale-codec",
+ "serde",
+ "sp-api",
+ "sp-application-crypto",
+ "sp-core",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-finality-tracker"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-inherents",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-inherents"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "sp-core",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-io"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "futures 0.3.8",
+ "hash-db",
+ "libsecp256k1",
+ "log",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "sp-core",
+ "sp-externalities",
+ "sp-runtime-interface",
+ "sp-state-machine",
+ "sp-std",
+ "sp-tracing",
+ "sp-trie",
+ "sp-wasm-interface",
+ "tracing",
+ "tracing-core",
+]
+
+[[package]]
+name = "sp-keyring"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "lazy_static",
+ "sp-core",
+ "sp-runtime",
+ "strum",
+]
+
+[[package]]
+name = "sp-npos-elections"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "serde",
+ "sp-arithmetic",
+ "sp-npos-elections-compact",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-npos-elections-compact"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sp-offchain"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "sp-api",
+ "sp-core",
+ "sp-runtime",
+]
+
+[[package]]
+name = "sp-panic-handler"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "backtrace",
+ "log",
+]
+
+[[package]]
+name = "sp-rpc"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "serde",
+ "sp-core",
+]
+
+[[package]]
+name = "sp-runtime"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "either",
+ "hash256-std-hasher",
+ "impl-trait-for-tuples",
+ "log",
+ "parity-scale-codec",
+ "parity-util-mem",
+ "paste",
+ "rand 0.7.3",
+ "serde",
+ "sp-application-crypto",
+ "sp-arithmetic",
+ "sp-core",
+ "sp-inherents",
+ "sp-io",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-runtime-interface"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "primitive-types",
+ "sp-externalities",
+ "sp-runtime-interface-proc-macro",
+ "sp-std",
+ "sp-storage",
+ "sp-tracing",
+ "sp-wasm-interface",
+ "static_assertions",
+]
+
+[[package]]
+name = "sp-runtime-interface-proc-macro"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "Inflector",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sp-sandbox"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-core",
+ "sp-io",
+ "sp-std",
+ "sp-wasm-interface",
+ "wasmi",
+]
+
+[[package]]
+name = "sp-serializer"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "sp-session"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-api",
+ "sp-core",
+ "sp-runtime",
+ "sp-staking",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-staking"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "parity-scale-codec",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-state-machine"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "hash-db",
+ "log",
+ "num-traits",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "rand 0.7.3",
+ "smallvec 1.6.0",
+ "sp-core",
+ "sp-externalities",
+ "sp-panic-handler",
+ "sp-std",
+ "sp-trie",
+ "trie-db",
+ "trie-root",
+]
+
+[[package]]
+name = "sp-std"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+
+[[package]]
+name = "sp-storage"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "impl-serde",
+ "parity-scale-codec",
+ "ref-cast",
+ "serde",
+ "sp-debug-derive",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-timestamp"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "impl-trait-for-tuples",
+ "parity-scale-codec",
+ "sp-api",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-std",
+ "wasm-timer",
+]
+
+[[package]]
+name = "sp-tracing"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "log",
+ "parity-scale-codec",
+ "sp-std",
+ "tracing",
+ "tracing-core",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "sp-transaction-pool"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "derive_more",
+ "futures 0.3.8",
+ "log",
+ "parity-scale-codec",
+ "serde",
+ "sp-api",
+ "sp-blockchain",
+ "sp-runtime",
+]
+
+[[package]]
+name = "sp-trie"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "hash-db",
+ "memory-db",
+ "parity-scale-codec",
+ "sp-core",
+ "sp-std",
+ "trie-db",
+ "trie-root",
+]
+
+[[package]]
+name = "sp-utils"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "futures 0.3.8",
+ "futures-core",
+ "futures-timer 3.0.2",
+ "lazy_static",
+ "prometheus",
+]
+
+[[package]]
+name = "sp-version"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "impl-serde",
+ "parity-scale-codec",
+ "serde",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "sp-wasm-interface"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "impl-trait-for-tuples",
+ "parity-scale-codec",
+ "sp-std",
+ "wasmi",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "statrs"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10102ac8d55e35db2b3fafc26f81ba8647da2e15879ab686a67e6d19af2685e8"
+dependencies = [
+ "rand 0.5.6",
+]
+
+[[package]]
+name = "statrs"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cce16f6de653e88beca7bd13780d08e09d4489dbca1f9210e041bc4852481382"
+dependencies = [
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "stb_truetype"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51"
+dependencies = [
+ "byteorder 1.3.4",
+]
+
+[[package]]
+name = "stream-cipher"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c80e15f898d8d8f25db24c253ea615cc14acf418ff307822995814e7d42cfa89"
+dependencies = [
+ "block-cipher",
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "string"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d"
+dependencies = [
+ "bytes 0.4.12",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "structopt"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "strum"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "substrate-bip39"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bed6646a0159b9935b5d045611560eeef842b78d7adc3ba36f5ca325a13a0236"
+dependencies = [
+ "hmac",
+ "pbkdf2",
+ "schnorrkel",
+ "sha2 0.8.2",
+ "zeroize",
+]
+
+[[package]]
+name = "substrate-prometheus-endpoint"
+version = "0.8.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+dependencies = [
+ "async-std",
+ "derive_more",
+ "futures-util",
+ "hyper 0.13.9",
+ "log",
+ "prometheus",
+ "tokio 0.2.23",
+]
+
+[[package]]
+name = "substrate-wasm-builder-runner"
+version = "1.0.6"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+
+[[package]]
+name = "subtle"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
+
+[[package]]
+name = "subtle"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd"
+
+[[package]]
+name = "syn"
+version = "1.0.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
+]
+
+[[package]]
+name = "take_mut"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
+
+[[package]]
+name = "tempfile"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "rand 0.7.3",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tiny-bip39"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0165e045cc2ae1660270ca65e1676dbaab60feb0f91b10f7d0665e9b47e31f2"
+dependencies = [
+ "failure",
+ "hmac",
+ "once_cell 1.5.2",
+ "pbkdf2",
+ "rand 0.7.3",
+ "rustc-hash",
+ "sha2 0.8.2",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "mio",
+ "num_cpus",
+ "tokio-codec",
+ "tokio-current-thread",
+ "tokio-executor 0.1.10",
+ "tokio-fs",
+ "tokio-io",
+ "tokio-reactor",
+ "tokio-sync 0.1.8",
+ "tokio-tcp",
+ "tokio-threadpool",
+ "tokio-timer",
+ "tokio-udp",
+ "tokio-uds",
+]
+
+[[package]]
+name = "tokio"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff"
+dependencies = [
+ "bytes 0.5.6",
+ "fnv",
+ "futures-core",
+ "iovec",
+ "lazy_static",
+ "libc",
+ "memchr",
+ "mio",
+ "mio-uds",
+ "num_cpus",
+ "pin-project-lite 0.1.11",
+ "signal-hook-registry",
+ "slab",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tokio-buf"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46"
+dependencies = [
+ "bytes 0.4.12",
+ "either",
+ "futures 0.1.30",
+]
+
+[[package]]
+name = "tokio-codec"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "tokio-io",
+]
+
+[[package]]
+name = "tokio-current-thread"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e"
+dependencies = [
+ "futures 0.1.30",
+ "tokio-executor 0.1.10",
+]
+
+[[package]]
+name = "tokio-executor"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.30",
+]
+
+[[package]]
+name = "tokio-executor"
+version = "0.2.0-alpha.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ee9ceecf69145923834ea73f32ba40c790fd877b74a7817dd0b089f1eb9c7c8"
+dependencies = [
+ "futures-util-preview",
+ "lazy_static",
+ "tokio-sync 0.2.0-alpha.6",
+]
+
+[[package]]
+name = "tokio-fs"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4"
+dependencies = [
+ "futures 0.1.30",
+ "tokio-io",
+ "tokio-threadpool",
+]
+
+[[package]]
+name = "tokio-io"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "log",
+]
+
+[[package]]
+name = "tokio-named-pipes"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d282d483052288b2308ba5ee795f5673b159c9bdf63c385a05609da782a5eae"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "mio",
+ "mio-named-pipes",
+ "tokio 0.1.22",
+]
+
+[[package]]
+name = "tokio-reactor"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.30",
+ "lazy_static",
+ "log",
+ "mio",
+ "num_cpus",
+ "parking_lot 0.9.0",
+ "slab",
+ "tokio-executor 0.1.10",
+ "tokio-io",
+ "tokio-sync 0.1.8",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a"
+dependencies = [
+ "futures-core",
+ "rustls",
+ "tokio 0.2.23",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-service"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162"
+dependencies = [
+ "futures 0.1.30",
+]
+
+[[package]]
+name = "tokio-sync"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee"
+dependencies = [
+ "fnv",
+ "futures 0.1.30",
+]
+
+[[package]]
+name = "tokio-sync"
+version = "0.2.0-alpha.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f1aaeb685540f7407ea0e27f1c9757d258c7c6bf4e3eb19da6fc59b747239d2"
+dependencies = [
+ "fnv",
+ "futures-core-preview",
+ "futures-util-preview",
+]
+
+[[package]]
+name = "tokio-tcp"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "iovec",
+ "mio",
+ "tokio-io",
+ "tokio-reactor",
+]
+
+[[package]]
+name = "tokio-threadpool"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89"
+dependencies = [
+ "crossbeam-deque 0.7.3",
+ "crossbeam-queue",
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.30",
+ "lazy_static",
+ "log",
+ "num_cpus",
+ "slab",
+ "tokio-executor 0.1.10",
+]
+
+[[package]]
+name = "tokio-timer"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.30",
+ "slab",
+ "tokio-executor 0.1.10",
+]
+
+[[package]]
+name = "tokio-udp"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "log",
+ "mio",
+ "tokio-codec",
+ "tokio-io",
+ "tokio-reactor",
+]
+
+[[package]]
+name = "tokio-uds"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.30",
+ "iovec",
+ "libc",
+ "log",
+ "mio",
+ "mio-uds",
+ "tokio-codec",
+ "tokio-io",
+ "tokio-reactor",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
+dependencies = [
+ "bytes 0.5.6",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite 0.1.11",
+ "tokio 0.2.23",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
+
+[[package]]
+name = "tracing"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
+dependencies = [
+ "cfg-if 1.0.0",
+ "log",
+ "pin-project-lite 0.2.0",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "tracing-futures"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c"
+dependencies = [
+ "pin-project 0.4.27",
+ "tracing",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401"
+dependencies = [
+ "ansi_term 0.12.1",
+ "chrono",
+ "lazy_static",
+ "matchers",
+ "regex",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec 1.6.0",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
+[[package]]
+name = "trie-db"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e55f7ace33d6237e14137e386f4e1672e2a5c6bbc97fef9f438581a143971f0"
+dependencies = [
+ "hash-db",
+ "hashbrown 0.8.2",
+ "log",
+ "rustc-hex",
+ "smallvec 1.6.0",
+]
+
+[[package]]
+name = "trie-root"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "652931506d2c1244d7217a70b99f56718a7b4161b37f04e7cd868072a99f68cd"
+dependencies = [
+ "hash-db",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "twox-hash"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
+dependencies = [
+ "cfg-if 0.1.10",
+ "rand 0.7.3",
+ "static_assertions",
+]
+
+[[package]]
+name = "typenum"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
+
+[[package]]
+name = "uint"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9db035e67dfaf7edd9aebfe8676afcd63eed53c8a4044fed514c8cccf1835177"
+dependencies = [
+ "byteorder 1.3.4",
+ "crunchy",
+ "rustc-hex",
+ "static_assertions",
+]
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+dependencies = [
+ "matches",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "universal-hash"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402"
+dependencies = [
+ "generic-array 0.14.4",
+ "subtle 2.3.0",
+]
+
+[[package]]
+name = "unsigned-varint"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "669d776983b692a906c881fcd0cfb34271a48e197e4d6cb8df32b05bfc3d3fa5"
+dependencies = [
+ "bytes 0.5.6",
+ "futures-io",
+ "futures-util",
+ "futures_codec",
+]
+
+[[package]]
+name = "unsigned-varint"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fdeedbf205afadfe39ae559b75c3240f24e257d0ca27e85f85cb82aa19ac35"
+dependencies = [
+ "futures-io",
+ "futures-util",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "url"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
+dependencies = [
+ "idna 0.1.5",
+ "matches",
+ "percent-encoding 1.0.1",
+]
+
+[[package]]
+name = "url"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
+dependencies = [
+ "form_urlencoded",
+ "idna 0.2.0",
+ "matches",
+ "percent-encoding 2.1.0",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
+
+[[package]]
+name = "vec-arena"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
+[[package]]
+name = "waker-fn"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi 0.3.9",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230"
+dependencies = [
+ "futures 0.1.30",
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35"
+dependencies = [
+ "cfg-if 1.0.0",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
+
+[[package]]
+name = "wasm-timer"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
+dependencies = [
+ "futures 0.3.8",
+ "js-sys",
+ "parking_lot 0.11.1",
+ "pin-utils",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "wasmi"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf617d864d25af3587aa745529f7aaa541066c876d57e050c0d0c85c61c92aff"
+dependencies = [
+ "libc",
+ "memory_units",
+ "num-rational 0.2.4",
+ "num-traits",
+ "parity-wasm",
+ "wasmi-validation",
+]
+
+[[package]]
+name = "wasmi-validation"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea78c597064ba73596099281e2f4cfc019075122a65cdda3205af94f0b264d93"
+dependencies = [
+ "parity-wasm",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4"
+dependencies = [
+ "webpki",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739"
+dependencies = [
+ "webpki",
+]
+
+[[package]]
+name = "wepoll-sys"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "which"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "wio"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "x25519-dalek"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217"
+dependencies = [
+ "curve25519-dalek 2.1.0",
+ "rand_core 0.5.1",
+ "zeroize",
+]
+
+[[package]]
+name = "x25519-dalek"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088"
+dependencies = [
+ "curve25519-dalek 3.0.0",
+ "rand_core 0.5.1",
+ "zeroize",
+]
+
+[[package]]
+name = "yamux"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aeb8c4043cac71c3c299dff107171c220d179492350ea198e109a414981b83c"
+dependencies = [
+ "futures 0.3.8",
+ "log",
+ "nohash-hasher",
+ "parking_lot 0.11.1",
+ "rand 0.7.3",
+ "static_assertions",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]

+ 49 - 0
analyses/bench/Cargo.toml

@@ -0,0 +1,49 @@
+[package]
+name = "node-bench"
+version = "0.8.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+description = "Substrate node integration benchmarks."
+edition = "2018"
+license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+log = "0.4.8"
+node-primitives = { package = 'node-primitives', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+node-runtime = { package = 'node-runtime', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sc-cli = { package = 'sc-cli', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sc-client-api = { package = 'sc-client-api', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sp-runtime = { package = 'sp-runtime', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sp-state-machine = { package = 'sp-state-machine', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+serde = "1.0.101"
+serde_json = "1.0.41"
+structopt = "0.3"
+derive_more = "0.99.2"
+kvdb = "0.7"
+kvdb-rocksdb = "0.9.1"
+sp-trie = { package = 'sp-trie', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sp-core = { package = 'sp-core', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sp-consensus = { package = 'sp-consensus', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sp-transaction-pool = { package = 'sp-transaction-pool', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sc-basic-authorship = { package = 'sc-basic-authorship', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sp-inherents = { package = 'sp-inherents', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sp-timestamp = { package = 'sp-timestamp', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+sp-tracing = { package = 'sp-tracing', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+hash-db = "0.15.2"
+tempfile = "3.1.0"
+fs_extra = "1"
+hex = "0.4.0"
+rand = { version = "0.7.2", features = ["small_rng"] }
+lazy_static = "1.4.0"
+parity-util-mem = { version = "0.7.0", default-features = false, features = ["primitive-types"] }
+parity-db = { version = "0.1.2" }
+sc-transaction-pool = { package = 'sc-transaction-pool', git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca' }
+futures = { version = "0.3.4", features = ["thread-pool"] }
+
+# Extra
+linregress = { version = "0.4.0" }
+plotters = { version = "0.3", optional = true }
+
+[features]
+plot = ['plotters']

+ 60 - 0
analyses/bench/src/common.rs

@@ -0,0 +1,60 @@
+use std::str::FromStr;
+
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#[derive(Clone, Copy, Debug, derive_more::Display)]
+pub enum DatabaseSize {
+    #[display(fmt = "small")]
+    Small,
+    #[display(fmt = "medium")]
+    Medium,
+    #[display(fmt = "large")]
+    Large,
+    #[display(fmt = "huge")]
+    Huge,
+}
+
+#[derive(Clone, Copy, Debug, derive_more::Display)]
+pub struct ParseDatabaseSizeError;
+
+impl FromStr for DatabaseSize {
+    type Err = ParseDatabaseSizeError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.to_lowercase().trim() {
+            "small" => Ok(Self::Small),
+            "medium" => Ok(Self::Medium),
+            "large" => Ok(Self::Large),
+            "huge" => Ok(Self::Huge),
+            _ => Err(ParseDatabaseSizeError),
+        }
+    }
+}
+
+impl DatabaseSize {
+    /// Should be multiple of SAMPLE_SIZE!
+    pub(crate) fn keys(&self) -> usize {
+        match *self {
+            Self::Small => 10_000,
+            Self::Medium => 100_000,
+            Self::Large => 200_000,
+            Self::Huge => 1_000_000,
+        }
+    }
+}

+ 296 - 0
analyses/bench/src/construct.rs

@@ -0,0 +1,296 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+//! Block construction benchmark.
+//!
+//! This benchmark is expected to measure block construction.
+//! We want to protect against cold-cache attacks, and so this
+//! benchmark should not rely on any caching (except those entries that
+//! DO NOT depend on user input). Thus transaction generation should be
+//! based on randomized data.
+
+use std::{
+	borrow::Cow,
+	collections::HashMap,
+	pin::Pin,
+	sync::Arc,
+};
+use futures::Future;
+
+use node_primitives::Block;
+use node_testing::bench::{BenchDb, Profile, BlockType, KeyTypes, DatabaseType};
+use sp_runtime::{
+	generic::BlockId,
+	traits::NumberFor,
+	OpaqueExtrinsic,
+};
+use sp_transaction_pool::{
+	ImportNotificationStream,
+	PoolFuture,
+	PoolStatus,
+	TransactionFor,
+	TransactionSource,
+	TransactionStatusStreamFor,
+	TxHash,
+};
+use sp_consensus::{Environment, Proposer};
+
+use crate::{
+	common::SizeType,
+	core::{self, Path, Mode},
+};
+
+pub struct ConstructionBenchmarkDescription {
+	pub profile: Profile,
+	pub key_types: KeyTypes,
+	pub block_type: BlockType,
+	pub size: SizeType,
+	pub database_type: DatabaseType,
+}
+
+pub struct ConstructionBenchmark {
+	profile: Profile,
+	database: BenchDb,
+	transactions: Transactions,
+}
+
+impl core::BenchmarkDescription for ConstructionBenchmarkDescription {
+	fn path(&self) -> Path {
+
+		let mut path = Path::new(&["node", "proposer"]);
+
+		match self.profile {
+			Profile::Wasm => path.push("wasm"),
+			Profile::Native => path.push("native"),
+		}
+
+		match self.key_types {
+			KeyTypes::Sr25519 => path.push("sr25519"),
+			KeyTypes::Ed25519 => path.push("ed25519"),
+		}
+
+		match self.block_type {
+			BlockType::RandomTransfersKeepAlive => path.push("transfer"),
+			BlockType::RandomTransfersReaping => path.push("transfer_reaping"),
+			BlockType::Noop => path.push("noop"),
+		}
+
+		match self.database_type {
+			DatabaseType::RocksDb => path.push("rocksdb"),
+			DatabaseType::ParityDb => path.push("paritydb"),
+		}
+
+		path.push(&format!("{}", self.size));
+
+		path
+	}
+
+	fn setup(self: Box<Self>) -> Box<dyn core::Benchmark> {
+		let mut extrinsics: Vec<Arc<PoolTransaction>> = Vec::new();
+
+		let mut bench_db = BenchDb::with_key_types(
+			self.database_type,
+			50_000,
+			self.key_types
+		);
+
+		let client = bench_db.client();
+
+		let content_type = self.block_type.to_content(self.size.transactions());
+		for transaction in bench_db.block_content(content_type, &client) {
+			extrinsics.push(Arc::new(transaction.into()));
+		}
+
+		Box::new(ConstructionBenchmark {
+			profile: self.profile,
+			database: bench_db,
+			transactions: Transactions(extrinsics),
+		})
+	}
+
+	fn name(&self) -> Cow<'static, str> {
+		format!(
+			"Block construction ({:?}/{}, {:?}, {:?} backend)",
+			self.block_type,
+			self.size,
+			self.profile,
+			self.database_type,
+		).into()
+	}
+}
+
+impl core::Benchmark for ConstructionBenchmark {
+	fn run(&mut self, mode: Mode) -> std::time::Duration {
+		let context = self.database.create_context(self.profile);
+
+		let _ = context.client.runtime_version_at(&BlockId::Number(0))
+			.expect("Failed to get runtime version")
+			.spec_version;
+
+		if mode == Mode::Profile {
+			std::thread::park_timeout(std::time::Duration::from_secs(3));
+		}
+
+		let mut proposer_factory = sc_basic_authorship::ProposerFactory::new(
+			context.spawn_handle.clone(),
+			context.client.clone(),
+			self.transactions.clone().into(),
+			None,
+		);
+		let inherent_data_providers = sp_inherents::InherentDataProviders::new();
+		inherent_data_providers
+			.register_provider(sp_timestamp::InherentDataProvider)
+			.expect("Failed to register timestamp data provider");
+
+		let start = std::time::Instant::now();
+
+		let proposer = futures::executor::block_on(proposer_factory.init(
+			&context.client.header(&BlockId::number(0))
+				.expect("Database error querying block #0")
+				.expect("Block #0 should exist"),
+		)).expect("Proposer initialization failed");
+
+		let _block = futures::executor::block_on(
+			proposer.propose(
+				inherent_data_providers.create_inherent_data().expect("Create inherent data failed"),
+				Default::default(),
+				std::time::Duration::from_secs(20),
+			),
+		).map(|r| r.block).expect("Proposing failed");
+
+		let elapsed = start.elapsed();
+
+		if mode == Mode::Profile {
+			std::thread::park_timeout(std::time::Duration::from_secs(1));
+		}
+
+		elapsed
+	}
+}
+
+#[derive(Clone, Debug)]
+pub struct PoolTransaction {
+	data: OpaqueExtrinsic,
+	hash: node_primitives::Hash,
+}
+
+impl From<OpaqueExtrinsic> for PoolTransaction {
+	fn from(e: OpaqueExtrinsic) -> Self {
+		PoolTransaction {
+			data: e,
+			hash: node_primitives::Hash::zero(),
+		}
+	}
+}
+
+impl sp_transaction_pool::InPoolTransaction for PoolTransaction {
+	type Transaction = OpaqueExtrinsic;
+	type Hash = node_primitives::Hash;
+
+	fn data(&self) -> &Self::Transaction {
+		&self.data
+	}
+
+	fn hash(&self) -> &Self::Hash {
+		&self.hash
+	}
+
+	fn priority(&self) -> &u64 { unimplemented!() }
+
+	fn longevity(&self) -> &u64 { unimplemented!() }
+
+	fn requires(&self) -> &[Vec<u8>] { unimplemented!() }
+
+	fn provides(&self) -> &[Vec<u8>] { unimplemented!() }
+
+	fn is_propagable(&self) -> bool { unimplemented!() }
+}
+
+#[derive(Clone, Debug)]
+pub struct Transactions(Vec<Arc<PoolTransaction>>);
+
+impl sp_transaction_pool::TransactionPool for Transactions {
+	type Block = Block;
+	type Hash = node_primitives::Hash;
+	type InPoolTransaction = PoolTransaction;
+	type Error = sp_transaction_pool::error::Error;
+
+	/// Returns a future that imports a bunch of unverified transactions to the pool.
+	fn submit_at(
+		&self,
+		_at: &BlockId<Self::Block>,
+		_source: TransactionSource,
+		_xts: Vec<TransactionFor<Self>>,
+	) -> PoolFuture<Vec<Result<node_primitives::Hash, Self::Error>>, Self::Error>  {
+		unimplemented!()
+	}
+
+	/// Returns a future that imports one unverified transaction to the pool.
+	fn submit_one(
+		&self,
+		_at: &BlockId<Self::Block>,
+		_source: TransactionSource,
+		_xt: TransactionFor<Self>,
+	) -> PoolFuture<TxHash<Self>, Self::Error> {
+		unimplemented!()
+	}
+
+	fn submit_and_watch(
+		&self,
+		_at: &BlockId<Self::Block>,
+		_source: TransactionSource,
+		_xt: TransactionFor<Self>,
+	) -> PoolFuture<Box<TransactionStatusStreamFor<Self>>, Self::Error> {
+		unimplemented!()
+	}
+
+	fn ready_at(&self, _at: NumberFor<Self::Block>)
+		-> Pin<Box<dyn Future<Output=Box<dyn Iterator<Item=Arc<Self::InPoolTransaction>> + Send>> + Send>>
+	{
+		let iter: Box<dyn Iterator<Item=Arc<PoolTransaction>> + Send> = Box::new(self.0.clone().into_iter());
+		Box::pin(futures::future::ready(iter))
+	}
+
+	fn ready(&self) -> Box<dyn Iterator<Item=Arc<Self::InPoolTransaction>> + Send> {
+		unimplemented!()
+	}
+
+	fn remove_invalid(&self, _hashes: &[TxHash<Self>]) -> Vec<Arc<Self::InPoolTransaction>> {
+		Default::default()
+	}
+
+	fn status(&self) -> PoolStatus {
+		unimplemented!()
+	}
+
+	fn import_notification_stream(&self) -> ImportNotificationStream<TxHash<Self>> {
+		unimplemented!()
+	}
+
+	fn on_broadcasted(&self, _propagations: HashMap<TxHash<Self>, Vec<String>>) {
+		unimplemented!()
+	}
+
+	fn hash_of(&self, _xt: &TransactionFor<Self>) -> TxHash<Self> {
+		unimplemented!()
+	}
+
+	fn ready_transaction(&self, _hash: &TxHash<Self>) -> Option<Arc<Self::InPoolTransaction>> {
+		unimplemented!()
+	}
+}

+ 161 - 0
analyses/bench/src/core.rs

@@ -0,0 +1,161 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use serde::Serialize;
+use std::{
+    borrow::{Cow, ToOwned},
+    convert::TryInto,
+};
+
+pub struct Path(Vec<String>);
+
+impl Path {
+    pub fn new(initial: &'static [&'static str]) -> Self {
+        Path(initial.iter().map(|x| x.to_string()).collect())
+    }
+}
+
+impl Path {
+    pub fn push(&mut self, item: &str) {
+        self.0.push(item.to_string());
+    }
+}
+
+pub trait BenchmarkDescription {
+    fn path(&self) -> Path;
+
+    fn setup(self: Box<Self>) -> Box<dyn Benchmark>;
+
+    fn name(&self) -> Cow<'static, str>;
+}
+
+pub trait Benchmark {
+    fn run(&mut self, mode: Mode) -> Vec<(usize, std::time::Duration)>;
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct BenchmarkOutput {
+    pub name: String,
+    raw_average: u64,
+    pub average: u64,
+    pub sd: f64,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct BenchmarkSeriesOutput {
+    // TODO: make private again
+    pub name: String,
+    pub raw: Vec<Vec<(usize, std::time::Duration)>>,
+    pub average: Vec<(usize, u128)>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Mode {
+    Regular,
+    Profile,
+}
+
+impl std::str::FromStr for Mode {
+    type Err = &'static str;
+    fn from_str(day: &str) -> Result<Self, Self::Err> {
+        match day {
+            "regular" => Ok(Mode::Regular),
+            "profile" => Ok(Mode::Profile),
+            _ => Err("Could not parse mode"),
+        }
+    }
+}
+
+fn calc_average(vecs: &[Vec<(usize, std::time::Duration)>]) -> Vec<(usize, u128)> {
+    let mut avg = Vec::new();
+    for i in 0..vecs[0].len() {
+        avg.push((
+            vecs[0][i].0,
+            vecs.iter()
+                .map(|v| v[i])
+                .map(|(_, t)| t.as_micros())
+                .sum::<u128>()
+                / vecs.len() as u128,
+        ));
+    }
+
+    avg
+}
+
+pub fn run_benchmark(benchmark: Box<dyn BenchmarkDescription>, mode: Mode) -> BenchmarkOutput {
+    let name = benchmark.name().to_owned();
+    let mut benchmark = benchmark.setup();
+
+    let mut durations = Vec::new();
+    for _ in 0..50 {
+        let duration = benchmark.run(mode);
+        let duration_len = duration.len();
+        durations.push(
+            (duration.iter().map(|(_, d)| d).sum::<std::time::Duration>() / duration_len as u32)
+                .as_micros(),
+        );
+    }
+
+    durations.sort_unstable();
+
+    let raw_average = (durations.iter().sum::<u128>() / (durations.len() as u128)) as u64;
+    let average = (durations.iter().skip(10).take(30).sum::<u128>() / 30) as u64;
+    let avg: i32 = average.try_into().unwrap();
+    let sd: f64 = durations
+        .into_iter()
+        .skip(10)
+        .take(30)
+        .map(|d| d.try_into().unwrap())
+        .map(|d: i32| d - avg)
+        .map(|x: i32| x * x)
+        .map::<u32, _>(|x: i32| x.try_into().unwrap())
+        .sum::<u32>()
+        .into();
+
+    let sd = sd.sqrt() / 30.0_f64.sqrt();
+
+    BenchmarkOutput {
+        name: name.into(),
+        raw_average,
+        average,
+        sd,
+    }
+}
+
+pub fn run_series_benchmark(
+    benchmark: Box<dyn BenchmarkDescription>,
+    mode: Mode,
+) -> BenchmarkSeriesOutput {
+    let name = benchmark.name().to_owned();
+    let mut benchmark = benchmark.setup();
+
+    let mut durations = Vec::new();
+    for _ in 0..50 {
+        let duration = benchmark.run(mode);
+        durations.push(duration);
+    }
+
+    durations.sort();
+    let average = calc_average(&durations);
+
+    BenchmarkSeriesOutput {
+        name: name.into(),
+        raw: durations,
+        average,
+    }
+}

+ 70 - 0
analyses/bench/src/generator.rs

@@ -0,0 +1,70 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use std::{collections::HashMap, sync::Arc};
+
+use kvdb::KeyValueDB;
+use node_primitives::Hash;
+use sp_trie::{trie_types::TrieDBMut, TrieMut};
+
+use crate::simple_trie::SimpleTrie;
+
+/// Generate trie from given `key_values`.
+///
+/// Will fill your database `db` with trie data from `key_values` and
+/// return root.
+pub fn generate_trie(
+    db: Arc<dyn KeyValueDB>,
+    key_values: impl IntoIterator<Item = (Vec<u8>, Vec<u8>)>,
+) -> Hash {
+    let mut root = Hash::default();
+
+    let (db, overlay) = {
+        let mut overlay = HashMap::new();
+        overlay.insert(
+            hex::decode("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314")
+                .expect("null key is valid"),
+            Some(vec![0]),
+        );
+        let mut trie = SimpleTrie {
+            db,
+            overlay: &mut overlay,
+        };
+        {
+            let mut trie_db = TrieDBMut::new(&mut trie, &mut root);
+
+            for (key, value) in key_values {
+                trie_db.insert(&key, &value).expect("trie insertion failed");
+            }
+
+            trie_db.commit();
+        }
+        (trie.db, overlay)
+    };
+
+    let mut transaction = db.transaction();
+    for (key, value) in overlay.into_iter() {
+        match value {
+            Some(value) => transaction.put(0, &key[..], &value[..]),
+            None => transaction.delete(0, &key[..]),
+        }
+    }
+    db.write(transaction).expect("Failed to write transaction");
+
+    root
+}

+ 204 - 0
analyses/bench/src/main.rs

@@ -0,0 +1,204 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+mod common;
+#[macro_use]
+mod core;
+mod generator;
+mod plots;
+mod simple_trie;
+mod state_sizes;
+mod tempdb;
+mod trie;
+mod trie_series;
+
+use std::convert::TryFrom;
+
+#[cfg(feature = "plot")]
+use crate::plots::*;
+
+use structopt::StructOpt;
+
+use crate::{
+    common::DatabaseSize,
+    core::{run_benchmark, run_series_benchmark, BenchmarkDescription, Mode as BenchmarkMode},
+    tempdb::DatabaseType,
+    trie::{TrieReadBenchmarkDescription, TrieWriteBenchmarkDescription},
+    trie_series::{TrieReadSeriesBenchmarkDescription, TrieWriteSeriesBenchmarkDescription},
+};
+
+use linregress::{FormulaRegressionBuilder, RegressionDataBuilder, RegressionModel};
+
+#[derive(Debug, StructOpt)]
+#[structopt(name = "node-bench", about = "Node integration benchmarks")]
+struct Opt {
+    /// Machine readable json output.
+    ///
+    /// This also suppresses all regular output (except to stderr)
+    #[structopt(short, long)]
+    json: bool,
+
+    /// Database size.
+    ///
+    /// Size of the Database to use
+    size: DatabaseSize,
+
+    /// Number of transactions for block import with `custom` size.
+    #[structopt(long)]
+    transactions: Option<usize>,
+
+    /// Mode
+    ///
+    /// "regular" for regular benchmark
+    ///
+    /// "profile" mode adds pauses between measurable runs,
+    /// so that actual interval can be selected in the profiler of choice.
+    #[structopt(short, long, default_value = "regular")]
+    mode: BenchmarkMode,
+}
+
+fn main() {
+    let opt = Opt::from_args();
+
+    if !opt.json {
+        sp_tracing::try_init_simple();
+    }
+
+    let database_size = opt.size;
+
+    let series_benchmarks: Vec<Box<dyn BenchmarkDescription>> = vec![
+        Box::new(TrieReadSeriesBenchmarkDescription {
+            database_size,
+            database_type: DatabaseType::RocksDb,
+            max_sample_size: 200_000,
+        }),
+        Box::new(TrieWriteSeriesBenchmarkDescription {
+            database_size,
+            database_type: DatabaseType::RocksDb,
+            max_sample_size: 200_000,
+        }),
+    ];
+
+    let benchmarks: Vec<Box<dyn BenchmarkDescription>> = vec![
+        Box::new(TrieReadBenchmarkDescription {
+            database_size,
+            database_type: DatabaseType::RocksDb,
+        }),
+        Box::new(TrieWriteBenchmarkDescription {
+            database_size,
+            database_type: DatabaseType::RocksDb,
+        }),
+    ];
+
+    let mut series_results = Vec::new();
+    let bench_total_time = std::time::Instant::now();
+    let bench_time = std::time::Instant::now();
+    for benchmark in series_benchmarks {
+        log::info!("Starting {}", benchmark.name());
+        series_results.push(run_series_benchmark(benchmark, opt.mode));
+    }
+
+    log::info!("Bench series time: {}ms", bench_time.elapsed().as_millis());
+
+    let mut results = Vec::new();
+
+    let bench_time = std::time::Instant::now();
+    for benchmark in benchmarks {
+        log::info!("Starting {}", benchmark.name());
+        results.push(run_benchmark(benchmark, opt.mode));
+    }
+
+    log::info!(
+        "Bench non-series time: {}ms",
+        bench_time.elapsed().as_millis()
+    );
+
+    log::info!(
+        "Bench total time: {}ms",
+        bench_total_time.elapsed().as_millis()
+    );
+
+    let plot_time = std::time::Instant::now();
+
+    for (i, series_result) in series_results.iter().enumerate() {
+        let model = fit_model(series_result.raw.clone());
+        println!(
+            "Standard benchmark for {} took {}µs +/- {}µs ",
+            results[i].name, results[i].average, results[i].sd
+        );
+
+        println!(
+            "Our model benchmark for {} is: y = a * x + b",
+            series_result.name
+        );
+
+        println!("with a = {}", model.parameters.pairs()[0].1,);
+
+        println!("with b = {}", model.parameters.intercept_value,);
+
+        println!("and R-Squared: {}", model.rsquared);
+
+        println!(
+            "The time for a read of 120kb is (average benchmarked time no-prediction): {}µs",
+            series_result
+                .average
+                .iter()
+                .filter(|(x, _)| 120_000 <= *x && *x <= 121_000)
+                .last()
+                .unwrap()
+                .1
+        );
+
+        #[cfg(feature = "plot")]
+        plot_points(
+            series_result.name.clone(),
+            series_result.raw.clone(),
+            results[i].average,
+            results[i].sd,
+            model,
+        );
+    }
+
+    log::info!("Plot time: {}ms", plot_time.elapsed().as_millis());
+
+    let plot_time = std::time::Instant::now();
+
+    #[cfg(feature = "plot")]
+    plot_hist();
+
+    log::info!("Plot time: {}ms", plot_time.elapsed().as_millis());
+}
+
+fn fit_model(series: Vec<Vec<(usize, std::time::Duration)>>) -> RegressionModel {
+    let (x, y): (Vec<f64>, Vec<f64>) = series
+        .iter()
+        .flatten()
+        .map(|(x, d)| (*x, d.as_micros()))
+        .map(|(x, y)| (u32::try_from(x).unwrap(), u32::try_from(y).unwrap()))
+        .map(|(x, y)| (f64::from(x), f64::from(y)))
+        .unzip();
+    let data = vec![("Y", y), ("X", x)];
+    let formula = "Y ~ X";
+    let data = RegressionDataBuilder::new().build_from(data).unwrap();
+
+    FormulaRegressionBuilder::new()
+        .data(&data)
+        .formula(formula)
+        .fit()
+        .expect("Can't fit data to linear model")
+}

+ 167 - 0
analyses/bench/src/plots.rs

@@ -0,0 +1,167 @@
+#![cfg(feature = "plot")]
+use crate::state_sizes::KUSAMA_STATE_DISTRIBUTION;
+use linregress::RegressionModel;
+use plotters::prelude::*;
+use std::convert::TryFrom;
+
+pub fn plot_points(
+    name: String,
+    series: Vec<Vec<(usize, std::time::Duration)>>,
+    bench_avg: u64,
+    sd: f64,
+    model: RegressionModel,
+) {
+    let open_image_time = std::time::Instant::now();
+    let plot_name = format!("{}.png", name);
+    let root = BitMapBackend::new(&plot_name, (1920, 1080)).into_drawing_area();
+
+    root.fill(&WHITE).unwrap();
+
+    log::info!(
+        "Open image time: {}ms",
+        open_image_time.elapsed().as_millis()
+    );
+
+    let get_limits_time = std::time::Instant::now();
+    let series_iter = series
+        .into_iter()
+        .flatten()
+        .map(|(size, val)| (size, f64::from(u32::try_from(val.as_micros()).unwrap())));
+
+    let max_x = series_iter
+        .clone()
+        .map(|(x, _)| x)
+        .max()
+        .expect("Series shouldn't be empty");
+
+    let max_y = series_iter
+        .clone()
+        .map(|(_, y)| y)
+        .max_by(|x, y| x.partial_cmp(y).expect("No value should be None"))
+        .expect("Series shouldn't be empty");
+
+    log::info!(
+        "Get limits time: {}ms",
+        get_limits_time.elapsed().as_millis()
+    );
+
+    let max_avg_y = f64::from(u32::try_from(bench_avg).unwrap()) + sd;
+    let max_y = max_avg_y.max(max_y);
+
+    let create_chart_time = std::time::Instant::now();
+    let x_range = 0usize..max_x;
+
+    let mut chart = ChartBuilder::on(&root)
+        .caption(name, ("sans-serif", 30))
+        .margin(40)
+        .y_label_area_size(100)
+        .x_label_area_size(80)
+        .build_cartesian_2d(x_range.clone(), 0f64..max_y)
+        .unwrap();
+
+    log::info!(
+        "Create chart time: {}ms",
+        create_chart_time.elapsed().as_millis()
+    );
+
+    let prepare_graph_time = std::time::Instant::now();
+    chart
+        .configure_mesh()
+        .x_labels(30)
+        .y_desc("Time(ms)")
+        .x_desc("Entry size(B)")
+        .axis_desc_style(("sans-serif", 25))
+        .x_label_style(("sans-serif", 25))
+        .y_label_style(("sans-serif", 25))
+        .draw()
+        .unwrap();
+
+    log::info!(
+        "Prepare time: {}ms",
+        prepare_graph_time.elapsed().as_millis()
+    );
+
+    let graph_time = std::time::Instant::now();
+
+    chart
+        .draw_series(series_iter.map(|point| Circle::new(point, 5, &BLUE)))
+        .unwrap()
+        .label("Raw Benchmark Data")
+        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &BLUE));
+
+    chart
+        .draw_series(vec![Rectangle::new(
+            [
+                (0, (f64::from(u32::try_from(bench_avg).unwrap()) + sd)),
+                (max_x, (f64::from(u32::try_from(bench_avg).unwrap()) - sd)),
+            ],
+            GREEN.mix(0.5).filled(),
+        )])
+        .unwrap()
+        .label("Standard Benchmark data")
+        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &GREEN));
+
+    let x_range_f64 = x_range
+        .clone()
+        .map(|x| f64::from(u32::try_from(x).unwrap()));
+    chart
+        .draw_series(LineSeries::new(
+            x_range.zip(model.predict(vec![("X", x_range_f64.collect())]).unwrap()),
+            &RED,
+        ))
+        .unwrap()
+        .label("Linear regression")
+        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED));
+
+    chart
+        .configure_series_labels()
+        .background_style(WHITE.filled())
+        .label_font(("sans-serif", 25))
+        .position(SeriesLabelPosition::UpperLeft)
+        .draw()
+        .unwrap();
+
+    log::info!("Graph time: {}ms", graph_time.elapsed().as_millis());
+}
+
+pub fn plot_hist() {
+    let root = BitMapBackend::new("state_dist.png", (1920, 1080)).into_drawing_area();
+
+    root.fill(&WHITE).unwrap();
+
+    let max_count = KUSAMA_STATE_DISTRIBUTION
+        .iter()
+        .map(|(_, y)| y)
+        .max()
+        .unwrap();
+
+    let mut chart = ChartBuilder::on(&root)
+        .x_label_area_size(70)
+        .y_label_area_size(150)
+        .margin(80)
+        .caption("State distribution", ("sans-serif", 50))
+        .build_cartesian_2d((0u32..9000u32).into_segmented(), 0u32..*max_count)
+        .unwrap();
+
+    chart
+        .configure_mesh()
+        .disable_x_mesh()
+        .bold_line_style(&WHITE.mix(0.3))
+        .y_desc("Count")
+        .x_desc("Size(B)")
+        .x_labels(20)
+        .x_label_style(("sans-serif", 25))
+        .y_label_style(("sans-serif", 25))
+        .axis_desc_style(("sans-serif", 25))
+        .draw()
+        .unwrap();
+
+    chart
+        .draw_series(
+            Histogram::vertical(&chart)
+                .margin(1)
+                .style(RED.mix(0.5).filled())
+                .data(KUSAMA_STATE_DISTRIBUTION.iter().map(|(x, y)| (*x, *y))),
+        )
+        .unwrap();
+}

+ 72 - 0
analyses/bench/src/simple_trie.rs

@@ -0,0 +1,72 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use std::{collections::HashMap, sync::Arc};
+
+use hash_db::{AsHashDB, HashDB, Hasher as _, Prefix};
+use kvdb::KeyValueDB;
+use node_primitives::Hash;
+use sp_trie::DBValue;
+
+pub type Hasher = sp_core::Blake2Hasher;
+
+/// Immutable generated trie database with root.
+pub struct SimpleTrie<'a> {
+    pub db: Arc<dyn KeyValueDB>,
+    pub overlay: &'a mut HashMap<Vec<u8>, Option<Vec<u8>>>,
+}
+
+impl<'a> AsHashDB<Hasher, DBValue> for SimpleTrie<'a> {
+    fn as_hash_db(&self) -> &dyn hash_db::HashDB<Hasher, DBValue> {
+        &*self
+    }
+
+    fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn HashDB<Hasher, DBValue> + 'b) {
+        &mut *self
+    }
+}
+
+impl<'a> HashDB<Hasher, DBValue> for SimpleTrie<'a> {
+    fn get(&self, key: &Hash, prefix: Prefix) -> Option<DBValue> {
+        let key = sp_trie::prefixed_key::<Hasher>(key, prefix);
+        if let Some(value) = self.overlay.get(&key) {
+            return value.clone();
+        }
+        self.db.get(0, &key).expect("Database backend error")
+    }
+
+    fn contains(&self, hash: &Hash, prefix: Prefix) -> bool {
+        self.get(hash, prefix).is_some()
+    }
+
+    fn insert(&mut self, prefix: Prefix, value: &[u8]) -> Hash {
+        let key = Hasher::hash(value);
+        self.emplace(key, prefix, value.to_vec());
+        key
+    }
+
+    fn emplace(&mut self, key: Hash, prefix: Prefix, value: DBValue) {
+        let key = sp_trie::prefixed_key::<Hasher>(&key, prefix);
+        self.overlay.insert(key, Some(value));
+    }
+
+    fn remove(&mut self, key: &Hash, prefix: Prefix) {
+        let key = sp_trie::prefixed_key::<Hasher>(key, prefix);
+        self.overlay.insert(key, None);
+    }
+}

+ 4758 - 0
analyses/bench/src/state_sizes.rs

@@ -0,0 +1,4758 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+/// Kusama value size distribution
+pub const KUSAMA_STATE_DISTRIBUTION: &[(u32, u32)] = &[
+    (32, 35),
+    (33, 20035),
+    (34, 5369),
+    (35, 184),
+    (36, 54599),
+    (37, 1515056),
+    (38, 885),
+    (39, 69965),
+    (41, 210754),
+    (42, 467),
+    (43, 3241),
+    (44, 32660),
+    (45, 231141),
+    (46, 220016),
+    (47, 248931),
+    (48, 157232),
+    (49, 143236),
+    (50, 2428),
+    (51, 1476159),
+    (52, 31),
+    (53, 112),
+    (54, 711),
+    (55, 1934),
+    (56, 39),
+    (57, 407),
+    (58, 6929),
+    (59, 6568),
+    (60, 26),
+    (61, 268673),
+    (62, 118137),
+    (63, 84640),
+    (64, 193232),
+    (65, 2584210),
+    (66, 1002),
+    (67, 2993),
+    (68, 4266),
+    (69, 5633381),
+    (70, 277369),
+    (71, 5106),
+    (72, 722),
+    (73, 1882),
+    (74, 8178),
+    (75, 4045),
+    (76, 1596),
+    (77, 5335),
+    (78, 14591),
+    (79, 9645),
+    (80, 44171),
+    (81, 13474),
+    (82, 51090),
+    (83, 2595),
+    (84, 6276),
+    (85, 382195),
+    (86, 1062),
+    (87, 3846),
+    (88, 5663),
+    (89, 3811),
+    (90, 1580),
+    (91, 5729),
+    (92, 19144),
+    (93, 197),
+    (94, 235),
+    (95, 545),
+    (96, 54914),
+    (97, 3858),
+    (98, 1610),
+    (99, 635),
+    (100, 2481),
+    (101, 6457),
+    (102, 3753951),
+    (103, 11821),
+    (104, 11114),
+    (105, 2601),
+    (106, 2518),
+    (107, 521925),
+    (108, 297),
+    (109, 411),
+    (110, 668),
+    (111, 4500),
+    (112, 704),
+    (113, 316),
+    (114, 59),
+    (115, 291),
+    (116, 1727),
+    (117, 6010),
+    (118, 51874),
+    (119, 13969),
+    (120, 9496),
+    (121, 274),
+    (122, 810),
+    (123, 643),
+    (124, 69),
+    (125, 41),
+    (126, 329),
+    (127, 175435),
+    (128, 2641),
+    (129, 2658),
+    (130, 415277),
+    (131, 2705),
+    (132, 2314),
+    (133, 4290),
+    (134, 693),
+    (135, 1957478),
+    (136, 1111),
+    (137, 1474503),
+    (138, 3656),
+    (139, 940),
+    (140, 1755692),
+    (141, 61),
+    (142, 4140),
+    (143, 47),
+    (144, 6725),
+    (145, 610),
+    (146, 250),
+    (147, 48),
+    (148, 28),
+    (149, 132),
+    (150, 123489),
+    (151, 7476),
+    (152, 55),
+    (153, 68),
+    (154, 170),
+    (155, 566),
+    (156, 8110),
+    (157, 1243),
+    (158, 1445),
+    (159, 2569),
+    (160, 1096),
+    (161, 865),
+    (162, 634),
+    (163, 372411),
+    (164, 685),
+    (165, 3481),
+    (166, 1467),
+    (167, 2146),
+    (168, 556539),
+    (169, 566),
+    (170, 5080),
+    (171, 202),
+    (172, 123),
+    (173, 100750),
+    (174, 667),
+    (175, 433),
+    (176, 737),
+    (177, 315),
+    (178, 317),
+    (179, 656),
+    (180, 2522),
+    (181, 315),
+    (182, 406),
+    (183, 4680),
+    (184, 4941),
+    (185, 828),
+    (186, 782),
+    (187, 565),
+    (188, 584),
+    (189, 376),
+    (190, 321),
+    (191, 418),
+    (192, 167),
+    (193, 362),
+    (194, 2198),
+    (195, 180),
+    (196, 787),
+    (197, 2680),
+    (198, 501),
+    (199, 843),
+    (200, 287),
+    (201, 608362),
+    (202, 1157),
+    (203, 959),
+    (204, 1683623),
+    (205, 440),
+    (206, 756),
+    (207, 812),
+    (208, 1147),
+    (209, 723),
+    (210, 856),
+    (211, 496),
+    (212, 916),
+    (213, 615),
+    (214, 488),
+    (215, 522),
+    (216, 8265),
+    (217, 32574),
+    (218, 417),
+    (219, 247),
+    (220, 579),
+    (221, 68),
+    (222, 126),
+    (223, 306),
+    (224, 310),
+    (225, 24),
+    (226, 37),
+    (227, 160),
+    (228, 11),
+    (229, 3288),
+    (230, 349),
+    (231, 23),
+    (232, 14),
+    (233, 45),
+    (234, 452840),
+    (235, 118),
+    (236, 741),
+    (237, 390),
+    (238, 517),
+    (239, 694),
+    (240, 765),
+    (241, 542),
+    (242, 417),
+    (243, 617),
+    (244, 1307),
+    (245, 583),
+    (246, 1640),
+    (247, 735),
+    (248, 478),
+    (249, 4312),
+    (250, 5426),
+    (251, 1067),
+    (252, 435),
+    (253, 202),
+    (254, 122),
+    (255, 486),
+    (256, 180),
+    (257, 279),
+    (258, 406),
+    (259, 160),
+    (260, 2759),
+    (261, 2600),
+    (262, 686),
+    (263, 95),
+    (264, 164),
+    (265, 150),
+    (266, 1013),
+    (267, 552618),
+    (268, 217),
+    (269, 188),
+    (270, 284),
+    (271, 416),
+    (272, 453),
+    (273, 95),
+    (274, 42),
+    (275, 68),
+    (276, 90),
+    (277, 123),
+    (278, 340),
+    (279, 98),
+    (280, 2795),
+    (281, 261),
+    (282, 7370),
+    (283, 5768),
+    (284, 3285),
+    (285, 461),
+    (286, 363),
+    (287, 456),
+    (288, 1475),
+    (289, 211),
+    (290, 153),
+    (291, 282),
+    (292, 241),
+    (293, 2924),
+    (294, 261),
+    (295, 1070),
+    (296, 1301),
+    (297, 688),
+    (298, 592),
+    (299, 95),
+    (300, 686447),
+    (301, 42),
+    (302, 385),
+    (303, 24),
+    (304, 931),
+    (305, 49),
+    (306, 23),
+    (307, 67),
+    (308, 32),
+    (309, 38),
+    (310, 2),
+    (311, 7),
+    (312, 198),
+    (313, 11),
+    (314, 38),
+    (315, 3704),
+    (316, 7406),
+    (317, 116),
+    (318, 229),
+    (319, 100),
+    (320, 437),
+    (321, 244),
+    (322, 285),
+    (323, 433),
+    (324, 382),
+    (325, 3171),
+    (326, 761),
+    (327, 324),
+    (328, 2264),
+    (329, 340),
+    (330, 353),
+    (331, 110),
+    (332, 403),
+    (333, 731366),
+    (334, 223),
+    (335, 350),
+    (336, 600),
+    (337, 219),
+    (338, 112),
+    (339, 10),
+    (340, 761),
+    (341, 35),
+    (342, 99),
+    (343, 83),
+    (344, 136),
+    (345, 7),
+    (346, 836),
+    (347, 11),
+    (348, 10832),
+    (349, 8931),
+    (350, 33),
+    (351, 64),
+    (352, 66),
+    (353, 54),
+    (354, 78),
+    (355, 198),
+    (356, 722),
+    (357, 2647),
+    (358, 64),
+    (359, 71),
+    (360, 2242),
+    (361, 1462),
+    (362, 505),
+    (363, 444),
+    (364, 597),
+    (365, 372),
+    (366, 664852),
+    (367, 464),
+    (368, 605),
+    (369, 123),
+    (370, 64),
+    (371, 117),
+    (372, 328),
+    (373, 123),
+    (374, 227),
+    (375, 151),
+    (376, 881),
+    (377, 111),
+    (378, 30),
+    (379, 73),
+    (380, 2126),
+    (381, 3662),
+    (382, 9107),
+    (383, 18),
+    (384, 294),
+    (385, 12),
+    (386, 262),
+    (387, 127),
+    (388, 269),
+    (389, 2566),
+    (390, 14),
+    (391, 17),
+    (392, 80),
+    (393, 67),
+    (394, 1470),
+    (395, 25),
+    (396, 220),
+    (397, 131),
+    (398, 225),
+    (399, 484755),
+    (400, 597),
+    (401, 300),
+    (402, 253),
+    (403, 359),
+    (404, 523),
+    (405, 311),
+    (406, 238),
+    (407, 999),
+    (408, 424),
+    (409, 165),
+    (410, 96),
+    (411, 248),
+    (412, 1771),
+    (413, 139),
+    (414, 7374),
+    (415, 11186),
+    (416, 1355),
+    (417, 1283666),
+    (418, 9),
+    (419, 116),
+    (420, 3897),
+    (421, 2554),
+    (422, 1),
+    (423, 1),
+    (424, 16878),
+    (425, 3198212),
+    (426, 335),
+    (427, 1676),
+    (428, 80),
+    (429, 19),
+    (430, 47),
+    (431, 495),
+    (432, 421946),
+    (433, 73),
+    (434, 95),
+    (435, 105),
+    (436, 184),
+    (437, 56903),
+    (438, 132),
+    (439, 87),
+    (440, 207411),
+    (441, 230),
+    (442, 372),
+    (443, 361),
+    (444, 387),
+    (445, 299),
+    (446, 175),
+    (447, 7487),
+    (448, 16346),
+    (449, 37),
+    (450, 98313),
+    (451, 307),
+    (452, 304),
+    (453, 2675),
+    (454, 229),
+    (455, 130),
+    (456, 134),
+    (457, 50),
+    (458, 238),
+    (459, 2),
+    (460, 2267),
+    (461, 7),
+    (462, 1),
+    (463, 8),
+    (464, 395),
+    (465, 1279781),
+    (466, 9),
+    (467, 12),
+    (468, 633),
+    (469, 37),
+    (470, 13),
+    (471, 54),
+    (472, 247),
+    (473, 82),
+    (474, 119),
+    (475, 114),
+    (476, 332),
+    (477, 79),
+    (478, 116),
+    (479, 128),
+    (480, 4206),
+    (481, 20732),
+    (482, 311),
+    (483, 343),
+    (484, 527),
+    (485, 2750),
+    (486, 76),
+    (487, 152),
+    (488, 510),
+    (489, 63),
+    (490, 257),
+    (491, 79),
+    (492, 825),
+    (493, 4198),
+    (494, 389),
+    (495, 72),
+    (496, 1547),
+    (497, 34),
+    (498, 631996),
+    (499, 5),
+    (500, 2334),
+    (501, 34),
+    (502, 7),
+    (503, 7),
+    (504, 7682),
+    (505, 6),
+    (506, 26),
+    (507, 22),
+    (508, 461),
+    (509, 95),
+    (510, 36),
+    (511, 46),
+    (512, 2741),
+    (513, 38455),
+    (514, 29678),
+    (515, 179),
+    (516, 1637),
+    (517, 2597),
+    (518, 166),
+    (519, 230),
+    (520, 2736),
+    (521, 187),
+    (522, 361),
+    (523, 310),
+    (524, 3327),
+    (525, 76),
+    (526, 8070),
+    (527, 35),
+    (528, 3310),
+    (529, 118),
+    (530, 167),
+    (531, 214180),
+    (532, 4597),
+    (533, 153),
+    (534, 126),
+    (535, 23),
+    (536, 13920),
+    (537, 10),
+    (538, 11),
+    (539, 50),
+    (540, 50739),
+    (541, 8),
+    (542, 347),
+    (543, 77),
+    (544, 451575),
+    (545, 16),
+    (546, 218814),
+    (547, 1859026),
+    (548, 303),
+    (549, 2511),
+    (550, 27),
+    (551, 28),
+    (552, 188),
+    (553, 46),
+    (554, 216),
+    (555, 63),
+    (556, 202),
+    (557, 192),
+    (558, 257),
+    (559, 170377),
+    (560, 902),
+    (561, 424),
+    (562, 186),
+    (563, 145),
+    (564, 342),
+    (565, 76),
+    (566, 41),
+    (567, 26),
+    (568, 136),
+    (569, 1336),
+    (570, 988),
+    (571, 131),
+    (572, 766),
+    (573, 95),
+    (574, 57),
+    (575, 16),
+    (576, 47),
+    (577, 63),
+    (578, 5),
+    (579, 140),
+    (580, 1263808),
+    (581, 2498),
+    (583, 2),
+    (584, 706),
+    (585, 49),
+    (586, 502),
+    (587, 16),
+    (588, 115),
+    (589, 25),
+    (590, 31),
+    (591, 34),
+    (592, 818),
+    (593, 60),
+    (594, 84),
+    (595, 116),
+    (596, 446),
+    (597, 111),
+    (598, 151),
+    (599, 153),
+    (600, 1408),
+    (601, 165),
+    (602, 575),
+    (603, 163),
+    (604, 309),
+    (605, 52),
+    (606, 40),
+    (607, 116),
+    (608, 749),
+    (609, 231),
+    (610, 171),
+    (611, 218),
+    (612, 1145),
+    (613, 2572),
+    (614, 27),
+    (615, 26),
+    (616, 2060),
+    (617, 173),
+    (618, 1094),
+    (619, 66),
+    (620, 14235),
+    (622, 294),
+    (623, 2),
+    (624, 79374),
+    (625, 1),
+    (626, 3),
+    (627, 7),
+    (628, 335),
+    (629, 27),
+    (630, 47),
+    (631, 113),
+    (632, 589),
+    (633, 56),
+    (634, 75),
+    (635, 85),
+    (636, 740),
+    (637, 118),
+    (638, 180),
+    (639, 149),
+    (640, 1169),
+    (641, 135),
+    (642, 169),
+    (643, 170),
+    (644, 1802),
+    (645, 2481),
+    (646, 28),
+    (647, 78),
+    (648, 5585),
+    (649, 173),
+    (650, 135),
+    (651, 177),
+    (652, 6553),
+    (653, 129),
+    (654, 55),
+    (655, 6),
+    (656, 13250),
+    (657, 5),
+    (658, 15),
+    (659, 3),
+    (660, 39892),
+    (661, 28),
+    (663, 1),
+    (664, 575061),
+    (665, 1),
+    (666, 5),
+    (667, 73),
+    (668, 39),
+    (669, 62),
+    (670, 50),
+    (671, 27),
+    (672, 33),
+    (673, 48),
+    (674, 44),
+    (675, 151),
+    (676, 70),
+    (677, 2540),
+    (678, 150),
+    (679, 109),
+    (680, 117),
+    (681, 95),
+    (682, 80),
+    (683, 44),
+    (684, 34),
+    (685, 31),
+    (686, 125),
+    (687, 146),
+    (688, 423),
+    (689, 142),
+    (690, 154),
+    (691, 135),
+    (692, 194),
+    (693, 48),
+    (694, 6),
+    (695, 141),
+    (696, 47),
+    (697, 9),
+    (699, 1),
+    (701, 1),
+    (702, 2),
+    (703, 81),
+    (704, 3),
+    (705, 4),
+    (706, 23),
+    (707, 131),
+    (708, 31),
+    (709, 2458),
+    (710, 346),
+    (711, 43),
+    (712, 46),
+    (713, 48),
+    (714, 85),
+    (715, 119),
+    (716, 89),
+    (717, 97),
+    (718, 95),
+    (719, 137),
+    (720, 437),
+    (721, 64),
+    (722, 28),
+    (723, 29),
+    (724, 121),
+    (725, 162),
+    (726, 241),
+    (727, 219),
+    (728, 143),
+    (729, 92),
+    (730, 100),
+    (731, 42),
+    (732, 38),
+    (733, 60),
+    (734, 2),
+    (735, 71),
+    (736, 12),
+    (737, 9),
+    (738, 7),
+    (739, 193),
+    (740, 2),
+    (741, 2404),
+    (742, 3),
+    (743, 11),
+    (744, 5),
+    (745, 5),
+    (746, 9),
+    (747, 16),
+    (748, 27),
+    (749, 32),
+    (750, 57),
+    (751, 54),
+    (752, 383),
+    (753, 61),
+    (754, 48),
+    (755, 84),
+    (756, 108),
+    (757, 134),
+    (758, 121),
+    (759, 160),
+    (760, 80),
+    (761, 68),
+    (762, 192),
+    (763, 107),
+    (764, 270),
+    (765, 58),
+    (766, 125),
+    (767, 151),
+    (768, 75),
+    (769, 94),
+    (770, 91),
+    (771, 187),
+    (772, 57),
+    (773, 2371),
+    (774, 8),
+    (775, 93),
+    (776, 107),
+    (777, 20),
+    (779, 1),
+    (780, 22),
+    (781, 1),
+    (783, 6),
+    (784, 318),
+    (785, 25),
+    (786, 31),
+    (787, 23),
+    (788, 28),
+    (789, 62),
+    (790, 53),
+    (791, 41),
+    (792, 68),
+    (793, 60),
+    (794, 88),
+    (795, 108),
+    (796, 63),
+    (797, 100),
+    (798, 68),
+    (799, 72),
+    (800, 83),
+    (801, 46),
+    (802, 36),
+    (803, 157),
+    (804, 139),
+    (805, 2439),
+    (806, 73),
+    (807, 81),
+    (808, 99),
+    (809, 66),
+    (810, 45),
+    (811, 98),
+    (812, 1),
+    (814, 31),
+    (815, 1),
+    (816, 312),
+    (818, 155),
+    (819, 2),
+    (820, 12),
+    (821, 27),
+    (822, 97),
+    (823, 23),
+    (824, 7),
+    (825, 15),
+    (826, 37),
+    (827, 39),
+    (828, 28),
+    (829, 33),
+    (830, 53),
+    (831, 101),
+    (832, 189),
+    (833, 94),
+    (834, 66),
+    (835, 173),
+    (836, 74),
+    (837, 2402),
+    (838, 64),
+    (839, 28),
+    (840, 20),
+    (841, 13),
+    (842, 32),
+    (843, 72),
+    (844, 68),
+    (845, 50),
+    (846, 41),
+    (847, 114),
+    (848, 345),
+    (849, 33),
+    (850, 17),
+    (851, 6),
+    (852, 61),
+    (853, 101),
+    (854, 123),
+    (855, 28),
+    (856, 3),
+    (857, 3),
+    (858, 30),
+    (859, 12),
+    (860, 28),
+    (861, 16),
+    (862, 20),
+    (863, 7),
+    (864, 23),
+    (865, 28),
+    (866, 40),
+    (867, 159),
+    (868, 40),
+    (869, 2361),
+    (870, 92),
+    (871, 88),
+    (872, 193),
+    (873, 61),
+    (874, 58),
+    (875, 67),
+    (876, 65),
+    (877, 46),
+    (878, 55),
+    (879, 30),
+    (880, 334),
+    (881, 74),
+    (882, 121),
+    (883, 107),
+    (884, 36),
+    (885, 66),
+    (886, 22),
+    (887, 25),
+    (888, 24),
+    (889, 10),
+    (890, 44),
+    (891, 5),
+    (892, 84),
+    (893, 4),
+    (894, 1),
+    (895, 7),
+    (896, 3),
+    (897, 8),
+    (898, 3),
+    (899, 126),
+    (900, 13),
+    (901, 2280),
+    (902, 74),
+    (903, 36),
+    (904, 46),
+    (905, 52),
+    (906, 24),
+    (907, 23),
+    (908, 43),
+    (909, 31),
+    (910, 66),
+    (911, 65),
+    (912, 376),
+    (913, 77),
+    (914, 85),
+    (915, 60),
+    (916, 29),
+    (917, 64),
+    (918, 48),
+    (919, 135),
+    (920, 21),
+    (921, 34),
+    (922, 26),
+    (923, 22),
+    (924, 52),
+    (925, 28),
+    (926, 142),
+    (927, 18),
+    (928, 14),
+    (929, 30),
+    (930, 56),
+    (931, 113),
+    (933, 2264),
+    (934, 14),
+    (935, 4),
+    (936, 10),
+    (937, 18),
+    (938, 2),
+    (939, 30),
+    (940, 9),
+    (941, 29),
+    (942, 10),
+    (943, 17),
+    (944, 296),
+    (945, 31),
+    (946, 40),
+    (947, 26),
+    (948, 70),
+    (949, 66),
+    (950, 44),
+    (951, 57),
+    (952, 55),
+    (953, 56),
+    (954, 51),
+    (955, 133),
+    (956, 39),
+    (957, 49),
+    (958, 45),
+    (959, 26),
+    (960, 30),
+    (961, 35),
+    (962, 40),
+    (963, 148),
+    (964, 34),
+    (965, 2264),
+    (966, 50),
+    (967, 21),
+    (968, 2),
+    (970, 24),
+    (972, 45),
+    (973, 8),
+    (974, 11),
+    (975, 20),
+    (976, 287),
+    (977, 20),
+    (978, 6),
+    (979, 9),
+    (980, 99),
+    (981, 32),
+    (982, 10),
+    (983, 13),
+    (984, 26),
+    (985, 30),
+    (986, 31),
+    (987, 38),
+    (988, 25),
+    (989, 32),
+    (990, 44),
+    (991, 125),
+    (992, 58),
+    (993, 44),
+    (994, 25),
+    (995, 140),
+    (996, 25),
+    (997, 2222),
+    (998, 16),
+    (999, 25),
+    (1000, 38),
+    (1001, 66),
+    (1002, 31),
+    (1003, 38),
+    (1004, 38),
+    (1005, 10),
+    (1006, 7),
+    (1008, 283),
+    (1009, 3),
+    (1010, 1),
+    (1011, 17),
+    (1012, 4),
+    (1013, 51),
+    (1014, 1),
+    (1015, 1),
+    (1016, 3),
+    (1017, 12),
+    (1018, 11),
+    (1019, 21),
+    (1020, 31),
+    (1021, 14),
+    (1022, 14),
+    (1023, 23),
+    (1024, 25),
+    (1025, 42),
+    (1026, 39),
+    (1027, 220),
+    (1028, 33),
+    (1029, 2206),
+    (1030, 24),
+    (1031, 64),
+    (1032, 36),
+    (1033, 61),
+    (1034, 123),
+    (1035, 32),
+    (1036, 20),
+    (1037, 15),
+    (1038, 11),
+    (1039, 33),
+    (1040, 311),
+    (1041, 58),
+    (1042, 80),
+    (1043, 29),
+    (1044, 10),
+    (1045, 48),
+    (1046, 18),
+    (1047, 22),
+    (1048, 3),
+    (1049, 17),
+    (1050, 1),
+    (1051, 2),
+    (1052, 5),
+    (1053, 4),
+    (1054, 4),
+    (1055, 1),
+    (1056, 4),
+    (1057, 15),
+    (1058, 11),
+    (1059, 135),
+    (1060, 59),
+    (1061, 2132),
+    (1062, 32),
+    (1063, 116),
+    (1064, 37),
+    (1065, 44),
+    (1066, 42),
+    (1067, 28),
+    (1068, 10),
+    (1069, 36),
+    (1070, 59),
+    (1071, 48),
+    (1072, 332),
+    (1073, 59),
+    (1074, 43),
+    (1075, 19),
+    (1076, 19),
+    (1077, 31),
+    (1078, 31),
+    (1079, 20),
+    (1080, 38),
+    (1081, 58),
+    (1082, 37),
+    (1083, 47),
+    (1084, 19),
+    (1085, 24),
+    (1086, 12),
+    (1087, 26),
+    (1088, 89),
+    (1089, 3),
+    (1091, 108),
+    (1093, 2112),
+    (1094, 13),
+    (1095, 4),
+    (1096, 4),
+    (1097, 17),
+    (1098, 7),
+    (1099, 105),
+    (1100, 12),
+    (1101, 10),
+    (1102, 17),
+    (1103, 19),
+    (1104, 329),
+    (1105, 28),
+    (1106, 58),
+    (1107, 21),
+    (1108, 22),
+    (1109, 63),
+    (1110, 29),
+    (1111, 53),
+    (1112, 84),
+    (1113, 28),
+    (1114, 30),
+    (1115, 22),
+    (1116, 40),
+    (1117, 16),
+    (1118, 20),
+    (1119, 75),
+    (1120, 43),
+    (1121, 49),
+    (1122, 25),
+    (1123, 118),
+    (1124, 8),
+    (1125, 2083),
+    (1126, 21),
+    (1127, 3),
+    (1128, 43),
+    (1129, 1),
+    (1130, 1),
+    (1132, 3),
+    (1133, 1),
+    (1134, 3),
+    (1135, 83),
+    (1136, 266),
+    (1137, 7),
+    (1138, 22),
+    (1139, 14),
+    (1140, 30),
+    (1141, 54),
+    (1142, 125),
+    (1143, 44),
+    (1144, 34),
+    (1145, 19),
+    (1146, 21),
+    (1147, 19),
+    (1148, 46),
+    (1149, 45),
+    (1150, 54),
+    (1151, 22),
+    (1152, 30),
+    (1153, 20),
+    (1154, 7),
+    (1155, 143),
+    (1156, 23),
+    (1157, 2078),
+    (1158, 30),
+    (1159, 23),
+    (1160, 12),
+    (1161, 18),
+    (1162, 6),
+    (1164, 5),
+    (1165, 1),
+    (1168, 254),
+    (1169, 1),
+    (1170, 3),
+    (1171, 95),
+    (1172, 37),
+    (1173, 23),
+    (1174, 7),
+    (1175, 11),
+    (1176, 5),
+    (1177, 14),
+    (1178, 15),
+    (1179, 19),
+    (1180, 10),
+    (1181, 28),
+    (1182, 87),
+    (1183, 35),
+    (1184, 30),
+    (1185, 30),
+    (1186, 38),
+    (1187, 148),
+    (1188, 49),
+    (1189, 2056),
+    (1190, 42),
+    (1191, 41),
+    (1192, 14),
+    (1193, 36),
+    (1194, 37),
+    (1195, 22),
+    (1196, 108),
+    (1197, 62),
+    (1198, 55),
+    (1199, 43),
+    (1200, 261),
+    (1201, 16),
+    (1202, 1),
+    (1203, 9),
+    (1204, 3),
+    (1205, 32),
+    (1207, 81),
+    (1208, 3),
+    (1210, 3),
+    (1212, 4),
+    (1213, 9),
+    (1214, 5),
+    (1215, 6),
+    (1216, 4),
+    (1217, 8),
+    (1218, 13),
+    (1219, 120),
+    (1220, 11),
+    (1221, 1989),
+    (1222, 11),
+    (1223, 20),
+    (1224, 15),
+    (1225, 21),
+    (1226, 23),
+    (1227, 50),
+    (1228, 37),
+    (1229, 51),
+    (1230, 37),
+    (1231, 21),
+    (1232, 256),
+    (1233, 26),
+    (1234, 25),
+    (1235, 21),
+    (1236, 79),
+    (1237, 50),
+    (1238, 21),
+    (1239, 2),
+    (1240, 6),
+    (1241, 8),
+    (1243, 95),
+    (1244, 1),
+    (1247, 1),
+    (1248, 1),
+    (1249, 1),
+    (1250, 96),
+    (1251, 112),
+    (1252, 43),
+    (1253, 1960),
+    (1254, 7),
+    (1255, 13),
+    (1256, 16),
+    (1257, 20),
+    (1258, 19),
+    (1259, 17),
+    (1260, 12),
+    (1261, 5),
+    (1262, 12),
+    (1263, 29),
+    (1264, 272),
+    (1265, 63),
+    (1266, 37),
+    (1267, 36),
+    (1268, 25),
+    (1269, 55),
+    (1270, 38),
+    (1271, 7),
+    (1272, 37),
+    (1273, 10),
+    (1274, 16),
+    (1275, 28),
+    (1276, 18),
+    (1277, 11),
+    (1278, 8),
+    (1279, 91),
+    (1280, 1),
+    (1282, 1),
+    (1283, 110),
+    (1284, 20),
+    (1285, 1923),
+    (1287, 3),
+    (1288, 1),
+    (1290, 23),
+    (1291, 4),
+    (1292, 4),
+    (1293, 12),
+    (1294, 19),
+    (1295, 8),
+    (1296, 248),
+    (1297, 21),
+    (1298, 12),
+    (1299, 31),
+    (1300, 10),
+    (1301, 60),
+    (1302, 1),
+    (1303, 8),
+    (1304, 99),
+    (1305, 29),
+    (1306, 29),
+    (1307, 28),
+    (1308, 33),
+    (1309, 19),
+    (1310, 8),
+    (1311, 1),
+    (1313, 11),
+    (1314, 12),
+    (1315, 236),
+    (1316, 18),
+    (1317, 1891),
+    (1318, 2),
+    (1322, 21),
+    (1324, 1),
+    (1326, 8),
+    (1327, 3),
+    (1328, 235),
+    (1329, 4),
+    (1330, 1),
+    (1331, 2),
+    (1332, 5),
+    (1333, 38),
+    (1334, 2),
+    (1335, 30),
+    (1336, 18),
+    (1337, 31),
+    (1338, 8),
+    (1339, 5),
+    (1340, 11),
+    (1341, 9),
+    (1342, 12),
+    (1343, 11),
+    (1344, 79),
+    (1345, 37),
+    (1346, 19),
+    (1347, 136),
+    (1348, 9),
+    (1349, 1861),
+    (1350, 8),
+    (1351, 112),
+    (1352, 10),
+    (1353, 3),
+    (1354, 16),
+    (1355, 4),
+    (1356, 12),
+    (1357, 18),
+    (1358, 67),
+    (1359, 6),
+    (1360, 229),
+    (1361, 1),
+    (1362, 1),
+    (1364, 1),
+    (1365, 27),
+    (1366, 6),
+    (1368, 14),
+    (1370, 8),
+    (1371, 29),
+    (1372, 3),
+    (1373, 21),
+    (1374, 8),
+    (1375, 6),
+    (1376, 3),
+    (1377, 9),
+    (1378, 9),
+    (1379, 120),
+    (1380, 5),
+    (1381, 1833),
+    (1382, 45),
+    (1383, 35),
+    (1384, 23),
+    (1385, 25),
+    (1386, 26),
+    (1387, 159),
+    (1388, 24),
+    (1389, 16),
+    (1390, 16),
+    (1391, 14),
+    (1392, 273),
+    (1393, 17),
+    (1394, 9),
+    (1395, 5),
+    (1396, 14),
+    (1397, 24),
+    (1398, 27),
+    (1400, 2),
+    (1404, 5),
+    (1405, 8),
+    (1406, 3),
+    (1407, 25),
+    (1408, 2),
+    (1409, 22),
+    (1410, 10),
+    (1411, 111),
+    (1412, 89),
+    (1413, 1793),
+    (1414, 4),
+    (1415, 9),
+    (1416, 16),
+    (1417, 13),
+    (1418, 13),
+    (1419, 13),
+    (1420, 15),
+    (1421, 19),
+    (1422, 26),
+    (1423, 110),
+    (1424, 229),
+    (1425, 11),
+    (1426, 10),
+    (1427, 7),
+    (1428, 7),
+    (1429, 28),
+    (1430, 12),
+    (1431, 11),
+    (1432, 14),
+    (1433, 2),
+    (1434, 2),
+    (1436, 1),
+    (1437, 1),
+    (1438, 13),
+    (1439, 1),
+    (1440, 1),
+    (1441, 1),
+    (1442, 2),
+    (1443, 132),
+    (1444, 5),
+    (1445, 1795),
+    (1448, 11),
+    (1449, 10),
+    (1450, 11),
+    (1451, 8),
+    (1452, 47),
+    (1453, 6),
+    (1454, 8),
+    (1455, 12),
+    (1456, 229),
+    (1457, 15),
+    (1458, 12),
+    (1459, 121),
+    (1460, 15),
+    (1461, 48),
+    (1462, 49),
+    (1463, 22),
+    (1464, 11),
+    (1465, 9),
+    (1466, 81),
+    (1467, 1),
+    (1468, 1),
+    (1469, 6),
+    (1470, 6),
+    (1471, 6),
+    (1472, 9),
+    (1473, 12),
+    (1474, 2),
+    (1475, 109),
+    (1476, 5),
+    (1477, 1721),
+    (1478, 1),
+    (1479, 28),
+    (1480, 7),
+    (1481, 23),
+    (1482, 2),
+    (1483, 12),
+    (1484, 5),
+    (1485, 3),
+    (1486, 2),
+    (1487, 4),
+    (1488, 219),
+    (1489, 7),
+    (1490, 8),
+    (1491, 10),
+    (1492, 16),
+    (1493, 32),
+    (1494, 25),
+    (1495, 96),
+    (1496, 13),
+    (1497, 15),
+    (1498, 16),
+    (1499, 12),
+    (1500, 14),
+    (1501, 19),
+    (1502, 7),
+    (1503, 11),
+    (1504, 3),
+    (1505, 8),
+    (1506, 41),
+    (1507, 108),
+    (1508, 25),
+    (1509, 1719),
+    (1510, 8),
+    (1511, 10),
+    (1514, 2),
+    (1515, 25),
+    (1516, 2),
+    (1517, 32),
+    (1518, 6),
+    (1519, 7),
+    (1520, 273),
+    (1521, 2),
+    (1522, 6),
+    (1523, 5),
+    (1524, 6),
+    (1525, 36),
+    (1526, 3),
+    (1527, 12),
+    (1528, 7),
+    (1529, 9),
+    (1530, 12),
+    (1531, 107),
+    (1532, 44),
+    (1533, 17),
+    (1534, 12),
+    (1535, 18),
+    (1536, 12),
+    (1537, 26),
+    (1538, 35),
+    (1539, 131),
+    (1540, 15),
+    (1541, 1693),
+    (1542, 11),
+    (1543, 7),
+    (1544, 2),
+    (1545, 6),
+    (1546, 14),
+    (1547, 6),
+    (1548, 2),
+    (1549, 24),
+    (1550, 2),
+    (1551, 33),
+    (1552, 206),
+    (1553, 18),
+    (1555, 1),
+    (1556, 7),
+    (1557, 38),
+    (1558, 6),
+    (1559, 3),
+    (1560, 21),
+    (1562, 2),
+    (1563, 5),
+    (1564, 7),
+    (1565, 5),
+    (1566, 6),
+    (1567, 110),
+    (1568, 9),
+    (1569, 16),
+    (1570, 13),
+    (1571, 109),
+    (1572, 6),
+    (1573, 1664),
+    (1574, 53),
+    (1575, 14),
+    (1576, 21),
+    (1577, 31),
+    (1578, 42),
+    (1579, 13),
+    (1580, 10),
+    (1581, 12),
+    (1582, 11),
+    (1583, 85),
+    (1584, 202),
+    (1585, 7),
+    (1586, 6),
+    (1587, 25),
+    (1588, 5),
+    (1589, 41),
+    (1590, 4),
+    (1591, 5),
+    (1593, 1),
+    (1595, 5),
+    (1596, 11),
+    (1598, 1),
+    (1599, 1),
+    (1600, 1),
+    (1601, 4),
+    (1602, 19),
+    (1603, 200),
+    (1604, 10),
+    (1605, 1640),
+    (1606, 15),
+    (1607, 14),
+    (1608, 7),
+    (1609, 12),
+    (1610, 5),
+    (1611, 2),
+    (1612, 3),
+    (1613, 7),
+    (1614, 37),
+    (1615, 4),
+    (1616, 203),
+    (1617, 13),
+    (1618, 3),
+    (1619, 12),
+    (1620, 38),
+    (1621, 22),
+    (1622, 12),
+    (1623, 43),
+    (1624, 19),
+    (1625, 35),
+    (1626, 15),
+    (1627, 26),
+    (1628, 43),
+    (1629, 2),
+    (1630, 10),
+    (1631, 1),
+    (1633, 1),
+    (1634, 1),
+    (1635, 110),
+    (1637, 1612),
+    (1638, 1),
+    (1639, 107),
+    (1640, 1),
+    (1641, 2),
+    (1643, 7),
+    (1644, 9),
+    (1645, 8),
+    (1646, 3),
+    (1647, 19),
+    (1648, 206),
+    (1649, 2),
+    (1650, 9),
+    (1651, 8),
+    (1652, 19),
+    (1653, 22),
+    (1654, 4),
+    (1655, 13),
+    (1656, 3),
+    (1657, 5),
+    (1658, 5),
+    (1659, 35),
+    (1660, 10),
+    (1661, 26),
+    (1662, 8),
+    (1663, 10),
+    (1664, 7),
+    (1665, 4),
+    (1666, 2),
+    (1667, 110),
+    (1668, 12),
+    (1669, 1594),
+    (1670, 1),
+    (1671, 2),
+    (1672, 15),
+    (1673, 4),
+    (1674, 2),
+    (1675, 303),
+    (1676, 12),
+    (1678, 1),
+    (1680, 194),
+    (1681, 1),
+    (1682, 40),
+    (1683, 2),
+    (1684, 2),
+    (1685, 19),
+    (1686, 16),
+    (1687, 2),
+    (1688, 6),
+    (1689, 9),
+    (1690, 18),
+    (1691, 15),
+    (1692, 5),
+    (1693, 7),
+    (1694, 6),
+    (1695, 32),
+    (1696, 4),
+    (1697, 34),
+    (1698, 1),
+    (1699, 117),
+    (1700, 5),
+    (1701, 1590),
+    (1702, 20),
+    (1703, 4),
+    (1704, 6),
+    (1705, 20),
+    (1707, 2),
+    (1710, 3),
+    (1711, 89),
+    (1712, 195),
+    (1713, 4),
+    (1714, 2),
+    (1715, 1),
+    (1716, 3),
+    (1717, 16),
+    (1718, 9),
+    (1719, 2),
+    (1720, 3),
+    (1723, 18),
+    (1724, 1),
+    (1725, 2),
+    (1726, 3),
+    (1727, 3),
+    (1728, 9),
+    (1729, 5),
+    (1730, 7),
+    (1731, 132),
+    (1732, 28),
+    (1733, 1585),
+    (1734, 5),
+    (1735, 3),
+    (1736, 5),
+    (1737, 27),
+    (1738, 4),
+    (1739, 19),
+    (1740, 15),
+    (1741, 4),
+    (1742, 15),
+    (1743, 9),
+    (1744, 183),
+    (1745, 12),
+    (1747, 119),
+    (1748, 1),
+    (1749, 15),
+    (1750, 5),
+    (1754, 1),
+    (1757, 2),
+    (1758, 8),
+    (1759, 7),
+    (1760, 7),
+    (1761, 2),
+    (1762, 13),
+    (1763, 113),
+    (1764, 8),
+    (1765, 1547),
+    (1766, 7),
+    (1767, 21),
+    (1768, 3),
+    (1769, 34),
+    (1770, 5),
+    (1772, 6),
+    (1773, 7),
+    (1774, 12),
+    (1775, 9),
+    (1776, 189),
+    (1777, 25),
+    (1778, 10),
+    (1779, 4),
+    (1780, 1),
+    (1781, 21),
+    (1782, 3),
+    (1783, 186),
+    (1784, 2),
+    (1787, 1),
+    (1788, 10),
+    (1789, 8),
+    (1790, 1),
+    (1791, 34),
+    (1792, 1),
+    (1793, 1),
+    (1794, 1),
+    (1795, 108),
+    (1796, 4),
+    (1797, 1519),
+    (1798, 9),
+    (1799, 9),
+    (1800, 3),
+    (1801, 6),
+    (1802, 4),
+    (1803, 35),
+    (1804, 15),
+    (1805, 30),
+    (1806, 5),
+    (1807, 7),
+    (1808, 192),
+    (1809, 8),
+    (1811, 4),
+    (1812, 24),
+    (1813, 36),
+    (1814, 4),
+    (1815, 14),
+    (1816, 2),
+    (1817, 2),
+    (1818, 4),
+    (1819, 72),
+    (1820, 3),
+    (1822, 1),
+    (1823, 4),
+    (1825, 1),
+    (1826, 5),
+    (1827, 104),
+    (1828, 1),
+    (1829, 1494),
+    (1830, 11),
+    (1831, 5),
+    (1832, 2),
+    (1833, 2),
+    (1834, 2),
+    (1835, 4),
+    (1836, 9),
+    (1837, 1),
+    (1838, 14),
+    (1839, 33),
+    (1840, 188),
+    (1841, 27),
+    (1842, 13),
+    (1843, 10),
+    (1844, 28),
+    (1845, 52),
+    (1846, 17),
+    (1847, 40),
+    (1848, 35),
+    (1849, 6),
+    (1850, 6),
+    (1851, 2),
+    (1853, 4),
+    (1854, 6),
+    (1855, 77),
+    (1856, 1),
+    (1859, 106),
+    (1860, 2),
+    (1861, 1466),
+    (1863, 2),
+    (1866, 1),
+    (1869, 1),
+    (1870, 2),
+    (1872, 179),
+    (1873, 1),
+    (1874, 9),
+    (1875, 29),
+    (1876, 15),
+    (1877, 43),
+    (1878, 2),
+    (1880, 8),
+    (1881, 13),
+    (1882, 18),
+    (1883, 12),
+    (1884, 14),
+    (1885, 18),
+    (1886, 16),
+    (1887, 6),
+    (1888, 2),
+    (1889, 3),
+    (1890, 9),
+    (1891, 196),
+    (1892, 13),
+    (1893, 1456),
+    (1894, 14),
+    (1895, 8),
+    (1896, 2),
+    (1898, 1),
+    (1899, 17),
+    (1900, 5),
+    (1901, 1),
+    (1904, 175),
+    (1905, 1),
+    (1906, 2),
+    (1907, 3),
+    (1908, 6),
+    (1909, 10),
+    (1910, 3),
+    (1911, 22),
+    (1912, 6),
+    (1913, 22),
+    (1914, 6),
+    (1915, 10),
+    (1916, 5),
+    (1917, 2),
+    (1918, 6),
+    (1919, 4),
+    (1920, 7),
+    (1921, 14),
+    (1922, 4),
+    (1923, 107),
+    (1924, 10),
+    (1925, 1434),
+    (1926, 7),
+    (1927, 76),
+    (1928, 4),
+    (1929, 7),
+    (1930, 10),
+    (1931, 14),
+    (1932, 6),
+    (1933, 15),
+    (1934, 4),
+    (1935, 2),
+    (1936, 182),
+    (1937, 2),
+    (1939, 11),
+    (1940, 1),
+    (1941, 4),
+    (1942, 2),
+    (1943, 9),
+    (1944, 1),
+    (1947, 24),
+    (1949, 22),
+    (1952, 15),
+    (1953, 14),
+    (1954, 5),
+    (1955, 111),
+    (1956, 11),
+    (1957, 1435),
+    (1958, 5),
+    (1959, 5),
+    (1960, 10),
+    (1961, 6),
+    (1962, 11),
+    (1963, 95),
+    (1964, 11),
+    (1965, 7),
+    (1966, 7),
+    (1967, 2),
+    (1968, 182),
+    (1969, 6),
+    (1970, 15),
+    (1972, 7),
+    (1973, 11),
+    (1974, 6),
+    (1975, 2),
+    (1976, 6),
+    (1977, 3),
+    (1978, 2),
+    (1983, 24),
+    (1985, 26),
+    (1986, 3),
+    (1987, 109),
+    (1988, 3),
+    (1989, 1421),
+    (1990, 1),
+    (1991, 3),
+    (1992, 8),
+    (1993, 4),
+    (1994, 6),
+    (1995, 5),
+    (1996, 13),
+    (1997, 6),
+    (1998, 10),
+    (1999, 92),
+    (2000, 181),
+    (2001, 5),
+    (2002, 5),
+    (2003, 1),
+    (2004, 1),
+    (2005, 14),
+    (2006, 12),
+    (2007, 10),
+    (2008, 7),
+    (2009, 9),
+    (2010, 6),
+    (2011, 8),
+    (2012, 13),
+    (2013, 2),
+    (2014, 2),
+    (2018, 1),
+    (2019, 128),
+    (2021, 1429),
+    (2022, 4),
+    (2026, 2),
+    (2027, 2),
+    (2030, 7),
+    (2032, 175),
+    (2033, 1),
+    (2035, 90),
+    (2036, 3),
+    (2037, 11),
+    (2038, 2),
+    (2039, 4),
+    (2040, 3),
+    (2041, 2),
+    (2042, 1),
+    (2043, 2),
+    (2044, 5),
+    (2045, 1),
+    (2046, 3),
+    (2047, 21),
+    (2048, 5),
+    (2050, 16),
+    (2051, 120),
+    (2053, 1403),
+    (2054, 4),
+    (2055, 29),
+    (2057, 26),
+    (2058, 3),
+    (2059, 4),
+    (2060, 4),
+    (2061, 7),
+    (2063, 1),
+    (2065, 170),
+    (2066, 3),
+    (2067, 2),
+    (2068, 7),
+    (2069, 13),
+    (2071, 77),
+    (2072, 1),
+    (2075, 4),
+    (2077, 1),
+    (2078, 2),
+    (2079, 5),
+    (2080, 4),
+    (2081, 3),
+    (2082, 3),
+    (2083, 2),
+    (2084, 293),
+    (2085, 6),
+    (2086, 1395),
+    (2087, 2),
+    (2089, 4),
+    (2090, 10),
+    (2091, 26),
+    (2092, 14),
+    (2093, 25),
+    (2097, 170),
+    (2099, 2),
+    (2100, 1),
+    (2101, 8),
+    (2102, 5),
+    (2104, 2),
+    (2105, 2),
+    (2107, 90),
+    (2108, 1),
+    (2110, 15),
+    (2112, 1),
+    (2113, 1),
+    (2114, 3),
+    (2115, 8),
+    (2116, 3),
+    (2117, 5),
+    (2118, 1380),
+    (2119, 4),
+    (2120, 1),
+    (2121, 3),
+    (2122, 1),
+    (2123, 6),
+    (2124, 24),
+    (2125, 1),
+    (2127, 33),
+    (2128, 4),
+    (2129, 197),
+    (2132, 1),
+    (2133, 3),
+    (2134, 8),
+    (2141, 1),
+    (2143, 95),
+    (2144, 6),
+    (2146, 1),
+    (2147, 1),
+    (2148, 3),
+    (2150, 1369),
+    (2152, 1),
+    (2153, 1),
+    (2155, 5),
+    (2156, 7),
+    (2157, 12),
+    (2158, 2),
+    (2159, 6),
+    (2160, 7),
+    (2161, 174),
+    (2162, 22),
+    (2163, 27),
+    (2164, 5),
+    (2165, 24),
+    (2166, 6),
+    (2169, 8),
+    (2170, 2),
+    (2171, 1),
+    (2172, 1),
+    (2174, 8),
+    (2175, 10),
+    (2176, 2),
+    (2177, 3),
+    (2179, 72),
+    (2180, 4),
+    (2181, 1),
+    (2182, 1366),
+    (2183, 2),
+    (2184, 5),
+    (2185, 4),
+    (2188, 3),
+    (2191, 1),
+    (2192, 2),
+    (2193, 169),
+    (2198, 7),
+    (2199, 27),
+    (2201, 28),
+    (2205, 2),
+    (2206, 2),
+    (2209, 9),
+    (2213, 8),
+    (2214, 1364),
+    (2215, 95),
+    (2216, 1),
+    (2217, 2),
+    (2218, 1),
+    (2219, 1),
+    (2220, 3),
+    (2221, 2),
+    (2222, 3),
+    (2223, 41),
+    (2225, 168),
+    (2228, 1),
+    (2229, 6),
+    (2230, 8),
+    (2231, 1),
+    (2232, 2),
+    (2233, 6),
+    (2234, 1),
+    (2235, 41),
+    (2236, 2),
+    (2237, 17),
+    (2240, 7),
+    (2242, 6),
+    (2244, 1),
+    (2246, 1350),
+    (2249, 2),
+    (2250, 4),
+    (2251, 89),
+    (2252, 1),
+    (2257, 167),
+    (2260, 4),
+    (2261, 3),
+    (2262, 6),
+    (2265, 1),
+    (2269, 2),
+    (2270, 4),
+    (2271, 32),
+    (2273, 21),
+    (2274, 1),
+    (2275, 3),
+    (2276, 1),
+    (2277, 2),
+    (2278, 1344),
+    (2279, 2),
+    (2280, 1),
+    (2281, 1),
+    (2284, 1),
+    (2287, 98),
+    (2288, 2),
+    (2289, 168),
+    (2292, 3),
+    (2293, 3),
+    (2294, 4),
+    (2298, 3),
+    (2303, 9),
+    (2307, 26),
+    (2308, 1),
+    (2309, 30),
+    (2310, 1344),
+    (2314, 1),
+    (2318, 1),
+    (2321, 164),
+    (2323, 1),
+    (2324, 82),
+    (2325, 1),
+    (2326, 5),
+    (2327, 1),
+    (2334, 6),
+    (2338, 1),
+    (2339, 1),
+    (2340, 1),
+    (2342, 1337),
+    (2343, 55),
+    (2344, 27),
+    (2345, 6),
+    (2346, 25),
+    (2347, 1),
+    (2348, 18),
+    (2350, 1),
+    (2351, 3),
+    (2352, 2),
+    (2353, 166),
+    (2358, 6),
+    (2360, 87),
+    (2361, 3),
+    (2362, 1),
+    (2373, 9),
+    (2374, 1330),
+    (2376, 1),
+    (2377, 1),
+    (2378, 11),
+    (2379, 4),
+    (2380, 28),
+    (2382, 29),
+    (2383, 2),
+    (2384, 8),
+    (2385, 169),
+    (2386, 4),
+    (2387, 9),
+    (2388, 8),
+    (2389, 4),
+    (2390, 15),
+    (2392, 1),
+    (2396, 117),
+    (2397, 4),
+    (2399, 1),
+    (2406, 1330),
+    (2410, 1),
+    (2414, 1),
+    (2415, 4),
+    (2416, 26),
+    (2417, 164),
+    (2418, 31),
+    (2421, 3),
+    (2422, 4),
+    (2424, 6),
+    (2425, 3),
+    (2426, 3),
+    (2427, 5),
+    (2428, 1),
+    (2429, 2),
+    (2432, 100),
+    (2433, 1),
+    (2435, 1),
+    (2436, 1),
+    (2438, 1328),
+    (2441, 10),
+    (2443, 11),
+    (2448, 2),
+    (2449, 163),
+    (2451, 1),
+    (2452, 27),
+    (2453, 8),
+    (2454, 24),
+    (2455, 1),
+    (2456, 2),
+    (2457, 2),
+    (2460, 4),
+    (2465, 5),
+    (2466, 3),
+    (2468, 95),
+    (2469, 6),
+    (2470, 1324),
+    (2471, 1),
+    (2472, 1),
+    (2476, 2),
+    (2477, 2),
+    (2478, 2),
+    (2479, 4),
+    (2481, 163),
+    (2484, 2),
+    (2485, 6),
+    (2486, 2),
+    (2488, 23),
+    (2489, 1),
+    (2490, 26),
+    (2491, 1),
+    (2493, 1),
+    (2494, 1),
+    (2495, 3),
+    (2496, 1),
+    (2500, 3),
+    (2502, 1327),
+    (2503, 1),
+    (2504, 93),
+    (2505, 2),
+    (2506, 1),
+    (2511, 4),
+    (2513, 166),
+    (2516, 3),
+    (2517, 5),
+    (2518, 8),
+    (2519, 2),
+    (2521, 1),
+    (2524, 27),
+    (2526, 20),
+    (2532, 1),
+    (2534, 1320),
+    (2535, 1),
+    (2540, 114),
+    (2541, 1),
+    (2543, 1),
+    (2545, 163),
+    (2550, 3),
+    (2555, 3),
+    (2557, 4),
+    (2558, 3),
+    (2559, 2),
+    (2560, 26),
+    (2561, 6),
+    (2562, 26),
+    (2564, 5),
+    (2565, 1),
+    (2566, 1325),
+    (2567, 5),
+    (2568, 9),
+    (2569, 10),
+    (2570, 2),
+    (2571, 1),
+    (2576, 97),
+    (2577, 165),
+    (2582, 3),
+    (2583, 5),
+    (2593, 2),
+    (2596, 42),
+    (2597, 1),
+    (2598, 1336),
+    (2602, 1),
+    (2609, 163),
+    (2612, 97),
+    (2613, 1),
+    (2614, 2),
+    (2619, 1),
+    (2621, 2),
+    (2624, 2),
+    (2628, 2),
+    (2630, 1684946),
+    (2632, 27),
+    (2633, 2),
+    (2634, 25),
+    (2635, 1),
+    (2637, 4),
+    (2639, 1),
+    (2640, 1),
+    (2641, 163),
+    (2644, 1),
+    (2645, 3),
+    (2646, 2),
+    (2648, 112),
+    (2649, 1),
+    (2653, 5),
+    (2659, 3),
+    (2660, 1),
+    (2661, 1),
+    (2662, 1315),
+    (2664, 1),
+    (2668, 30),
+    (2669, 1),
+    (2670, 26),
+    (2673, 163),
+    (2674, 2),
+    (2675, 1),
+    (2678, 7),
+    (2679, 1),
+    (2680, 1),
+    (2684, 90),
+    (2685, 1),
+    (2686, 1),
+    (2694, 1315),
+    (2699, 1),
+    (2701, 1),
+    (2704, 30),
+    (2705, 163),
+    (2706, 27),
+    (2710, 2),
+    (2712, 1),
+    (2720, 112),
+    (2721, 2),
+    (2723, 5),
+    (2726, 1316),
+    (2736, 1),
+    (2737, 165),
+    (2738, 2),
+    (2740, 25),
+    (2742, 33),
+    (2745, 1),
+    (2756, 97),
+    (2757, 1),
+    (2758, 1315),
+    (2769, 163),
+    (2774, 3),
+    (2776, 32),
+    (2778, 34),
+    (2781, 1),
+    (2782, 1),
+    (2784, 1),
+    (2790, 1313),
+    (2792, 94),
+    (2793, 12),
+    (2796, 1),
+    (2800, 1),
+    (2801, 163),
+    (2804, 2),
+    (2805, 6),
+    (2806, 2),
+    (2807, 2),
+    (2809, 1),
+    (2810, 1),
+    (2812, 23),
+    (2814, 33),
+    (2815, 3),
+    (2816, 1),
+    (2820, 2),
+    (2821, 1),
+    (2822, 1314),
+    (2824, 1),
+    (2828, 104),
+    (2829, 1),
+    (2833, 163),
+    (2837, 6),
+    (2838, 4),
+    (2839, 1),
+    (2848, 32),
+    (2849, 4),
+    (2850, 32),
+    (2852, 4),
+    (2853, 1),
+    (2854, 1312),
+    (2861, 1),
+    (2863, 52),
+    (2864, 111),
+    (2865, 164),
+    (2868, 2),
+    (2869, 15),
+    (2870, 2),
+    (2871, 1),
+    (2884, 30),
+    (2886, 1333),
+    (2890, 2),
+    (2891, 2),
+    (2892, 3),
+    (2893, 4),
+    (2894, 2),
+    (2897, 163),
+    (2899, 3),
+    (2900, 230),
+    (2901, 1),
+    (2902, 2),
+    (2908, 2),
+    (2911, 1),
+    (2918, 1312),
+    (2920, 42),
+    (2922, 25),
+    (2923, 1),
+    (2925, 1),
+    (2929, 165),
+    (2930, 2),
+    (2931, 5),
+    (2932, 4),
+    (2933, 8),
+    (2934, 2),
+    (2936, 110),
+    (2937, 1),
+    (2938, 1),
+    (2939, 1),
+    (2948, 1),
+    (2950, 1313),
+    (2956, 38),
+    (2958, 32),
+    (2961, 163),
+    (2964, 1),
+    (2966, 4),
+    (2967, 2),
+    (2969, 1),
+    (2971, 1),
+    (2972, 151),
+    (2973, 1),
+    (2975, 3),
+    (2976, 4),
+    (2977, 3),
+    (2978, 1),
+    (2979, 1),
+    (2980, 1),
+    (2982, 1312),
+    (2992, 28),
+    (2993, 163),
+    (2994, 29),
+    (2998, 2),
+    (3006, 1),
+    (3007, 2),
+    (3008, 188),
+    (3009, 2),
+    (3014, 1311),
+    (3015, 5),
+    (3016, 9),
+    (3017, 1),
+    (3020, 1),
+    (3025, 164),
+    (3028, 27),
+    (3030, 31),
+    (3044, 223),
+    (3045, 1),
+    (3046, 1311),
+    (3048, 1),
+    (3057, 163),
+    (3061, 2),
+    (3062, 4),
+    (3064, 41),
+    (3066, 35),
+    (3076, 2),
+    (3078, 1310),
+    (3080, 151),
+    (3081, 2),
+    (3089, 163),
+    (3094, 2),
+    (3100, 35),
+    (3101, 2),
+    (3102, 38),
+    (3104, 2),
+    (3110, 1310),
+    (3116, 106),
+    (3117, 2),
+    (3121, 163),
+    (3125, 5),
+    (3126, 2),
+    (3132, 2),
+    (3136, 36),
+    (3138, 39),
+    (3140, 2),
+    (3141, 1),
+    (3142, 1309),
+    (3143, 1),
+    (3144, 1),
+    (3152, 120),
+    (3153, 164),
+    (3155, 1),
+    (3157, 1),
+    (3158, 2),
+    (3163, 1),
+    (3164, 1),
+    (3172, 34),
+    (3174, 1343),
+    (3185, 163),
+    (3188, 136),
+    (3189, 1),
+    (3190, 2),
+    (3203, 1),
+    (3204, 1),
+    (3206, 1308),
+    (3208, 53),
+    (3210, 52),
+    (3217, 163),
+    (3220, 38),
+    (3221, 114),
+    (3222, 2),
+    (3224, 141),
+    (3225, 5),
+    (3230, 1),
+    (3236, 38),
+    (3238, 1308),
+    (3244, 35),
+    (3246, 46),
+    (3249, 163),
+    (3254, 2),
+    (3260, 105),
+    (3261, 4),
+    (3263, 1),
+    (3270, 1308),
+    (3280, 38),
+    (3281, 163),
+    (3282, 28),
+    (3286, 3),
+    (3292, 1),
+    (3296, 138),
+    (3297, 1),
+    (3301, 1),
+    (3302, 1308),
+    (3304, 1),
+    (3313, 163),
+    (3316, 33),
+    (3318, 34),
+    (3329, 1),
+    (3331, 1),
+    (3332, 120),
+    (3333, 1),
+    (3334, 1309),
+    (3345, 163),
+    (3350, 3),
+    (3352, 34),
+    (3354, 31),
+    (3357, 1),
+    (3366, 1307),
+    (3368, 230),
+    (3369, 6),
+    (3377, 163),
+    (3382, 2),
+    (3388, 37),
+    (3390, 45),
+    (3398, 1307),
+    (3404, 3128),
+    (3405, 2),
+    (3409, 163),
+    (3414, 2),
+    (3424, 40),
+    (3426, 23),
+    (3430, 1307),
+    (3440, 117),
+    (3441, 164),
+    (3446, 2),
+    (3460, 30),
+    (3462, 1344),
+    (3469, 1),
+    (3473, 163),
+    (3476, 116),
+    (3477, 1),
+    (3478, 3),
+    (3494, 1305),
+    (3496, 36),
+    (3498, 38),
+    (3501, 2),
+    (3504, 2),
+    (3505, 163),
+    (3510, 2),
+    (3512, 124),
+    (3513, 4),
+    (3515, 1),
+    (3525, 1),
+    (3526, 1305),
+    (3532, 27),
+    (3534, 33),
+    (3537, 165),
+    (3541, 2),
+    (3542, 2),
+    (3544, 2),
+    (3548, 119),
+    (3549, 1),
+    (3558, 1305),
+    (3568, 29),
+    (3569, 163),
+    (3570, 53),
+    (3574, 2),
+    (3581, 6),
+    (3584, 115),
+    (3585, 2),
+    (3590, 1306),
+    (3601, 163),
+    (3604, 39),
+    (3606, 45),
+    (3620, 107),
+    (3621, 1),
+    (3622, 1304),
+    (3633, 163),
+    (3634, 1),
+    (3637, 1),
+    (3638, 2),
+    (3640, 43),
+    (3642, 35),
+    (3654, 1305),
+    (3656, 126),
+    (3657, 2),
+    (3661, 1),
+    (3664, 1),
+    (3665, 163),
+    (3670, 3),
+    (3676, 32),
+    (3678, 48),
+    (3679, 1),
+    (3686, 1303),
+    (3692, 128),
+    (3693, 2),
+    (3697, 163),
+    (3702, 3),
+    (3712, 33),
+    (3714, 28),
+    (3718, 1302),
+    (3728, 137),
+    (3729, 165),
+    (3734, 2),
+    (3748, 54),
+    (3749, 1),
+    (3750, 1333),
+    (3758, 1),
+    (3761, 163),
+    (3764, 125),
+    (3765, 2),
+    (3766, 3),
+    (3782, 1301),
+    (3784, 32),
+    (3786, 50),
+    (3793, 163),
+    (3798, 2),
+    (3800, 123),
+    (3801, 3),
+    (3805, 1),
+    (3814, 1301),
+    (3820, 53),
+    (3822, 30),
+    (3825, 163),
+    (3830, 2),
+    (3833, 1),
+    (3836, 109),
+    (3837, 3),
+    (3846, 1301),
+    (3856, 35),
+    (3857, 163),
+    (3858, 54),
+    (3860, 20),
+    (3861, 51),
+    (3862, 2),
+    (3872, 124),
+    (3873, 2),
+    (3876, 17),
+    (3878, 1302),
+    (3882, 1),
+    (3889, 163),
+    (3892, 45),
+    (3894, 47),
+    (3901, 2),
+    (3903, 1),
+    (3904, 2),
+    (3908, 138),
+    (3909, 2),
+    (3910, 1300),
+    (3917, 2),
+    (3921, 163),
+    (3926, 2),
+    (3928, 38),
+    (3930, 37),
+    (3942, 1300),
+    (3944, 137),
+    (3945, 2),
+    (3953, 163),
+    (3958, 2),
+    (3964, 66),
+    (3966, 37),
+    (3971, 1),
+    (3974, 1300),
+    (3980, 166),
+    (3981, 1),
+    (3985, 163),
+    (3990, 2),
+    (4000, 35),
+    (4002, 54),
+    (4006, 1300),
+    (4016, 150),
+    (4017, 164),
+    (4021, 38),
+    (4022, 2),
+    (4024, 38),
+    (4036, 47),
+    (4038, 1347),
+    (4049, 163),
+    (4052, 134),
+    (4053, 10),
+    (4054, 2),
+    (4068, 1),
+    (4070, 1300),
+    (4072, 52),
+    (4074, 40),
+    (4075, 1),
+    (4081, 163),
+    (4085, 7),
+    (4086, 2),
+    (4088, 123),
+    (4089, 4),
+    (4100, 2),
+    (4102, 1300),
+    (4108, 38),
+    (4110, 43),
+    (4113, 163),
+    (4118, 2),
+    (4119, 2),
+    (4124, 159),
+    (4125, 3),
+    (4128, 1),
+    (4134, 1299),
+    (4141, 1),
+    (4144, 51),
+    (4145, 163),
+    (4146, 41),
+    (4150, 2),
+    (4152, 30),
+    (4160, 153),
+    (4161, 1),
+    (4164, 2),
+    (4166, 1299),
+    (4177, 163),
+    (4180, 225),
+    (4181, 596),
+    (4182, 50),
+    (4187, 1),
+    (4196, 373),
+    (4197, 3),
+    (4198, 1299),
+    (4209, 163),
+    (4214, 2),
+    (4216, 66),
+    (4217, 3),
+    (4218, 69),
+    (4221, 1),
+    (4230, 1299),
+    (4232, 158),
+    (4233, 2),
+    (4241, 163),
+    (4246, 2),
+    (4252, 45),
+    (4253, 1),
+    (4254, 48),
+    (4262, 1300),
+    (4267, 2),
+    (4268, 145),
+    (4269, 3),
+    (4270, 1),
+    (4271, 1),
+    (4273, 163),
+    (4278, 3),
+    (4288, 75),
+    (4290, 36),
+    (4294, 1298),
+    (4301, 1),
+    (4304, 173),
+    (4305, 166),
+    (4309, 2),
+    (4310, 2),
+    (4324, 52),
+    (4326, 1359),
+    (4337, 163),
+    (4340, 195),
+    (4341, 2),
+    (4342, 3),
+    (4358, 1297),
+    (4360, 76),
+    (4362, 56),
+    (4365, 2),
+    (4369, 163),
+    (4374, 2),
+    (4376, 171),
+    (4377, 1),
+    (4390, 1298),
+    (4396, 52),
+    (4398, 49),
+    (4401, 163),
+    (4406, 3),
+    (4407, 2),
+    (4412, 170),
+    (4413, 2),
+    (4421, 1),
+    (4422, 1296),
+    (4432, 57),
+    (4433, 163),
+    (4434, 51),
+    (4436, 1),
+    (4438, 2),
+    (4448, 481),
+    (4449, 2),
+    (4451, 1),
+    (4454, 1295),
+    (4463, 1),
+    (4465, 163),
+    (4468, 74),
+    (4470, 92),
+    (4484, 448),
+    (4485, 3),
+    (4486, 1295),
+    (4487, 1),
+    (4497, 163),
+    (4502, 2),
+    (4504, 52),
+    (4506, 65),
+    (4518, 1295),
+    (4519, 2),
+    (4520, 631),
+    (4521, 3),
+    (4529, 164),
+    (4530, 1),
+    (4532, 1),
+    (4533, 3),
+    (4534, 2),
+    (4540, 55),
+    (4542, 48),
+    (4550, 1294),
+    (4556, 2358),
+    (4557, 3),
+    (4561, 163),
+    (4562, 1),
+    (4566, 2),
+    (4576, 58),
+    (4578, 74),
+    (4582, 1294),
+    (4592, 193),
+    (4593, 167),
+    (4598, 2),
+    (4612, 66),
+    (4614, 1363),
+    (4621, 2),
+    (4625, 163),
+    (4628, 218),
+    (4629, 3),
+    (4630, 2),
+    (4635, 3),
+    (4640, 1),
+    (4645, 1),
+    (4646, 1295),
+    (4648, 57),
+    (4650, 90),
+    (4657, 163),
+    (4662, 3),
+    (4664, 194),
+    (4665, 1),
+    (4678, 1295),
+    (4684, 49),
+    (4685, 1),
+    (4686, 85),
+    (4689, 163),
+    (4694, 4),
+    (4700, 183),
+    (4701, 3),
+    (4710, 1291),
+    (4720, 61),
+    (4721, 163),
+    (4722, 75),
+    (4726, 3),
+    (4736, 175),
+    (4737, 4),
+    (4742, 1291),
+    (4753, 163),
+    (4756, 84),
+    (4758, 53),
+    (4772, 210),
+    (4773, 4),
+    (4774, 1291),
+    (4785, 163),
+    (4790, 2),
+    (4792, 54),
+    (4794, 66),
+    (4799, 2),
+    (4806, 1292),
+    (4808, 180),
+    (4809, 6),
+    (4817, 164),
+    (4820, 32),
+    (4821, 132),
+    (4822, 3),
+    (4824, 17),
+    (4828, 70),
+    (4830, 62),
+    (4836, 42),
+    (4838, 1290),
+    (4844, 199),
+    (4845, 3),
+    (4849, 163),
+    (4854, 2),
+    (4864, 104),
+    (4866, 98),
+    (4870, 1290),
+    (4873, 1),
+    (4880, 184),
+    (4881, 164),
+    (4886, 2),
+    (4900, 88),
+    (4902, 1387),
+    (4909, 1),
+    (4913, 163),
+    (4916, 187),
+    (4917, 6),
+    (4918, 2),
+    (4934, 1290),
+    (4936, 65),
+    (4938, 59),
+    (4945, 163),
+    (4948, 1),
+    (4950, 2),
+    (4952, 198),
+    (4953, 3),
+    (4966, 1290),
+    (4972, 64),
+    (4974, 108),
+    (4977, 163),
+    (4982, 2),
+    (4988, 199),
+    (4989, 8),
+    (4998, 1290),
+    (5008, 82),
+    (5009, 163),
+    (5010, 113),
+    (5012, 3),
+    (5013, 9),
+    (5014, 2),
+    (5017, 1),
+    (5024, 228),
+    (5025, 2),
+    (5028, 4),
+    (5030, 1290),
+    (5041, 162),
+    (5044, 96),
+    (5046, 71),
+    (5060, 275),
+    (5061, 6),
+    (5062, 1291),
+    (5064, 1),
+    (5070, 1),
+    (5073, 162),
+    (5078, 3),
+    (5080, 66),
+    (5082, 153),
+    (5094, 1289),
+    (5096, 272),
+    (5097, 10),
+    (5101, 2),
+    (5104, 2),
+    (5105, 162),
+    (5110, 2),
+    (5116, 87),
+    (5118, 80),
+    (5126, 1289),
+    (5132, 266),
+    (5133, 5),
+    (5135, 1),
+    (5137, 162),
+    (5140, 190),
+    (5141, 681),
+    (5142, 2),
+    (5152, 104),
+    (5154, 184),
+    (5156, 238),
+    (5158, 1289),
+    (5168, 257),
+    (5169, 165),
+    (5174, 2),
+    (5188, 99),
+    (5190, 1435),
+    (5201, 162),
+    (5204, 228),
+    (5205, 6),
+    (5206, 2),
+    (5221, 206),
+    (5222, 1289),
+    (5224, 312),
+    (5226, 110),
+    (5231, 1),
+    (5233, 162),
+    (5238, 2),
+    (5240, 266),
+    (5241, 7),
+    (5254, 1289),
+    (5260, 87),
+    (5262, 243),
+    (5265, 162),
+    (5270, 2),
+    (5274, 8),
+    (5276, 318),
+    (5277, 7),
+    (5286, 1289),
+    (5288, 86),
+    (5296, 88),
+    (5297, 162),
+    (5298, 123),
+    (5302, 3),
+    (5312, 351),
+    (5313, 1),
+    (5318, 1289),
+    (5329, 162),
+    (5332, 115),
+    (5334, 173),
+    (5339, 6),
+    (5344, 1),
+    (5348, 313),
+    (5349, 3),
+    (5350, 1289),
+    (5352, 24),
+    (5353, 14),
+    (5361, 162),
+    (5366, 3),
+    (5368, 157),
+    (5370, 107),
+    (5374, 1),
+    (5382, 1289),
+    (5384, 293),
+    (5385, 4),
+    (5388, 4),
+    (5393, 162),
+    (5396, 1),
+    (5398, 2),
+    (5404, 142),
+    (5406, 201),
+    (5407, 1),
+    (5414, 1289),
+    (5417, 3),
+    (5420, 285),
+    (5421, 5),
+    (5423, 1),
+    (5425, 162),
+    (5430, 2),
+    (5436, 1),
+    (5440, 142),
+    (5442, 210),
+    (5444, 1),
+    (5446, 1294),
+    (5456, 318),
+    (5457, 166),
+    (5462, 3),
+    (5476, 123),
+    (5478, 1608),
+    (5482, 2),
+    (5489, 162),
+    (5492, 329),
+    (5493, 2),
+    (5494, 2),
+    (5504, 1),
+    (5506, 1),
+    (5510, 1289),
+    (5511, 1),
+    (5512, 165),
+    (5514, 167),
+    (5521, 163),
+    (5522, 1),
+    (5526, 2),
+    (5528, 367),
+    (5529, 8),
+    (5542, 1289),
+    (5548, 192),
+    (5550, 291),
+    (5553, 162),
+    (5558, 2),
+    (5564, 399),
+    (5565, 13),
+    (5574, 1289),
+    (5584, 188),
+    (5585, 163),
+    (5586, 356),
+    (5590, 2),
+    (5592, 1),
+    (5599, 1),
+    (5600, 375),
+    (5601, 3),
+    (5606, 1290),
+    (5608, 1),
+    (5617, 162),
+    (5618, 1),
+    (5620, 261),
+    (5622, 667),
+    (5623, 1),
+    (5626, 1),
+    (5633, 1),
+    (5636, 406),
+    (5637, 4),
+    (5638, 1289),
+    (5639, 1),
+    (5649, 162),
+    (5654, 2),
+    (5656, 468),
+    (5658, 1159),
+    (5662, 1),
+    (5670, 1289),
+    (5671, 1),
+    (5672, 349),
+    (5673, 8),
+    (5675, 1),
+    (5681, 162),
+    (5686, 2),
+    (5692, 321),
+    (5694, 3067),
+    (5702, 1289),
+    (5706, 1),
+    (5708, 443),
+    (5709, 7),
+    (5713, 162),
+    (5718, 2),
+    (5728, 496),
+    (5730, 4577),
+    (5734, 1289),
+    (5744, 383),
+    (5745, 165),
+    (5750, 3),
+    (5756, 1),
+    (5758, 1),
+    (5764, 5847),
+    (5766, 8966),
+    (5775, 1),
+    (5777, 162),
+    (5780, 616),
+    (5781, 240),
+    (5782, 2),
+    (5784, 1),
+    (5788, 1),
+    (5796, 81),
+    (5798, 1289),
+    (5799, 1),
+    (5800, 5543),
+    (5802, 13287),
+    (5809, 162),
+    (5814, 2),
+    (5816, 409),
+    (5817, 3),
+    (5830, 1289),
+    (5833, 1),
+    (5836, 123),
+    (5838, 59),
+    (5841, 162),
+    (5846, 2),
+    (5852, 480),
+    (5853, 10),
+    (5862, 1289),
+    (5872, 191),
+    (5873, 162),
+    (5874, 38),
+    (5878, 2),
+    (5888, 616),
+    (5889, 12),
+    (5894, 1289),
+    (5905, 162),
+    (5908, 139),
+    (5910, 54),
+    (5922, 1),
+    (5924, 675),
+    (5925, 9),
+    (5926, 1289),
+    (5937, 162),
+    (5942, 2),
+    (5944, 153),
+    (5946, 48),
+    (5958, 1289),
+    (5960, 614),
+    (5961, 33),
+    (5969, 162),
+    (5974, 2),
+    (5980, 140),
+    (5982, 95),
+    (5990, 1289),
+    (5996, 628),
+    (5997, 10),
+    (6001, 162),
+    (6006, 2),
+    (6016, 155),
+    (6018, 67),
+    (6021, 42),
+    (6022, 1289),
+    (6024, 42),
+    (6032, 772),
+    (6033, 177),
+    (6038, 2),
+    (6049, 1),
+    (6052, 109),
+    (6054, 1340),
+    (6065, 162),
+    (6068, 749),
+    (6069, 11),
+    (6070, 2),
+    (6086, 1289),
+    (6088, 364),
+    (6090, 49),
+    (6096, 1),
+    (6097, 162),
+    (6102, 2),
+    (6104, 975),
+    (6105, 4),
+    (6106, 1),
+    (6118, 1289),
+    (6124, 273),
+    (6126, 58),
+    (6129, 162),
+    (6134, 2),
+    (6138, 1),
+    (6140, 1053),
+    (6141, 13),
+    (6150, 1289),
+    (6152, 1),
+    (6153, 2),
+    (6160, 372),
+    (6161, 162),
+    (6162, 70),
+    (6164, 1),
+    (6166, 2),
+    (6172, 1),
+    (6176, 1088),
+    (6177, 96),
+    (6178, 1),
+    (6182, 1290),
+    (6188, 4),
+    (6193, 162),
+    (6194, 1),
+    (6196, 346),
+    (6198, 101),
+    (6206, 1),
+    (6212, 1352),
+    (6213, 4),
+    (6214, 1290),
+    (6219, 2),
+    (6223, 1),
+    (6225, 162),
+    (6230, 1),
+    (6232, 321),
+    (6234, 170),
+    (6246, 1290),
+    (6248, 1755),
+    (6249, 4),
+    (6257, 162),
+    (6261, 4),
+    (6262, 1),
+    (6264, 4),
+    (6268, 616),
+    (6270, 141),
+    (6275, 1),
+    (6278, 1289),
+    (6280, 1),
+    (6281, 1),
+    (6284, 2516),
+    (6285, 73),
+    (6289, 162),
+    (6294, 1),
+    (6304, 409),
+    (6306, 163),
+    (6310, 1289),
+    (6314, 2),
+    (6320, 2276),
+    (6321, 210),
+    (6326, 1),
+    (6340, 445),
+    (6342, 1437),
+    (6353, 162),
+    (6356, 4090),
+    (6357, 55),
+    (6358, 1),
+    (6364, 1),
+    (6374, 1290),
+    (6376, 929),
+    (6378, 270),
+    (6385, 162),
+    (6390, 1),
+    (6392, 6135),
+    (6393, 16),
+    (6400, 1),
+    (6406, 1289),
+    (6412, 607),
+    (6414, 386),
+    (6417, 162),
+    (6420, 1),
+    (6421, 238),
+    (6422, 1),
+    (6424, 238),
+    (6428, 15189),
+    (6429, 227),
+    (6438, 1289),
+    (6443, 1),
+    (6448, 1211),
+    (6449, 162),
+    (6450, 1135),
+    (6453, 2),
+    (6454, 1),
+    (6464, 66588),
+    (6465, 77),
+    (6470, 1289),
+    (6474, 31),
+    (6481, 162),
+    (6484, 21001),
+    (6486, 9926),
+    (6488, 95),
+    (6498, 1),
+    (6500, 51017),
+    (6501, 2547),
+    (6502, 1289),
+    (6513, 162),
+    (6518, 1),
+    (6520, 11978),
+    (6522, 2546),
+    (6534, 1289),
+    (6536, 1),
+    (6537, 4),
+    (6539, 7),
+    (6545, 162),
+    (6546, 1),
+    (6550, 1),
+    (6553, 27),
+    (6566, 1289),
+    (6572, 1),
+    (6573, 2),
+    (6574, 1),
+    (6577, 163),
+    (6582, 2),
+    (6587, 1),
+    (6588, 17),
+    (6598, 1289),
+    (6600, 1),
+    (6603, 1),
+    (6605, 1),
+    (6606, 2),
+    (6608, 1),
+    (6609, 163),
+    (6610, 1),
+    (6614, 1),
+    (6623, 4),
+    (6630, 1289),
+    (6631, 1),
+    (6633, 1),
+    (6635, 1),
+    (6640, 1),
+    (6641, 162),
+    (6644, 1),
+    (6645, 2),
+    (6646, 2),
+    (6662, 1289),
+    (6666, 1),
+    (6670, 1),
+    (6673, 162),
+    (6678, 1),
+    (6679, 1),
+    (6680, 1),
+    (6681, 5),
+    (6686, 1),
+    (6694, 1289),
+    (6705, 162),
+    (6710, 1),
+    (6711, 1),
+    (6714, 1),
+    (6716, 1),
+    (6717, 10),
+    (6726, 1289),
+    (6734, 1),
+    (6737, 163),
+    (6738, 1),
+    (6740, 2),
+    (6742, 1),
+    (6752, 1),
+    (6753, 1),
+    (6757, 1),
+    (6758, 1289),
+    (6769, 162),
+    (6770, 1),
+    (6774, 1),
+    (6775, 1),
+    (6788, 1),
+    (6789, 3),
+    (6790, 1289),
+    (6797, 1),
+    (6801, 162),
+    (6802, 1),
+    (6803, 1),
+    (6806, 1),
+    (6818, 1),
+    (6819, 1),
+    (6822, 1289),
+    (6824, 1),
+    (6825, 5),
+    (6833, 162),
+    (6834, 1),
+    (6837, 1),
+    (6838, 1),
+    (6844, 2),
+    (6854, 1289),
+    (6860, 1),
+    (6861, 5),
+    (6865, 163),
+    (6869, 1),
+    (6870, 1),
+    (6872, 1),
+    (6875, 1),
+    (6881, 3),
+    (6886, 1289),
+    (6896, 1),
+    (6897, 166),
+    (6902, 1),
+    (6915, 1),
+    (6918, 1289),
+    (6929, 162),
+    (6932, 2),
+    (6933, 1),
+    (6934, 1),
+    (6947, 1),
+    (6950, 1290),
+    (6961, 162),
+    (6966, 1),
+    (6969, 2),
+    (6982, 1289),
+    (6993, 162),
+    (6998, 1),
+    (7004, 1),
+    (7005, 1),
+    (7014, 1289),
+    (7025, 162),
+    (7030, 1),
+    (7032, 1),
+    (7034, 1),
+    (7040, 1),
+    (7041, 1),
+    (7046, 1289),
+    (7057, 162),
+    (7058, 1),
+    (7059, 1),
+    (7062, 1),
+    (7070, 1),
+    (7076, 1),
+    (7077, 3),
+    (7078, 1289),
+    (7084, 1),
+    (7089, 162),
+    (7094, 1),
+    (7110, 1289),
+    (7112, 1),
+    (7113, 5),
+    (7121, 162),
+    (7124, 1),
+    (7126, 1),
+    (7133, 1),
+    (7142, 1289),
+    (7148, 1),
+    (7149, 12),
+    (7153, 162),
+    (7158, 1),
+    (7174, 1289),
+    (7184, 1),
+    (7185, 170),
+    (7190, 1),
+    (7206, 1289),
+    (7217, 162),
+    (7220, 1),
+    (7221, 82),
+    (7222, 1),
+    (7224, 81),
+    (7229, 1),
+    (7237, 1),
+    (7238, 1289),
+    (7242, 1),
+    (7243, 1),
+    (7248, 1),
+    (7249, 162),
+    (7254, 1),
+    (7256, 1),
+    (7257, 1),
+    (7266, 4),
+    (7270, 1289),
+    (7274, 13),
+    (7280, 20),
+    (7281, 162),
+    (7286, 1),
+    (7288, 12),
+    (7292, 1),
+    (7293, 5),
+    (7296, 1),
+    (7302, 1289),
+    (7308, 1),
+    (7313, 162),
+    (7315, 1),
+    (7318, 1),
+    (7328, 1),
+    (7329, 1),
+    (7334, 1290),
+    (7345, 162),
+    (7349, 1),
+    (7350, 1),
+    (7353, 1),
+    (7364, 1),
+    (7365, 1),
+    (7366, 1290),
+    (7377, 162),
+    (7382, 1),
+    (7392, 1),
+    (7398, 1289),
+    (7400, 1),
+    (7401, 4),
+    (7406, 1),
+    (7409, 162),
+    (7411, 1),
+    (7414, 1),
+    (7430, 1289),
+    (7431, 3),
+    (7436, 1),
+    (7437, 2),
+    (7441, 162),
+    (7445, 5),
+    (7446, 1),
+    (7448, 1),
+    (7460, 1),
+    (7462, 1289),
+    (7472, 1),
+    (7473, 166),
+    (7474, 1),
+    (7478, 1),
+    (7494, 1289),
+    (7505, 162),
+    (7508, 3),
+    (7509, 2),
+    (7510, 2),
+    (7525, 1),
+    (7526, 1289),
+    (7532, 1),
+    (7537, 162),
+    (7542, 1),
+    (7544, 1),
+    (7545, 9),
+    (7546, 1),
+    (7558, 1289),
+    (7569, 162),
+    (7574, 1),
+    (7580, 1),
+    (7581, 6),
+    (7590, 1289),
+    (7601, 162),
+    (7606, 1),
+    (7616, 1),
+    (7617, 6),
+    (7622, 1289),
+    (7623, 1),
+    (7625, 1),
+    (7633, 162),
+    (7638, 1),
+    (7652, 1),
+    (7653, 11),
+    (7654, 1289),
+    (7657, 1),
+    (7665, 162),
+    (7670, 1),
+    (7686, 1289),
+    (7688, 1),
+    (7689, 1),
+    (7697, 162),
+    (7702, 1),
+    (7708, 1),
+    (7715, 1),
+    (7717, 2),
+    (7718, 1289),
+    (7724, 1),
+    (7725, 3),
+    (7729, 162),
+    (7734, 1),
+    (7746, 1),
+    (7750, 1289),
+    (7760, 1),
+    (7761, 167),
+    (7766, 1),
+    (7782, 1289),
+    (7793, 162),
+    (7794, 1),
+    (7796, 1),
+    (7797, 1),
+    (7798, 1),
+    (7814, 1289),
+    (7820, 1),
+    (7825, 162),
+    (7826, 1),
+    (7830, 1),
+    (7832, 1),
+    (7833, 14),
+    (7842, 1),
+    (7846, 1289),
+    (7857, 162),
+    (7862, 1),
+    (7863, 1),
+    (7868, 1),
+    (7869, 4),
+    (7878, 1289),
+    (7885, 1),
+    (7889, 162),
+    (7894, 1),
+    (7904, 1),
+    (7905, 2),
+    (7910, 1289),
+    (7921, 162),
+    (7926, 1),
+    (7929, 1),
+    (7940, 1),
+    (7941, 2),
+    (7942, 1289),
+    (7953, 162),
+    (7958, 1),
+    (7963, 1),
+    (7973, 1),
+    (7974, 1289),
+    (7976, 1),
+    (7977, 16),
+    (7985, 162),
+    (7989, 1),
+    (7990, 1),
+    (7991, 1),
+    (7997, 1),
+    (8000, 1),
+    (8006, 1289),
+    (8012, 1),
+    (8013, 14),
+    (8017, 162),
+    (8022, 1),
+    (8038, 1289),
+    (8048, 1),
+    (8049, 185),
+    (8054, 2),
+    (8070, 1289),
+    (8081, 162),
+    (8084, 1),
+    (8085, 24),
+    (8086, 1),
+    (8102, 1289),
+    (8113, 162),
+    (8118, 1),
+    (8119, 1),
+    (8120, 1),
+    (8121, 1),
+    (8126, 1),
+    (8134, 1289),
+    (8140, 1),
+    (8145, 162),
+    (8150, 1),
+    (8157, 20),
+    (8166, 1289),
+    (8177, 162),
+    (8182, 1),
+    (8192, 1),
+    (8193, 1),
+    (8198, 1289),
+    (8209, 162),
+    (8214, 1),
+    (8228, 1),
+    (8229, 32),
+    (8230, 1290),
+    (8246, 1),
+    (8264, 1),
+    (8265, 27),
+    (8269, 1),
+    (8276, 1),
+    (8282, 1),
+    (8300, 1),
+    (8301, 133),
+    (8336, 2),
+    (8337, 60),
+    (8348, 3),
+    (8356, 1),
+    (8358, 1),
+    (8372, 1),
+    (8373, 196),
+    (8408, 1),
+    (8444, 1),
+    (8468, 1),
+    (8480, 1),
+    (8499, 1),
+    (8516, 1),
+    (8552, 1),
+    (8555, 1),
+    (8588, 1),
+    (8624, 1),
+    (8660, 3),
+    (8675, 1),
+    (8696, 1),
+    (8704, 1),
+    (8724, 1),
+    (8732, 1),
+    (8768, 1),
+    (8779, 1),
+    (8804, 1),
+    (8840, 1),
+    (8852, 2),
+    (8876, 1),
+    (8912, 1),
+    (8948, 1),
+    (8984, 1),
+    (9020, 1),
+    (9128, 1),
+    (9164, 1),
+    (9192, 1),
+    (9200, 2),
+    (9236, 1),
+    (9272, 1),
+    (9308, 1),
+    (9344, 1),
+    (9380, 1),
+    (9416, 1),
+    (9452, 1),
+    (9524, 1),
+    (9560, 1),
+    (9589, 1),
+    (9632, 1),
+    (9642, 1),
+    (9704, 1),
+    (9776, 1),
+    (9848, 1),
+    (9992, 1),
+    (10064, 1),
+    (10100, 1),
+    (10136, 1),
+    (10172, 1),
+    (10208, 1),
+    (10244, 1),
+    (10280, 1),
+    (10316, 1),
+    (10388, 1),
+    (10532, 1),
+    (10572, 1),
+    (10620, 1),
+    (10640, 1),
+    (10669, 1),
+    (10748, 1),
+    (10856, 1),
+    (10964, 1),
+    (11067, 1),
+    (11072, 1),
+    (11180, 1),
+    (11216, 1),
+    (11252, 1),
+    (11288, 1),
+    (11324, 1),
+    (11348, 2),
+    (11360, 1),
+    (11396, 1),
+    (11432, 1),
+    (11468, 1),
+    (11504, 1),
+    (11540, 1),
+    (11576, 1),
+    (11612, 1),
+    (11648, 1),
+    (11756, 1),
+    (11792, 1),
+    (11828, 1),
+    (11864, 1),
+    (11936, 1),
+    (12008, 1),
+    (12080, 1),
+    (12152, 1),
+    (12188, 1),
+    (12224, 1),
+    (12260, 1),
+    (12296, 1),
+    (12332, 1),
+    (12360, 1),
+    (12368, 1),
+    (12404, 1),
+    (12440, 1),
+    (12476, 1),
+    (12501, 2),
+    (12512, 1),
+    (12548, 1),
+    (12584, 1),
+    (12620, 1),
+    (12656, 1),
+    (12693, 1),
+    (12728, 1),
+    (12885, 1),
+    (13123, 1),
+    (13269, 1),
+    (13461, 1),
+    (13653, 1),
+    (13664, 1),
+    (13740, 1),
+    (13872, 1),
+    (13946, 1),
+    (14109, 1),
+    (14613, 2),
+    (14805, 2),
+    (14945, 1),
+    (14997, 1),
+    (15176, 1),
+    (15276, 1),
+    (15384, 1),
+    (15492, 1),
+    (15600, 1),
+    (15708, 1),
+    (15716, 1),
+    (15765, 1),
+    (15816, 1),
+    (15924, 1),
+    (16068, 1),
+    (16104, 1),
+    (16140, 1),
+    (16176, 1),
+    (16212, 1),
+    (16248, 1),
+    (16284, 1),
+    (16320, 1),
+    (16356, 1),
+    (16392, 1),
+    (16430, 1),
+    (16468, 1),
+    (16504, 1),
+    (16540, 1),
+    (16727, 2),
+    (16728, 1),
+    (16919, 2),
+    (16921, 1),
+    (16938, 1),
+    (17111, 6),
+    (17413, 1),
+    (17430, 1),
+    (17495, 1),
+    (17880, 1),
+    (18647, 2),
+    (18672, 1),
+    (19223, 38),
+    (19680, 1),
+    (20436, 1),
+    (21156, 1),
+    (21732, 1),
+    (22380, 1),
+    (22992, 1),
+    (23063, 17),
+    (23244, 1),
+    (23532, 1),
+    (23892, 1),
+    (24108, 1),
+    (24215, 1),
+    (24324, 1),
+    (24407, 2),
+    (24504, 1),
+    (24720, 1),
+    (24900, 1),
+    (24983, 205),
+    (25440, 1),
+    (25620, 1),
+    (26088, 1),
+    (26268, 1),
+    (26448, 1),
+    (26664, 1),
+    (26988, 1),
+    (27276, 1),
+    (27492, 1),
+    (27744, 1),
+    (28032, 1),
+    (28284, 1),
+    (28536, 1),
+    (28823, 42),
+    (28896, 1),
+    (29184, 1),
+    (29292, 1),
+    (29400, 1),
+    (29796, 1),
+    (29975, 4),
+    (30156, 1),
+    (30228, 1),
+    (30743, 238),
+    (30768, 1),
+    (31056, 1),
+    (31092, 1),
+    (31416, 1),
+    (32100, 1),
+    (32712, 1),
+    (33144, 1),
+    (33324, 1),
+    (33792, 1),
+    (34008, 1),
+    (34440, 1),
+    (34583, 81),
+    (34656, 1),
+    (34872, 1),
+    (34944, 1),
+    (35160, 1),
+    (35304, 1),
+    (35376, 1),
+    (35412, 1),
+    (35556, 1),
+    (35628, 1),
+    (35664, 1),
+    (35808, 1),
+    (36204, 1),
+    (36744, 1),
+    (37788, 1),
+    (39372, 1),
+    (40956, 1),
+    (41640, 1),
+    (41892, 1),
+    (42144, 1),
+    (42576, 1),
+    (42936, 1),
+    (43476, 1),
+    (45096, 1),
+    (47256, 1),
+    (47760, 1),
+    (47796, 1),
+    (47868, 1),
+    (48228, 1),
+    (48948, 1),
+    (49128, 1),
+    (49452, 1),
+    (49560, 1),
+    (49668, 1),
+    (49776, 1),
+    (50352, 1),
+    (50964, 1),
+    (52008, 1),
+    (53880, 1),
+    (55284, 1),
+    (55860, 1),
+    (56040, 1),
+    (56400, 1),
+    (56904, 1),
+    (57444, 1),
+    (59424, 1),
+    (60156, 1),
+    (60626, 1),
+    (60641, 1),
+    (61260, 1),
+    (62520, 1),
+    (64392, 1),
+    (65976, 1),
+    (67308, 1),
+    (68064, 1),
+    (68748, 1),
+    (69216, 1),
+    (69504, 1),
+    (69648, 1),
+    (69684, 1),
+    (69720, 1),
+    (69756, 1),
+    (69792, 1),
+    (69828, 1),
+    (70224, 1),
+    (70620, 1),
+    (71016, 1),
+    (71412, 1),
+    (71772, 1),
+    (71952, 1),
+    (72024, 1),
+    (72096, 1),
+    (72168, 1),
+    (72240, 1),
+    (72312, 1),
+    (72348, 1),
+    (72420, 1),
+    (72492, 1),
+    (72600, 1),
+    (72672, 1),
+    (72780, 1),
+    (72996, 1),
+    (73320, 1),
+    (73356, 1),
+    (73500, 1),
+    (73536, 1),
+    (73572, 1),
+    (73608, 1),
+    (73680, 1),
+    (73716, 1),
+    (73788, 1),
+    (73896, 1),
+    (74040, 1),
+    (74112, 1),
+    (74170, 1),
+    (74184, 1),
+    (74185, 1),
+    (74220, 1),
+    (74256, 1),
+    (74292, 1),
+    (74328, 1),
+    (74364, 1),
+    (74400, 1),
+    (74436, 1),
+    (74472, 1),
+    (74616, 1),
+    (74976, 1),
+    (75156, 1),
+    (75228, 1),
+    (75336, 1),
+    (75408, 1),
+    (75588, 1),
+    (75696, 1),
+    (75804, 1),
+    (75984, 1),
+    (76056, 1),
+    (76164, 1),
+    (76308, 1),
+    (76452, 1),
+    (76560, 1),
+    (76776, 1),
+    (76920, 1),
+    (77064, 1),
+    (77208, 1),
+    (77316, 1),
+    (77532, 1),
+    (77676, 1),
+    (77748, 1),
+    (77820, 1),
+    (77928, 1),
+    (78000, 1),
+    (78036, 1),
+    (78072, 1),
+    (78108, 1),
+    (78180, 1),
+    (78324, 1),
+    (78396, 1),
+    (78576, 1),
+    (78684, 1),
+    (78828, 1),
+    (78864, 1),
+    (78900, 1),
+    (78972, 1),
+    (79080, 1),
+    (79116, 1),
+    (79152, 1),
+    (79512, 1),
+    (79872, 1),
+    (80268, 1),
+    (80592, 1),
+    (80700, 1),
+    (80916, 1),
+    (81168, 1),
+    (81276, 1),
+    (81528, 1),
+    (81708, 1),
+    (81816, 1),
+    (81888, 1),
+    (82068, 1),
+    (82176, 1),
+    (82284, 1),
+    (82356, 1),
+    (82716, 1),
+    (83004, 1),
+    (83312, 1),
+    (83436, 1),
+    (83688, 1),
+    (83904, 1),
+    (84012, 1),
+    (84408, 1),
+    (84660, 1),
+    (85056, 1),
+    (85488, 1),
+    (85776, 1),
+    (85992, 1),
+    (86172, 1),
+    (86424, 1),
+    (86615, 1),
+    (86640, 1),
+    (86928, 1),
+    (87072, 1),
+    (87288, 1),
+    (87576, 1),
+    (87684, 1),
+    (87756, 1),
+    (87972, 1),
+    (88044, 1),
+    (88152, 1),
+    (88368, 1),
+    (88728, 1),
+    (88836, 1),
+    (88944, 1),
+    (89088, 1),
+    (89448, 1),
+    (89592, 1),
+    (89700, 1),
+    (89808, 1),
+    (89952, 1),
+    (90060, 1),
+    (90204, 1),
+    (90348, 1),
+    (90528, 1),
+    (90636, 1),
+    (90744, 1),
+    (90816, 1),
+    (91032, 1),
+    (91068, 1),
+    (91140, 1),
+    (91212, 1),
+    (91284, 1),
+    (91860, 1),
+    (92112, 1),
+    (92292, 1),
+    (92400, 1),
+    (92544, 1),
+    (92652, 1),
+    (92796, 1),
+    (92904, 1),
+    (92976, 1),
+    (93192, 1),
+    (93300, 1),
+    (93444, 1),
+    (93516, 1),
+    (93624, 1),
+    (93696, 1),
+    (93840, 1),
+    (93984, 1),
+    (94056, 1),
+    (94128, 1),
+    (94164, 1),
+    (94200, 1),
+    (94236, 1),
+    (94272, 1),
+    (94344, 1),
+    (94452, 1),
+    (94524, 1),
+    (94596, 1),
+    (94704, 1),
+    (94776, 1),
+    (94884, 1),
+    (94956, 1),
+    (95172, 1),
+    (95244, 1),
+    (95280, 1),
+    (95316, 1),
+    (95352, 1),
+    (95388, 1),
+    (95424, 1),
+    (95460, 1),
+    (95496, 1),
+    (95604, 1),
+    (95676, 1),
+    (95784, 1),
+    (95856, 1),
+    (95928, 1),
+    (96000, 1),
+    (96036, 1),
+    (96072, 1),
+    (96108, 1),
+    (96144, 1),
+    (96180, 1),
+    (96216, 1),
+    (96288, 1),
+    (96576, 1),
+    (98029, 1),
+    (98304, 1),
+    (98527, 1),
+    (98628, 1),
+    (99276, 1),
+    (99528, 1),
+    (99780, 1),
+    (99996, 1),
+    (100212, 1),
+    (100428, 1),
+    (100680, 1),
+    (100752, 1),
+    (100788, 1),
+    (100860, 1),
+    (100932, 1),
+    (101004, 1),
+    (101076, 1),
+    (101148, 1),
+    (101220, 1),
+    (101256, 1),
+    (101328, 1),
+    (101364, 1),
+    (101400, 1),
+    (101436, 1),
+    (101472, 1),
+    (101508, 1),
+    (101544, 1),
+    (101616, 1),
+    (101652, 1),
+    (101724, 1),
+    (101832, 1),
+    (101904, 1),
+    (101940, 1),
+    (101976, 1),
+    (102012, 1),
+    (102048, 1),
+    (102084, 1),
+    (102120, 1),
+    (102264, 1),
+    (102516, 1),
+    (102588, 1),
+    (102624, 1),
+    (102660, 1),
+    (102696, 1),
+    (102732, 1),
+    (102768, 1),
+    (102804, 1),
+    (102840, 1),
+    (102876, 1),
+    (102912, 1),
+    (102948, 1),
+    (102984, 1),
+    (103056, 1),
+    (103092, 1),
+    (103128, 1),
+    (103164, 1),
+    (103200, 1),
+    (103236, 1),
+    (103272, 1),
+    (103308, 1),
+    (103344, 1),
+    (103380, 1),
+    (103452, 1),
+    (103560, 1),
+    (103596, 1),
+    (103632, 1),
+    (103668, 1),
+    (103704, 1),
+    (103740, 1),
+    (103776, 1),
+    (103848, 1),
+    (103920, 1),
+    (103956, 1),
+    (104028, 1),
+    (104100, 1),
+    (104136, 1),
+    (104208, 1),
+    (104244, 1),
+    (104316, 1),
+    (104352, 1),
+    (104388, 1),
+    (104424, 1),
+    (104460, 1),
+    (104496, 1),
+    (104532, 1),
+    (104568, 1),
+    (104604, 1),
+    (104676, 1),
+    (104712, 1),
+    (104748, 1),
+    (104784, 1),
+    (104820, 1),
+    (104856, 1),
+    (104892, 1),
+    (104928, 1),
+    (104964, 1),
+    (105000, 1),
+    (105036, 1),
+    (105072, 1),
+    (105108, 1),
+    (105216, 1),
+    (105324, 1),
+    (105360, 1),
+    (105396, 1),
+    (105432, 1),
+    (105468, 1),
+    (105504, 1),
+    (105540, 1),
+    (105576, 1),
+    (105612, 1),
+    (105648, 1),
+    (105684, 1),
+    (105720, 1),
+    (105756, 1),
+    (105792, 1),
+    (105828, 1),
+    (105864, 1),
+    (105900, 1),
+    (105936, 1),
+    (110580, 1),
+    (115224, 1),
+    (118788, 1),
+    (121056, 1),
+    (121452, 1),
+    (121848, 1),
+    (122244, 1),
+    (122604, 1),
+    (122928, 1),
+    (123252, 1),
+    (123288, 1),
+    (123360, 1),
+    (123432, 1),
+    (123468, 1),
+    (123504, 1),
+    (123540, 1),
+    (123612, 1),
+    (123684, 1),
+    (123756, 1),
+    (123828, 1),
+    (123900, 1),
+    (123972, 1),
+    (124080, 1),
+    (124188, 1),
+    (124296, 1),
+    (124404, 1),
+    (124548, 1),
+    (124764, 1),
+    (124872, 1),
+    (124980, 1),
+    (125088, 1),
+    (125196, 1),
+    (125304, 1),
+    (125412, 1),
+    (125448, 1),
+    (125520, 1),
+    (125628, 1),
+    (125700, 1),
+    (125772, 1),
+    (125844, 1),
+    (125880, 1),
+    (125916, 1),
+    (125952, 1),
+    (125988, 1),
+    (126024, 1),
+    (126060, 1),
+    (126096, 1),
+    (126168, 1),
+    (126276, 1),
+    (126312, 1),
+    (126348, 1),
+    (126420, 1),
+    (126492, 1),
+    (126564, 1),
+    (126636, 1),
+    (126708, 1),
+    (126780, 1),
+    (126852, 1),
+    (126960, 1),
+    (127068, 1),
+    (127176, 1),
+    (127212, 1),
+    (127248, 1),
+    (127284, 1),
+    (127320, 1),
+    (127356, 1),
+    (127392, 1),
+    (127464, 1),
+    (127536, 1),
+    (127608, 1),
+    (127644, 1),
+    (127680, 1),
+    (127716, 1),
+    (127788, 1),
+    (127860, 1),
+    (127932, 1),
+    (128004, 1),
+    (128076, 1),
+    (128148, 1),
+    (128220, 1),
+    (128256, 1),
+    (128292, 1),
+    (128328, 1),
+    (128364, 1),
+    (128400, 1),
+    (128436, 1),
+    (128472, 1),
+    (128508, 1),
+    (128544, 1),
+    (128580, 1),
+    (128616, 1),
+    (128652, 1),
+    (128688, 1),
+    (128724, 1),
+    (128760, 1),
+    (128832, 1),
+    (128904, 1),
+    (128976, 1),
+    (129048, 1),
+    (129120, 1),
+    (129192, 1),
+    (129228, 1),
+    (129264, 1),
+    (129300, 1),
+    (129372, 1),
+    (129408, 1),
+    (129444, 1),
+    (129480, 1),
+    (129516, 1),
+    (129552, 1),
+    (129588, 1),
+    (129660, 1),
+    (129732, 1),
+    (129768, 1),
+    (129804, 1),
+    (129840, 1),
+    (129876, 1),
+    (129912, 1),
+    (129948, 1),
+    (129984, 1),
+    (130020, 1),
+    (130056, 1),
+    (130128, 1),
+    (130200, 1),
+    (130236, 1),
+    (130272, 1),
+    (130308, 1),
+    (130380, 1),
+    (130452, 1),
+    (130524, 1),
+    (130560, 1),
+    (130596, 1),
+    (130632, 1),
+    (130668, 1),
+    (130704, 1),
+    (130776, 1),
+    (130812, 1),
+    (130848, 1),
+    (130920, 1),
+    (130992, 1),
+    (131064, 1),
+    (131136, 1),
+    (131172, 1),
+    (131208, 1),
+    (131244, 1),
+    (131316, 1),
+    (131388, 1),
+    (131424, 1),
+    (131532, 1),
+    (131640, 1),
+    (131784, 1),
+    (131892, 1),
+    (131964, 1),
+    (132036, 1),
+    (132108, 1),
+    (132180, 1),
+    (132252, 1),
+    (132324, 1),
+    (132360, 1),
+    (132432, 1),
+    (132504, 1),
+    (132576, 1),
+    (132684, 1),
+    (132792, 1),
+    (132900, 1),
+    (132972, 1),
+    (133044, 1),
+    (133116, 1),
+    (133188, 1),
+    (133260, 1),
+    (133332, 1),
+    (133368, 1),
+    (133404, 1),
+    (133440, 1),
+    (133476, 1),
+    (133512, 1),
+    (133548, 1),
+    (133620, 1),
+    (133692, 1),
+    (133764, 1),
+    (133836, 1),
+    (133908, 1),
+    (133980, 1),
+    (134016, 1),
+    (134052, 1),
+    (134088, 1),
+    (134124, 1),
+    (134160, 1),
+    (134196, 1),
+    (134232, 1),
+    (134268, 1),
+    (134304, 1),
+    (134340, 1),
+    (134376, 1),
+    (134412, 1),
+    (134484, 1),
+    (134592, 1),
+    (134700, 1),
+    (134808, 1),
+    (134916, 1),
+    (134988, 1),
+    (135024, 1),
+    (135060, 1),
+    (135096, 1),
+    (135132, 1),
+    (135168, 1),
+    (135204, 1),
+    (135240, 1),
+    (135276, 1),
+    (135312, 1),
+    (135348, 1),
+    (135384, 1),
+    (135456, 1),
+    (135492, 1),
+    (135528, 1),
+    (135564, 1),
+    (135600, 1),
+    (135636, 1),
+    (135672, 1),
+    (135708, 1),
+    (135744, 1),
+    (135780, 1),
+    (135816, 1),
+    (135852, 1),
+    (135888, 1),
+    (135924, 1),
+    (135960, 1),
+    (135996, 1),
+    (136032, 1),
+    (136068, 1),
+    (136140, 1),
+    (136212, 1),
+    (136284, 1),
+    (136356, 1),
+    (136428, 1),
+    (136500, 1),
+    (136572, 1),
+    (136608, 1),
+    (136644, 1),
+    (136680, 1),
+    (136716, 1),
+    (136752, 1),
+    (136788, 1),
+    (136824, 1),
+    (136860, 1),
+    (136896, 1),
+    (136932, 1),
+    (136968, 1),
+    (137004, 1),
+    (137040, 1),
+    (137076, 1),
+    (137112, 1),
+    (137148, 1),
+    (137184, 1),
+    (137256, 1),
+    (137328, 1),
+    (137400, 1),
+    (137472, 1),
+    (137544, 1),
+    (137580, 1),
+    (137616, 1),
+    (137652, 1),
+    (137688, 1),
+    (137724, 1),
+    (137796, 1),
+    (137832, 1),
+    (137868, 1),
+    (137904, 1),
+    (137940, 1),
+    (137976, 1),
+    (138012, 1),
+    (138048, 1),
+    (138084, 1),
+    (138120, 1),
+    (138228, 1),
+    (138300, 1),
+    (138336, 1),
+    (138372, 1),
+    (138408, 1),
+    (138444, 1),
+    (138480, 1),
+    (138516, 1),
+    (138552, 1),
+    (138588, 1),
+    (138624, 1),
+    (138696, 1),
+    (138768, 1),
+    (138840, 1),
+    (138912, 1),
+    (138948, 1),
+    (138984, 1),
+    (139020, 1),
+    (139056, 1),
+    (139092, 1),
+    (139128, 1),
+    (139164, 1),
+    (139200, 1),
+    (139272, 1),
+    (139308, 1),
+    (139380, 1),
+    (139452, 1),
+    (139488, 1),
+    (139524, 1),
+    (139596, 1),
+    (139632, 1),
+    (139668, 1),
+    (139704, 1),
+    (139740, 1),
+    (139776, 1),
+    (139848, 1),
+    (139884, 1),
+    (139920, 1),
+    (139956, 1),
+    (139992, 1),
+    (140028, 1),
+    (140064, 1),
+    (140136, 1),
+    (140172, 1),
+    (140208, 1),
+    (140244, 1),
+    (140280, 1),
+    (140316, 1),
+    (140352, 1),
+    (140424, 1),
+    (140460, 1),
+    (140496, 1),
+    (140532, 1),
+    (140604, 1),
+    (140640, 1),
+    (140676, 1),
+    (140712, 1),
+    (140748, 1),
+    (140784, 1),
+    (140820, 1),
+    (140856, 1),
+    (140928, 1),
+    (141036, 1),
+    (141072, 1),
+    (141108, 1),
+    (141144, 1),
+    (141180, 1),
+    (141216, 1),
+    (141252, 1),
+    (141324, 1),
+    (141396, 1),
+    (141432, 1),
+    (141468, 1),
+    (141504, 1),
+    (141612, 1),
+    (142152, 1),
+    (142188, 1),
+    (142260, 1),
+    (142296, 1),
+    (142800, 1),
+    (143304, 1),
+    (143376, 1),
+    (143448, 1),
+    (143520, 1),
+    (143592, 1),
+    (143664, 1),
+    (143700, 1),
+    (143736, 1),
+    (143772, 1),
+    (143808, 1),
+    (143844, 1),
+    (143880, 1),
+    (143952, 1),
+    (144096, 1),
+    (144240, 1),
+    (144348, 1),
+    (144456, 1),
+    (144564, 1),
+    (144672, 1),
+    (144708, 1),
+    (144744, 1),
+    (144780, 1),
+    (144816, 1),
+    (144852, 1),
+    (144888, 1),
+    (144924, 1),
+    (144960, 1),
+    (144996, 1),
+    (145032, 1),
+    (145068, 1),
+    (145104, 1),
+    (145140, 1),
+    (145176, 1),
+    (145212, 1),
+    (145248, 1),
+    (145284, 1),
+    (145320, 1),
+    (145356, 1),
+    (145392, 1),
+    (145464, 1),
+    (145500, 1),
+    (145536, 1),
+    (145572, 1),
+    (145644, 1),
+    (145716, 1),
+    (145752, 1),
+    (145788, 1),
+    (145824, 1),
+    (145860, 1),
+    (145896, 1),
+    (145932, 1),
+    (145968, 1),
+    (146004, 1),
+    (146040, 1),
+    (146076, 1),
+    (146112, 1),
+    (146148, 1),
+    (146184, 1),
+    (146220, 1),
+    (146256, 1),
+    (146292, 1),
+    (146328, 1),
+    (146364, 1),
+    (146400, 1),
+    (146436, 1),
+    (146472, 1),
+    (146508, 1),
+    (146544, 1),
+    (146580, 1),
+    (146616, 1),
+    (146652, 1),
+    (146688, 1),
+    (146724, 1),
+    (146760, 1),
+    (146796, 1),
+    (146832, 1),
+    (146868, 1),
+    (146940, 1),
+    (146976, 1),
+    (147012, 1),
+    (147048, 1),
+    (147084, 1),
+    (147120, 1),
+    (147156, 1),
+    (147192, 1),
+    (147228, 1),
+    (147264, 1),
+    (147300, 1),
+    (147336, 1),
+    (147372, 1),
+    (147408, 1),
+    (147444, 1),
+    (147480, 1),
+    (147516, 1),
+    (147552, 1),
+    (147588, 1),
+    (147624, 1),
+    (147660, 1),
+    (147732, 1),
+    (147768, 1),
+    (147804, 1),
+    (147840, 1),
+    (147876, 1),
+    (147912, 1),
+    (147948, 1),
+    (147984, 1),
+    (148020, 1),
+    (148056, 1),
+    (148092, 1),
+    (148128, 1),
+    (148164, 1),
+    (148200, 1),
+    (148236, 1),
+    (148272, 1),
+    (1070556, 1),
+    (1079378, 1),
+    (1085421, 1),
+    (1086835, 1),
+    (1121118, 1),
+    (1121208, 1),
+    (1124515, 1),
+    (1128287, 1),
+    (1128379, 1),
+    (1153308, 1),
+    (1153342, 4),
+    (1153344, 5),
+    (1153398, 1),
+    (1153571, 1),
+    (1153663, 1),
+    (1153670, 1),
+    (1153672, 3),
+    (1153688, 3),
+    (1154504, 1),
+    (1154538, 5),
+    (1154540, 6),
+    (1154596, 1),
+    (1164963, 1),
+    (1165053, 1),
+    (1166494, 1),
+    (1166586, 1),
+    (1175528, 1),
+    (1175636, 1),
+    (1177016, 1),
+    (1193653, 1),
+    (1193743, 1),
+    (1205060, 1),
+    (1205152, 1),
+    (1323322, 1),
+    (1323414, 1),
+    (1336354, 1),
+    (1336444, 1),
+    (1348925, 1),
+    (1349015, 1),
+    (1353326, 1),
+    (1353418, 1),
+    (1426757, 1),
+    (1426845, 1),
+    (1426847, 1),
+    (1426937, 1),
+    (1476463, 1),
+    (1476553, 1),
+    (1516580, 1),
+    (1516670, 1),
+    (1605731, 1),
+    (1605821, 1),
+];

+ 131 - 0
analyses/bench/src/tempdb.rs

@@ -0,0 +1,131 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use kvdb::{DBTransaction, KeyValueDB};
+use kvdb_rocksdb::{Database, DatabaseConfig};
+use std::{io, path::PathBuf, sync::Arc};
+
+#[derive(Debug, Clone, Copy, derive_more::Display)]
+pub enum DatabaseType {
+    RocksDb,
+}
+
+pub struct TempDatabase(tempfile::TempDir);
+
+struct ParityDbWrapper(parity_db::Db);
+parity_util_mem::malloc_size_of_is_0!(ParityDbWrapper);
+
+impl KeyValueDB for ParityDbWrapper {
+    /// Get a value by key.
+    fn get(&self, col: u32, key: &[u8]) -> io::Result<Option<Vec<u8>>> {
+        Ok(self
+            .0
+            .get(col as u8, &key[key.len() - 32..])
+            .expect("db error"))
+    }
+
+    /// Get a value by partial key. Only works for flushed data.
+    fn get_by_prefix(&self, _col: u32, _prefix: &[u8]) -> Option<Box<[u8]>> {
+        unimplemented!()
+    }
+
+    /// Write a transaction of changes to the buffer.
+    fn write(&self, transaction: DBTransaction) -> io::Result<()> {
+        self.0
+            .commit(transaction.ops.iter().map(|op| match op {
+                kvdb::DBOp::Insert { col, key, value } => {
+                    (*col as u8, &key[key.len() - 32..], Some(value.to_vec()))
+                }
+                kvdb::DBOp::Delete { col, key } => (*col as u8, &key[key.len() - 32..], None),
+                kvdb::DBOp::DeletePrefix { col: _, prefix: _ } => unimplemented!(),
+            }))
+            .expect("db error");
+        Ok(())
+    }
+
+    /// Iterate over flushed data for a given column.
+    #[allow(clippy::type_complexity)]
+    fn iter<'a>(&'a self, _col: u32) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + 'a> {
+        unimplemented!()
+    }
+
+    /// Iterate over flushed data for a given column, starting from a given prefix.
+    #[allow(clippy::type_complexity)]
+    fn iter_with_prefix<'a>(
+        &'a self,
+        _col: u32,
+        _prefix: &'a [u8],
+    ) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + 'a> {
+        unimplemented!()
+    }
+
+    /// Attempt to replace this database with a new one located at the given path.
+    fn restore(&self, _new_db: &str) -> io::Result<()> {
+        unimplemented!()
+    }
+}
+
+impl TempDatabase {
+    pub fn new() -> Self {
+        let dir = tempfile::tempdir().expect("temp dir creation failed");
+        log::trace!(
+            target: "bench-logistics",
+            "Created temp db at {}",
+            dir.path().to_string_lossy(),
+        );
+
+        TempDatabase(dir)
+    }
+
+    pub fn open(&mut self, db_type: DatabaseType) -> Arc<dyn KeyValueDB> {
+        match db_type {
+            DatabaseType::RocksDb => {
+                let db_cfg = DatabaseConfig::with_columns(1);
+                let db = Database::open(&db_cfg, &self.0.path().to_string_lossy())
+                    .expect("Database backend error");
+                Arc::new(db)
+            }
+        }
+    }
+}
+
+impl Clone for TempDatabase {
+    fn clone(&self) -> Self {
+        let new_dir = tempfile::tempdir().expect("temp dir creation failed");
+        let self_dir = self.0.path();
+
+        log::trace!(
+            target: "bench-logistics",
+            "Cloning db ({}) to {}",
+            self_dir.to_string_lossy(),
+            new_dir.path().to_string_lossy(),
+        );
+        let self_db_files = std::fs::read_dir(self_dir)
+            .expect("failed to list file in seed dir")
+            .map(|f_result| f_result.expect("failed to read file in seed db").path())
+            .collect::<Vec<PathBuf>>();
+        fs_extra::copy_items(
+            &self_db_files,
+            new_dir.path(),
+            &fs_extra::dir::CopyOptions::new(),
+        )
+        .expect("Copy of seed database is ok");
+
+        TempDatabase(new_dir)
+    }
+}

+ 355 - 0
analyses/bench/src/trie.rs

@@ -0,0 +1,355 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+//! Trie benchmark (integrated).
+
+use hash_db::Prefix;
+use kvdb::KeyValueDB;
+use lazy_static::lazy_static;
+use rand::Rng;
+use sp_state_machine::Backend as _;
+use sp_trie::{trie_types::TrieDBMut, TrieMut as _};
+use std::{borrow::Cow, collections::HashMap, sync::Arc};
+
+use node_primitives::Hash;
+
+use crate::{
+    common::DatabaseSize,
+    core::{self, Mode, Path},
+    generator::generate_trie,
+    simple_trie::SimpleTrie,
+    tempdb::{DatabaseType, TempDatabase},
+};
+
+pub const SAMPLE_SIZE: usize = 100;
+pub const TEST_WRITE_SIZE: usize = 128;
+
+pub type KeyValue = (Vec<u8>, Vec<u8>);
+pub type KeyValues = Vec<KeyValue>;
+
+lazy_static! {
+    static ref KUSAMA_STATE_DISTRIBUTION: SizePool =
+        SizePool::from_histogram(crate::state_sizes::KUSAMA_STATE_DISTRIBUTION);
+}
+
+fn pretty_print(v: usize) -> String {
+    let mut print = String::new();
+    for (idx, val) in v.to_string().chars().rev().enumerate() {
+        if idx != 0 && idx % 3 == 0 {
+            print.insert(0, ',');
+        }
+        print.insert(0, val);
+    }
+    print
+}
+
+pub struct TrieReadBenchmarkDescription {
+    pub database_size: DatabaseSize,
+    pub database_type: DatabaseType,
+}
+
+pub struct TrieReadBenchmark {
+    database: TempDatabase,
+    root: Hash,
+    warmup_keys: KeyValues,
+    query_keys: KeyValues,
+    database_type: DatabaseType,
+}
+
+impl core::BenchmarkDescription for TrieReadBenchmarkDescription {
+    fn path(&self) -> Path {
+        let mut path = Path::new(&["trie", "read"]);
+        path.push(&format!("{}", self.database_size));
+        path
+    }
+
+    fn setup(self: Box<Self>) -> Box<dyn core::Benchmark> {
+        let mut database = TempDatabase::new();
+
+        let mut rng = rand::thread_rng();
+        let warmup_prefix = KUSAMA_STATE_DISTRIBUTION.key(&mut rng);
+
+        let mut key_values = KeyValues::new();
+        let mut warmup_keys = KeyValues::new();
+        let mut query_keys = KeyValues::new();
+        let every_x_key = self.database_size.keys() / SAMPLE_SIZE;
+        for idx in 0..self.database_size.keys() {
+            let kv = (
+                KUSAMA_STATE_DISTRIBUTION.key(&mut rng).to_vec(),
+                KUSAMA_STATE_DISTRIBUTION.value(&mut rng),
+            );
+            if idx % every_x_key == 0 {
+                // warmup keys go to separate tree with high prob
+                let mut actual_warmup_key = warmup_prefix.clone();
+                actual_warmup_key[16..].copy_from_slice(&kv.0[16..]);
+                warmup_keys.push((actual_warmup_key.clone(), kv.1.clone()));
+                key_values.push((actual_warmup_key.clone(), kv.1.clone()));
+            } else if idx % every_x_key == 1 {
+                query_keys.push(kv.clone());
+            }
+
+            key_values.push(kv)
+        }
+
+        assert_eq!(warmup_keys.len(), SAMPLE_SIZE);
+        assert_eq!(query_keys.len(), SAMPLE_SIZE);
+
+        let root = generate_trie(database.open(self.database_type), key_values);
+
+        Box::new(TrieReadBenchmark {
+            database,
+            root,
+            warmup_keys,
+            query_keys,
+            database_type: self.database_type,
+        })
+    }
+
+    fn name(&self) -> Cow<'static, str> {
+        format!(
+            "Trie read benchmark({} database ({} keys), db_type: {})",
+            self.database_size,
+            pretty_print(self.database_size.keys()),
+            self.database_type,
+        )
+        .into()
+    }
+}
+
+struct Storage(Arc<dyn KeyValueDB>);
+
+impl sp_state_machine::Storage<sp_core::Blake2Hasher> for Storage {
+    fn get(&self, key: &Hash, prefix: Prefix) -> Result<Option<Vec<u8>>, String> {
+        let key = sp_trie::prefixed_key::<sp_core::Blake2Hasher>(key, prefix);
+        self.0
+            .get(0, &key)
+            .map_err(|e| format!("Database backend error: {:?}", e))
+    }
+}
+
+impl core::Benchmark for TrieReadBenchmark {
+    fn run(&mut self, mode: Mode) -> Vec<(usize, std::time::Duration)> {
+        let mut db = self.database.clone();
+
+        let storage: Arc<dyn sp_state_machine::Storage<sp_core::Blake2Hasher>> =
+            Arc::new(Storage(db.open(self.database_type)));
+
+        let trie_backend = sp_state_machine::TrieBackend::new(storage, self.root);
+        for (warmup_key, warmup_value) in self.warmup_keys.iter() {
+            let value = trie_backend
+                .storage(&warmup_key[..])
+                .expect("Failed to get key: db error")
+                .expect("Warmup key should exist");
+
+            // sanity for warmup keys
+            assert_eq!(&value, warmup_value);
+        }
+
+        if mode == Mode::Profile {
+            std::thread::park_timeout(std::time::Duration::from_secs(3));
+        }
+
+        let mut result = Vec::new();
+        for (key, _) in self.query_keys.iter() {
+            let started = std::time::Instant::now();
+            let val = trie_backend.storage(&key[..]);
+            result.push((
+                val.expect("Storage error")
+                    .expect("Value shouldn't be None")
+                    .len(),
+                started.elapsed(),
+            ));
+        }
+
+        if mode == Mode::Profile {
+            std::thread::park_timeout(std::time::Duration::from_secs(1));
+        }
+
+        result
+    }
+}
+
+pub struct TrieWriteBenchmarkDescription {
+    pub database_size: DatabaseSize,
+    pub database_type: DatabaseType,
+}
+
+impl core::BenchmarkDescription for TrieWriteBenchmarkDescription {
+    fn path(&self) -> Path {
+        let mut path = Path::new(&["trie", "write"]);
+        path.push(&format!("{}", self.database_size));
+        path
+    }
+
+    fn setup(self: Box<Self>) -> Box<dyn core::Benchmark> {
+        let mut database = TempDatabase::new();
+
+        let mut rng = rand::thread_rng();
+        let warmup_prefix = KUSAMA_STATE_DISTRIBUTION.key(&mut rng);
+
+        let mut key_values = KeyValues::new();
+        let mut warmup_keys = KeyValues::new();
+        let every_x_key = self.database_size.keys() / SAMPLE_SIZE;
+        for idx in 0..self.database_size.keys() {
+            let kv = (
+                KUSAMA_STATE_DISTRIBUTION.key(&mut rng).to_vec(),
+                KUSAMA_STATE_DISTRIBUTION.value(&mut rng),
+            );
+            if idx % every_x_key == 0 {
+                // warmup keys go to separate tree with high prob
+                let mut actual_warmup_key = warmup_prefix.clone();
+                actual_warmup_key[16..].copy_from_slice(&kv.0[16..]);
+                warmup_keys.push((actual_warmup_key.clone(), kv.1.clone()));
+                key_values.push((actual_warmup_key.clone(), kv.1.clone()));
+            }
+
+            key_values.push(kv)
+        }
+
+        assert_eq!(warmup_keys.len(), SAMPLE_SIZE);
+
+        let root = generate_trie(database.open(self.database_type), key_values);
+
+        Box::new(TrieWriteBenchmark {
+            database,
+            root,
+            warmup_keys,
+            database_type: self.database_type,
+        })
+    }
+
+    fn name(&self) -> Cow<'static, str> {
+        format!(
+            "Trie write benchmark({} database ({} keys), db_type = {})",
+            self.database_size,
+            pretty_print(self.database_size.keys()),
+            self.database_type,
+        )
+        .into()
+    }
+}
+
+struct TrieWriteBenchmark {
+    database: TempDatabase,
+    root: Hash,
+    warmup_keys: KeyValues,
+    database_type: DatabaseType,
+}
+
+impl core::Benchmark for TrieWriteBenchmark {
+    fn run(&mut self, mode: Mode) -> Vec<(usize, std::time::Duration)> {
+        let mut rng = rand::thread_rng();
+        let mut db = self.database.clone();
+        let kvdb = db.open(self.database_type);
+
+        let mut new_root = self.root;
+
+        let mut overlay = HashMap::new();
+        let mut trie = SimpleTrie {
+            db: kvdb.clone(),
+            overlay: &mut overlay,
+        };
+        let mut trie_db_mut =
+            TrieDBMut::from_existing(&mut trie, &mut new_root).expect("Failed to create TrieDBMut");
+
+        for (warmup_key, warmup_value) in self.warmup_keys.iter() {
+            let value = trie_db_mut
+                .get(&warmup_key[..])
+                .expect("Failed to get key: db error")
+                .expect("Warmup key should exist");
+
+            // sanity for warmup keys
+            assert_eq!(&value, warmup_value);
+        }
+
+        let test_key = random_vec(&mut rng, 32);
+        let test_val = random_vec(&mut rng, TEST_WRITE_SIZE);
+
+        if mode == Mode::Profile {
+            std::thread::park_timeout(std::time::Duration::from_secs(3));
+        }
+
+        let started = std::time::Instant::now();
+
+        trie_db_mut
+            .insert(&test_key, &test_val)
+            .expect("Should be inserted ok");
+        trie_db_mut.commit();
+        drop(trie_db_mut);
+
+        let mut transaction = kvdb.transaction();
+        for (key, value) in overlay.into_iter() {
+            match value {
+                Some(value) => transaction.put(0, &key[..], &value[..]),
+                None => transaction.delete(0, &key[..]),
+            }
+        }
+        kvdb.write(transaction)
+            .expect("Failed to write transaction");
+
+        let elapsed = started.elapsed();
+
+        // sanity check
+        assert!(new_root != self.root);
+
+        if mode == Mode::Profile {
+            std::thread::park_timeout(std::time::Duration::from_secs(1));
+        }
+
+        vec![(TEST_WRITE_SIZE, elapsed)]
+    }
+}
+
+fn random_vec<R: Rng>(rng: &mut R, len: usize) -> Vec<u8> {
+    let mut val = vec![0u8; len];
+    rng.fill_bytes(&mut val[..]);
+    val
+}
+
+struct SizePool {
+    distribution: std::collections::BTreeMap<u32, u32>,
+    total: u32,
+}
+
+impl SizePool {
+    fn from_histogram(h: &[(u32, u32)]) -> SizePool {
+        let mut distribution = std::collections::BTreeMap::default();
+        let mut total = 0;
+        for (size, count) in h {
+            total += count;
+            distribution.insert(total, *size);
+        }
+        SizePool {
+            distribution,
+            total,
+        }
+    }
+
+    fn value<R: Rng>(&self, rng: &mut R) -> Vec<u8> {
+        let sr = (rng.next_u64() % self.total as u64) as u32;
+        let mut range = self
+            .distribution
+            .range((std::ops::Bound::Included(sr), std::ops::Bound::Unbounded));
+        let size = *range.next().unwrap().1 as usize;
+        random_vec(rng, size)
+    }
+
+    fn key<R: Rng>(&self, rng: &mut R) -> Vec<u8> {
+        random_vec(rng, 32)
+    }
+}

+ 370 - 0
analyses/bench/src/trie_series.rs

@@ -0,0 +1,370 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+//! Trie benchmark (integrated).
+
+use hash_db::Prefix;
+use kvdb::KeyValueDB;
+use lazy_static::lazy_static;
+use rand::Rng;
+use sp_state_machine::Backend as _;
+use sp_trie::{trie_types::TrieDBMut, TrieMut as _};
+use std::{borrow::Cow, collections::HashMap, sync::Arc};
+
+use node_primitives::Hash;
+
+use crate::{
+    common::DatabaseSize,
+    core::{self, Mode, Path},
+    generator::generate_trie,
+    simple_trie::SimpleTrie,
+    tempdb::{DatabaseType, TempDatabase},
+};
+
+pub const SAMPLE_SIZE: usize = 50;
+
+pub type KeyValue = (Vec<u8>, Vec<u8>);
+pub type KeyValues = Vec<KeyValue>;
+
+lazy_static! {
+    static ref KUSAMA_STATE_DISTRIBUTION: SizePool =
+        SizePool::from_histogram(crate::state_sizes::KUSAMA_STATE_DISTRIBUTION);
+}
+
+fn pretty_print(v: usize) -> String {
+    let mut print = String::new();
+    for (idx, val) in v.to_string().chars().rev().enumerate() {
+        if idx != 0 && idx % 3 == 0 {
+            print.insert(0, ',');
+        }
+        print.insert(0, val);
+    }
+    print
+}
+
+pub struct TrieReadSeriesBenchmarkDescription {
+    pub database_size: DatabaseSize,
+    pub database_type: DatabaseType,
+    pub max_sample_size: usize,
+}
+
+struct TrieReadSeriesBenchmark {
+    database: TempDatabase,
+    root: Hash,
+    warmup_keys: KeyValues,
+    query_keys: KeyValues,
+    database_type: DatabaseType,
+}
+
+impl core::BenchmarkDescription for TrieReadSeriesBenchmarkDescription {
+    fn path(&self) -> Path {
+        let mut path = Path::new(&["trie", "read"]);
+        path.push(&format!("{}", self.database_size));
+        path
+    }
+
+    fn setup(self: Box<Self>) -> Box<dyn core::Benchmark> {
+        let mut database = TempDatabase::new();
+
+        let mut rng = rand::thread_rng();
+        let warmup_prefix = KUSAMA_STATE_DISTRIBUTION.key(&mut rng);
+
+        let mut key_values = KeyValues::new();
+        let mut warmup_keys = KeyValues::new();
+        let mut query_keys = KeyValues::new();
+        let every_x_key = self.database_size.keys() / SAMPLE_SIZE;
+        for idx in 0..self.database_size.keys() {
+            let kv = (
+                KUSAMA_STATE_DISTRIBUTION.key(&mut rng).to_vec(),
+                KUSAMA_STATE_DISTRIBUTION.value(&mut rng),
+            );
+
+            if idx % every_x_key == 0 {
+                // warmup keys go to separate tree with high prob
+                let mut actual_warmup_key = warmup_prefix.clone();
+                actual_warmup_key[16..].copy_from_slice(&kv.0[16..]);
+                warmup_keys.push((actual_warmup_key.clone(), kv.1.clone()));
+                key_values.push((actual_warmup_key.clone(), kv.1.clone()));
+            }
+
+            key_values.push(kv)
+        }
+
+        for i in (1..self.max_sample_size).step_by(self.max_sample_size / SAMPLE_SIZE) {
+            let kv = (
+                KUSAMA_STATE_DISTRIBUTION.key(&mut rng).to_vec(),
+                random_vec(&mut rng, i),
+            );
+
+            query_keys.push(kv.clone());
+            key_values.push(kv)
+        }
+
+        assert_eq!(warmup_keys.len(), SAMPLE_SIZE);
+        assert_eq!(query_keys.len(), SAMPLE_SIZE);
+
+        let root = generate_trie(database.open(self.database_type), key_values);
+
+        Box::new(TrieReadSeriesBenchmark {
+            database,
+            root,
+            warmup_keys,
+            query_keys,
+            database_type: self.database_type,
+        })
+    }
+
+    fn name(&self) -> Cow<'static, str> {
+        format!(
+            "Trie read benchmark({} database ({} keys), db_type: {})",
+            self.database_size,
+            pretty_print(self.database_size.keys()),
+            self.database_type,
+        )
+        .into()
+    }
+}
+
+struct Storage(Arc<dyn KeyValueDB>);
+
+impl sp_state_machine::Storage<sp_core::Blake2Hasher> for Storage {
+    fn get(&self, key: &Hash, prefix: Prefix) -> Result<Option<Vec<u8>>, String> {
+        let key = sp_trie::prefixed_key::<sp_core::Blake2Hasher>(key, prefix);
+        self.0
+            .get(0, &key)
+            .map_err(|e| format!("Database backend error: {:?}", e))
+    }
+}
+
+impl core::Benchmark for TrieReadSeriesBenchmark {
+    fn run(&mut self, mode: Mode) -> Vec<(usize, std::time::Duration)> {
+        let mut db = self.database.clone();
+
+        let storage: Arc<dyn sp_state_machine::Storage<sp_core::Blake2Hasher>> =
+            Arc::new(Storage(db.open(self.database_type)));
+
+        let trie_backend = sp_state_machine::TrieBackend::new(storage, self.root);
+        for (warmup_key, warmup_value) in self.warmup_keys.iter() {
+            let value = trie_backend
+                .storage(&warmup_key[..])
+                .expect("Failed to get key: db error")
+                .expect("Warmup key should exist");
+
+            // sanity for warmup keys
+            assert_eq!(&value, warmup_value);
+        }
+
+        if mode == Mode::Profile {
+            std::thread::park_timeout(std::time::Duration::from_secs(3));
+        }
+
+        let mut results = Vec::new();
+
+        for (key, _) in self.query_keys.iter() {
+            let read_time = std::time::Instant::now();
+            let val = trie_backend.storage(&key[..]).expect("Read error").expect(
+                "Key shouldn't be
+				none",
+            );
+            results.push((val.len(), read_time.elapsed()));
+        }
+
+        if mode == Mode::Profile {
+            std::thread::park_timeout(std::time::Duration::from_secs(1));
+        }
+
+        results
+    }
+}
+
+pub struct TrieWriteSeriesBenchmarkDescription {
+    pub database_size: DatabaseSize,
+    pub database_type: DatabaseType,
+    pub max_sample_size: usize,
+}
+
+impl core::BenchmarkDescription for TrieWriteSeriesBenchmarkDescription {
+    fn path(&self) -> Path {
+        let mut path = Path::new(&["trie", "write"]);
+        path.push(&format!("{}", self.database_size));
+        path
+    }
+
+    fn setup(self: Box<Self>) -> Box<dyn core::Benchmark> {
+        let mut database = TempDatabase::new();
+
+        let mut rng = rand::thread_rng();
+        let warmup_prefix = KUSAMA_STATE_DISTRIBUTION.key(&mut rng);
+
+        let mut key_values = KeyValues::new();
+        let mut warmup_keys = KeyValues::new();
+        let every_x_key = self.database_size.keys() / SAMPLE_SIZE;
+        for idx in 0..self.database_size.keys() {
+            let kv = (
+                KUSAMA_STATE_DISTRIBUTION.key(&mut rng).to_vec(),
+                KUSAMA_STATE_DISTRIBUTION.value(&mut rng),
+            );
+            if idx % every_x_key == 0 {
+                // warmup keys go to separate tree with high prob
+                let mut actual_warmup_key = warmup_prefix.clone();
+                actual_warmup_key[16..].copy_from_slice(&kv.0[16..]);
+                warmup_keys.push((actual_warmup_key.clone(), kv.1.clone()));
+                key_values.push((actual_warmup_key.clone(), kv.1.clone()));
+            }
+
+            key_values.push(kv)
+        }
+
+        assert_eq!(warmup_keys.len(), SAMPLE_SIZE);
+
+        let root = generate_trie(database.open(self.database_type), key_values);
+
+        Box::new(TrieWriteBenchmark {
+            database,
+            root,
+            warmup_keys,
+            database_type: self.database_type,
+            max_sample_size: self.max_sample_size,
+        })
+    }
+
+    fn name(&self) -> Cow<'static, str> {
+        format!(
+            "Trie write benchmark({} database ({} keys), db_type = {})",
+            self.database_size,
+            pretty_print(self.database_size.keys()),
+            self.database_type,
+        )
+        .into()
+    }
+}
+
+struct TrieWriteBenchmark {
+    database: TempDatabase,
+    root: Hash,
+    warmup_keys: KeyValues,
+    database_type: DatabaseType,
+    max_sample_size: usize,
+}
+
+impl core::Benchmark for TrieWriteBenchmark {
+    fn run(&mut self, mode: Mode) -> Vec<(usize, std::time::Duration)> {
+        let mut results = Vec::new();
+
+        for i in (1..=self.max_sample_size).step_by(self.max_sample_size / SAMPLE_SIZE) {
+            let mut rng = rand::thread_rng();
+            let mut db = self.database.clone();
+            let kvdb = db.open(self.database_type);
+            let mut new_root = self.root;
+            let mut overlay = HashMap::new();
+            let mut trie = SimpleTrie {
+                db: kvdb.clone(),
+                overlay: &mut overlay,
+            };
+
+            let mut trie_db_mut = TrieDBMut::from_existing(&mut trie, &mut new_root)
+                .expect("Failed to create TrieDBMut");
+
+            for (warmup_key, warmup_value) in self.warmup_keys.iter() {
+                let value = trie_db_mut
+                    .get(&warmup_key[..])
+                    .expect("Failed to get key: db error")
+                    .expect("Warmup key should exist");
+
+                // sanity for warmup keys
+                assert_eq!(&value, warmup_value);
+            }
+            let test_key = random_vec(&mut rng, 32);
+            let test_val = random_vec(&mut rng, i);
+
+            if mode == Mode::Profile {
+                std::thread::park_timeout(std::time::Duration::from_secs(3));
+            }
+
+            let started = std::time::Instant::now();
+
+            trie_db_mut
+                .insert(&test_key, &test_val)
+                .expect("Should be inserted ok");
+            trie_db_mut.commit();
+            drop(trie_db_mut);
+
+            let mut transaction = kvdb.transaction();
+            for (key, value) in overlay.into_iter() {
+                match value {
+                    Some(value) => transaction.put(0, &key[..], &value[..]),
+                    None => transaction.delete(0, &key[..]),
+                }
+            }
+            kvdb.write(transaction)
+                .expect("Failed to write transaction");
+
+            let elapsed = started.elapsed();
+
+            // sanity check
+            assert!(new_root != self.root);
+
+            if mode == Mode::Profile {
+                std::thread::park_timeout(std::time::Duration::from_secs(1));
+            }
+
+            results.push((test_val.len(), elapsed));
+        }
+
+        results
+    }
+}
+
+fn random_vec<R: Rng>(rng: &mut R, len: usize) -> Vec<u8> {
+    let mut val = vec![0u8; len];
+    rng.fill_bytes(&mut val[..]);
+    val
+}
+
+struct SizePool {
+    distribution: std::collections::BTreeMap<u32, u32>,
+    total: u32,
+}
+
+impl SizePool {
+    fn from_histogram(h: &[(u32, u32)]) -> SizePool {
+        let mut distribution = std::collections::BTreeMap::default();
+        let mut total = 0;
+        for (size, count) in h {
+            total += count;
+            distribution.insert(total, *size);
+        }
+        SizePool {
+            distribution,
+            total,
+        }
+    }
+
+    fn value<R: Rng>(&self, rng: &mut R) -> Vec<u8> {
+        let sr = (rng.next_u64() % self.total as u64) as u32;
+        let mut range = self
+            .distribution
+            .range((std::ops::Bound::Included(sr), std::ops::Bound::Unbounded));
+        let size = *range.next().unwrap().1 as usize;
+        random_vec(rng, size)
+    }
+
+    fn key<R: Rng>(&self, rng: &mut R) -> Vec<u8> {
+        random_vec(rng, 32)
+    }
+}

+ 58 - 3
analyses/fee-analysis/README.md

@@ -6,7 +6,7 @@ This directory has a CLI to output the analysis of weights into a `CSV` and a mo
 
 ## Requirements
 
-* python3
+* python >= 3.9
 * (virtualenv)[https://pypi.org/project/virtualenv/]
 
 ## Steps to run
@@ -17,15 +17,70 @@ This directory has a CLI to output the analysis of weights into a `CSV` and a mo
 * a browser tab should launch with the files in this directory
 
 ## To run the CLI
+This CLI will output a CSV with the weights of the extrinsics given a file or directory with files that are output by
+the FRAME benchmarks.
+Furthermore, given the parameters in a configuration file it will calculate the token fees for the given
+weights.
+
+Moreover, it can optionally try to estimate the price in fiat currency of the extrinsics given the total issuance of tokens in
+the system and the market cap.
+
 * `python analysis_cli --help` will output the help
 
 You will need to provide a path to a directory containing the weight files or the path to a single
 weight file.
 
+By default the output will be in `output.csv` where the cli is run, to change this use the `-o` option.
+
 For the CSV to include an analysis pass the parameter `-p` (Meaning the calculated price in tokens and prices)
 
-The `config.json` file in the `analysis_cli` there is configuration for said analysis. Such as specializing the
-parametrs and length for a given extrinsics, the coefficients, issuance and marke caps.
+The parameter configurations is found in `config.json` in the `analysis_cli` directory, you can change the file using
+the `-c` option.
+
+The config file has the following form:
+
+```json
+{
+    "weight_coefficient": Number,
+    "issuance": Number,
+    "length_coefficient": Number,
+    "min_market_cap": Number,
+    "max_market_cap": Number,
+    "lengths": {
+        "<extrinsic_name>": Number
+    },
+    "params": {
+        "<extrinsic_name>": {
+            "i": Number,
+            "j": Number,
+            "k": Number,
+            ...
+        }
+    }
+}
+```
+
+Where:
+* `weight_coefficient`: is the coefficient for converting a weight to a token fee, this can be found in
+  `runtime/src/constants.rs` as part of the `WeightToFeePolynomial` implementation by `WeightToFee`.
+* `issuance`: The total number of tokens available in the system, this is found in `runtime/src/constants.rs` as `JOYS`.
+* `length_coefficient`: This is how much a byte of an extrinsic cost in number of tokens, this is found in
+  `runtime/src/constants.rs` as `TransactionByteFee`.
+* `min_market_cap`: This is the estimated minimum market cap of the token. This is only used when the dollar price of a
+  extrinsics is calculated.
+* `max_market_cap`: This is the estimated maximum market cap of the token. This is only used when the dollar price of a
+  extrinsics is calculated.
+* `lengths`: This is a dictionary containing `<extrinsic_name>`. This entries maps to a length in bytes for a given
+  extrinsic. When calculating the token fee of an extrinsic this will be added over its weight fee using
+  `length_coefficient` for the convertion. (It's specially important to set a length for `runtime_upgrade` since this
+  extrinsic can easily take up to 3MB in size which will make a considerable part of its cost.
+* `params`: This is a dictionary of dictionaries, each entry on the first level represent a different extrinsic with
+  `<extrinsic_name>`. Each of these entries are a dictionary with `<parameter>` that represent a value of a parameter
+  that will be used when calculating the weight of the extrinsic with its corresponding function.
+
+Note that the `<extrinsic_name>` need its fully qualified path, e.g. `proposals_discussion::add_post`.
+
+Currently the `weight_coefficient` and the `length_coefficient` is set to the same as the runtime.
 
 **Note:** The output csv file already sums the `EXTRINSIC_BASE_WEIGHT` to the weight column
 

+ 1 - 1
cli/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@joystream/cli",
   "description": "Command Line Interface for Joystream community and governance activities",
-  "version": "0.3.1",
+  "version": "0.5.0",
   "author": "Leszek Wiesner",
   "bin": {
     "joystream-cli": "./bin/run"

+ 19 - 7
cli/src/Api.ts

@@ -49,6 +49,13 @@ export const apiModuleByGroup = {
   [WorkingGroups.Membership]: 'membershipWorkingGroup',
 } as const
 
+export const lockIdByWorkingGroup: { [K in WorkingGroups]: string } = {
+  [WorkingGroups.StorageProviders]: '0x0606060606060606',
+  [WorkingGroups.Curators]: '0x0707070707070707',
+  [WorkingGroups.Forum]: '0x0808080808080808',
+  [WorkingGroups.Membership]: '0x0909090909090909',
+}
+
 // Api wrapper for handling most common api calls and allowing easy API implementation switch in the future
 export default class Api {
   private _api: ApiPromise
@@ -232,18 +239,22 @@ export default class Api {
     const leadWorkerId = optLeadId.unwrap()
     const leadWorker = await this.workerByWorkerId(group, leadWorkerId.toNumber())
 
-    return await this.parseGroupMember(leadWorkerId, leadWorker)
+    return await this.parseGroupMember(group, leadWorkerId, leadWorker)
   }
 
-  protected async fetchStake(account: AccountId | string): Promise<Balance> {
+  protected async fetchStake(account: AccountId | string, group: WorkingGroups): Promise<Balance> {
     return this._api.createType(
       'Balance',
-      (await this._api.query.balances.locks(account)).reduce((sum, lock) => sum.add(lock.amount), new BN(0))
+      new BN(
+        (await this._api.query.balances.locks(account)).find((lock) => lock.id.eq(lockIdByWorkingGroup[group]))
+          ?.amount || 0
+      )
     )
   }
 
-  protected async parseGroupMember(id: WorkerId, worker: Worker): Promise<GroupMember> {
+  protected async parseGroupMember(group: WorkingGroups, id: WorkerId, worker: Worker): Promise<GroupMember> {
     const roleAccount = worker.role_account_id
+    const stakingAccount = worker.staking_account_id
     const memberId = worker.member_id
 
     const profile = await this.membershipById(memberId)
@@ -252,7 +263,7 @@ export default class Api {
       throw new Error(`Group member profile not found! (member id: ${memberId.toNumber()})`)
     }
 
-    const stake = await this.fetchStake(worker.staking_account_id)
+    const stake = await this.fetchStake(worker.staking_account_id, group)
 
     const reward: Reward = {
       valuePerBlock: worker.reward_per_block.unwrapOr(undefined),
@@ -262,6 +273,7 @@ export default class Api {
     return {
       workerId: id,
       roleAccount,
+      stakingAccount,
       memberId,
       profile,
       stake,
@@ -288,14 +300,14 @@ export default class Api {
 
   async groupMember(group: WorkingGroups, workerId: number) {
     const worker = await this.workerByWorkerId(group, workerId)
-    return await this.parseGroupMember(this._api.createType('WorkerId', workerId), worker)
+    return await this.parseGroupMember(group, this._api.createType('WorkerId', workerId), worker)
   }
 
   async groupMembers(group: WorkingGroups): Promise<GroupMember[]> {
     const workerEntries = await this.groupWorkers(group)
 
     const groupMembers: GroupMember[] = await Promise.all(
-      workerEntries.map(([id, worker]) => this.parseGroupMember(id, worker))
+      workerEntries.map(([id, worker]) => this.parseGroupMember(group, id, worker))
     )
 
     return groupMembers.reverse() // Sort by newest

+ 1 - 0
cli/src/Types.ts

@@ -55,6 +55,7 @@ export type GroupMember = {
   workerId: WorkerId
   memberId: MemberId
   roleAccount: AccountId
+  stakingAccount: AccountId
   profile: MemberDetails
   stake: Balance
   reward: Reward

+ 5 - 1
cli/src/base/AccountsCommandBase.ts

@@ -25,6 +25,7 @@ export const DEFAULT_ACCOUNT_TYPE = 'sr25519'
 export const KEYRING_OPTIONS: KeyringOptions = {
   type: DEFAULT_ACCOUNT_TYPE,
 }
+export const STAKING_ACCOUNT_CANDIDATE_STAKE = new BN(200)
 
 /**
  * Abstract base class for account-related commands.
@@ -392,6 +393,7 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
             await this.getDecodedPair(stakingAccount),
             this.getOriginalApi().tx.members.addStakingAccountCandidate(memberId)
           )
+          additionalStakingAccountCosts = additionalStakingAccountCosts.add(STAKING_ACCOUNT_CANDIDATE_STAKE)
         }
       }
 
@@ -403,7 +405,9 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
             formatBalance(missingStakingAccountBalance)
           )}.` +
             (additionalStakingAccountCosts.gtn(0)
-              ? ` (includes ${formatBalance(additionalStakingAccountCosts)} fee for setting new staking account)`
+              ? ` (includes ${formatBalance(
+                  additionalStakingAccountCosts
+                )} which is a required fee and candidate stake for adding a new staking account)`
               : '')
         )
         const transferTokens = await this.simplePrompt({

+ 25 - 0
cli/src/commands/working-groups/createOpening.ts

@@ -10,6 +10,10 @@ import { IOFlags, getInputJson, ensureOutputFileIsWriteable, saveOutputJsonToFil
 import ExitCodes from '../../ExitCodes'
 import { flags } from '@oclif/command'
 import { AugmentedSubmittables } from '@polkadot/api/types'
+import { formatBalance } from '@polkadot/util'
+import BN from 'bn.js'
+
+const OPENING_STAKE = new BN(2000)
 
 export default class WorkingGroupsCreateOpening extends WorkingGroupsCommandBase {
   static description = 'Create working group opening (requires lead access)'
@@ -69,6 +73,25 @@ export default class WorkingGroupsCreateOpening extends WorkingGroupsCommandBase
     return inputParams as OpeningParamsJson
   }
 
+  async promptForStakeTopUp(stakingAccount: string): Promise<void> {
+    this.log(`You need to stake ${chalk.bold(formatBalance(OPENING_STAKE))} in order to create a new opening.`)
+
+    const [balances] = await this.getApi().getAccountsBalancesInfo([stakingAccount])
+    const missingBalance = OPENING_STAKE.sub(balances.availableBalance)
+    if (missingBalance.gtn(0)) {
+      await this.requireConfirmation(
+        `Do you wish to transfer remaining ${chalk.bold(
+          formatBalance(missingBalance)
+        )} to your staking account? (${stakingAccount})`
+      )
+      const account = await this.promptForAccount('Choose account to transfer the funds from')
+      await this.sendAndFollowNamedTx(await this.getDecodedPair(account), 'balances', 'transferKeepAlive', [
+        stakingAccount,
+        missingBalance,
+      ])
+    }
+  }
+
   async run() {
     // lead-only gate
     const lead = await this.getRequiredLeadContext()
@@ -94,6 +117,8 @@ export default class WorkingGroupsCreateOpening extends WorkingGroupsCommandBase
       // Remember the provided/fetched data in a variable
       rememberedInput = openingJson
 
+      await this.promptForStakeTopUp(lead.stakingAccount.toString())
+
       // Generate and ask to confirm tx params
       const txParams = this.createTxParams(openingJson)
       this.jsonPrettyPrint(JSON.stringify(txParams))

+ 1 - 1
node/Cargo.toml

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

+ 6 - 5
node/src/chain_spec/forum_config.rs

@@ -2,7 +2,7 @@ use codec::Decode;
 use node_runtime::{
     forum,
     forum::{Category, Post, Thread},
-    AccountId, Balance, ForumConfig, Moment, PostId, Runtime, ThreadId,
+    AccountId, Balance, BlockNumber, ForumConfig, Moment, PostId, Runtime, ThreadId,
 };
 use serde::Deserialize;
 use sp_core::H256;
@@ -12,18 +12,18 @@ type CategoryId = <Runtime as forum::Trait>::CategoryId;
 type ForumUserId = forum::ForumUserId<Runtime>;
 type ModeratorId = forum::ModeratorId<Runtime>;
 type Hash = H256;
-type PostOf = Post<ForumUserId, Hash>;
+type PostOf = Post<ForumUserId, ThreadId, H256, Balance, BlockNumber>;
 
 type ThreadOf = (
     CategoryId,
     ThreadId,
-    Thread<ForumUserId, CategoryId, Moment, Hash, PostId, PostOf, Balance>,
+    Thread<ForumUserId, CategoryId, Moment, Hash, Balance>,
 );
 
 #[derive(Decode)]
 struct ForumData {
-    categories: Vec<(CategoryId, Category<CategoryId, ThreadId, H256>)>,
-    posts: Vec<(ThreadId, PostId, Post<ForumUserId, H256>)>,
+    categories: Vec<(CategoryId, Category<CategoryId, ThreadId, Hash>)>,
+    posts: Vec<(ThreadId, PostId, PostOf)>,
     threads: Vec<ThreadOf>,
     category_by_moderator: Vec<(CategoryId, ModeratorId, ())>,
     data_migration_done: bool,
@@ -139,6 +139,7 @@ fn create(_forum_sudo: AccountId, forum_data: EncodedForumData) -> ForumConfig {
     ForumConfig {
         category_by_id: forum_data.categories,
         thread_by_id: forum_data.threads,
+        post_by_id: forum_data.posts,
         category_by_moderator: forum_data.category_by_moderator,
         next_category_id,
         next_thread_id,

+ 1 - 1
node/src/service.rs

@@ -286,7 +286,7 @@ pub fn new_full_base(
     }
 
     // Spawn authority discovery module.
-    if matches!(role, Role::Authority{..} | Role::Sentry {..}) {
+    if matches!(role, Role::Authority { .. } | Role::Sentry { .. }) {
         let (sentries, authority_discovery_role) = match role {
             sc_service::config::Role::Authority { ref sentry_nodes } => (
                 sentry_nodes.clone(),

+ 4 - 4
runtime-modules/blog/Cargo.toml

@@ -12,23 +12,22 @@ frame-system = { package = 'frame-system', default-features = false, git = 'http
 codec = { package = 'parity-scale-codec', version = '1.0.0', default-features = false, features = ['derive'] }
 sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 common = { package = 'pallet-common', default-features = false, path = '../common'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 
 #Benchmark dependencies
 frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
 membership = { package = 'pallet-membership', default-features = false, path = '../membership', optional = true}
-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
 
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 membership = { package = 'pallet-membership', default-features = false, path = '../membership' }
 staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler' }
 pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 
 [features]
 default = ['std']
-runtime-benchmarks = ['frame-benchmarking', 'membership', 'balances']
+runtime-benchmarks = ['frame-benchmarking', 'membership']
 std = [
 	'codec/std',
 	'sp-std/std',
@@ -36,5 +35,6 @@ std = [
 	'sp-runtime/std',
 	'frame-system/std',
     'common/std',
-    'sp-arithmetic/std'
+    'sp-arithmetic/std',
+    'balances/std'
 ]

+ 74 - 18
runtime-modules/blog/src/benchmarking.rs

@@ -24,6 +24,16 @@ fn assert_last_event<T: Trait<I>, I: Instance>(generic_event: <T as Trait<I>>::E
     assert_eq!(event, &system_event);
 }
 
+fn assert_in_events<T: Trait<I>, I: Instance>(generic_event: <T as Trait<I>>::Event) {
+    let events = System::<T>::events();
+    let system_event: <T as frame_system::Trait>::Event = generic_event.into();
+
+    assert!(!events.is_empty(), "There are no events in event queue");
+
+    // compare to the last event record
+    assert!(events.iter().any(|e| e.event == system_event));
+}
+
 fn get_byte(num: u32, byte_number: u8) -> u8 {
     ((num & (0xff << (8 * byte_number))) >> 8 * byte_number) as u8
 }
@@ -84,14 +94,14 @@ fn handle_from_id<T: membership::Trait>(id: u32) -> Vec<u8> {
     handle
 }
 
-fn generate_post<T: Trait<I>, I: Instance>() -> PostId {
-    assert_eq!(Blog::<T, I>::post_count(), 0);
+fn generate_post<T: Trait<I>, I: Instance>(seq_num: u64) -> PostId {
+    assert_eq!(Blog::<T, I>::post_count(), seq_num);
 
     Blog::<T, I>::create_post(RawOrigin::Root.into(), vec![0u8], vec![0u8]).unwrap();
 
-    let post_id = 0;
+    let post_id = seq_num;
 
-    assert_eq!(Blog::<T, I>::post_count(), 1);
+    assert_eq!(Blog::<T, I>::post_count(), seq_num + 1);
 
     assert_eq!(
         Blog::<T, I>::post_by_id(post_id),
@@ -113,12 +123,18 @@ fn generate_reply<T: Trait<I>, I: Instance>(
         post_id,
         None,
         vec![0u8],
+        true,
     )
     .unwrap();
 
     assert_eq!(
         Blog::<T, I>::reply_by_id(post_id, T::ReplyId::zero()),
-        Reply::<T, I>::new(vec![0u8], participant_id, ParentId::Post(post_id))
+        Reply::<T, I>::new(
+            vec![0u8],
+            participant_id,
+            ParentId::Post(post_id),
+            T::ReplyDeposit::get()
+        )
     );
 
     T::ReplyId::zero()
@@ -154,7 +170,7 @@ benchmarks_instance! {
     }
 
     lock_post {
-        let post_id = generate_post::<T, I>();
+        let post_id = generate_post::<T, I>(0);
     }: _(RawOrigin::Root, post_id)
     verify {
         assert!(Blog::<T, I>::post_by_id(post_id).is_locked());
@@ -162,7 +178,7 @@ benchmarks_instance! {
     }
 
     unlock_post {
-        let post_id = generate_post::<T, I>();
+        let post_id = generate_post::<T, I>(0);
         Blog::<T, I>::lock_post(RawOrigin::Root.into(), post_id).unwrap();
         assert!(Blog::<T, I>::post_by_id(post_id).is_locked());
     }: _(RawOrigin::Root, post_id)
@@ -175,7 +191,7 @@ benchmarks_instance! {
         let t in 0 .. MAX_BYTES;
         let b in 0 .. MAX_BYTES;
 
-        let post_id = generate_post::<T, I>();
+        let post_id = generate_post::<T, I>(0);
         let title = Some(vec![1u8; t.try_into().unwrap()]);
         let body = Some(vec![1u8; b.try_into().unwrap()]);
     }: _(RawOrigin::Root, post_id, title.clone(), body.clone())
@@ -190,11 +206,11 @@ benchmarks_instance! {
     create_reply_to_post {
         let t in 0 .. MAX_BYTES;
 
-        let post_id = generate_post::<T, I>();
+        let post_id = generate_post::<T, I>(0);
         let (account_id, participant_id) = member_funded_account::<T, I>("caller", 0);
         let origin = RawOrigin::Signed(account_id);
         let text = vec![0u8; t.try_into().unwrap()];
-    }: create_reply(origin.clone(), participant_id, post_id, None, text.clone())
+    }: create_reply(origin.clone(), participant_id, post_id, None, text.clone(), true)
     verify {
         let mut expected_post = Post::<T, I>::new(&vec![0u8], &vec![0u8]);
         expected_post.increment_replies_counter();
@@ -204,7 +220,8 @@ benchmarks_instance! {
             Reply::<T, I>::new(
                 text.clone(),
                 participant_id,
-                ParentId::Post(post_id)
+                ParentId::Post(post_id),
+                T::ReplyDeposit::get(),
             )
         );
 
@@ -213,7 +230,8 @@ benchmarks_instance! {
                 participant_id,
                 post_id,
                 Zero::zero(),
-                text
+                text,
+                true
             ).into()
         );
     }
@@ -221,7 +239,7 @@ benchmarks_instance! {
     create_reply_to_reply {
         let t in 0 .. MAX_BYTES;
 
-        let post_id = generate_post::<T, I>();
+        let post_id = generate_post::<T, I>(0);
         let (account_id, participant_id) = member_funded_account::<T, I>("caller", 0);
         let reply_id = generate_reply::<T, I>(account_id.clone(), participant_id, post_id.clone());
         let origin = RawOrigin::Signed(account_id);
@@ -229,7 +247,7 @@ benchmarks_instance! {
         expected_post.increment_replies_counter();
         assert_eq!(Blog::<T, I>::post_by_id(post_id), expected_post);
         let text = vec![0u8; t.try_into().unwrap()];
-    }: create_reply(origin.clone(), participant_id, post_id, Some(reply_id), text.clone())
+    }: create_reply(origin.clone(), participant_id, post_id, Some(reply_id), text.clone(), true)
     verify {
         expected_post.increment_replies_counter();
         assert_eq!(Blog::<T, I>::post_by_id(post_id), expected_post);
@@ -238,7 +256,8 @@ benchmarks_instance! {
             Reply::<T, I>::new(
                 text.clone(),
                 participant_id,
-                ParentId::Reply(reply_id)
+                ParentId::Reply(reply_id),
+                T::ReplyDeposit::get(),
             )
         );
 
@@ -248,7 +267,8 @@ benchmarks_instance! {
                 post_id,
                 reply_id,
                 One::one(),
-                text
+                text,
+                true,
             ).into()
         );
     }
@@ -256,7 +276,7 @@ benchmarks_instance! {
     edit_reply {
         let t in 0 .. MAX_BYTES;
 
-        let post_id = generate_post::<T, I>();
+        let post_id = generate_post::<T, I>(0);
         let (account_id, participant_id) = member_funded_account::<T, I>("caller", 0);
         let reply_id = generate_reply::<T, I>(account_id.clone(), participant_id, post_id.clone());
         let origin = RawOrigin::Signed(account_id);
@@ -272,7 +292,8 @@ benchmarks_instance! {
             Reply::<T, I>::new(
                 updated_text.clone(),
                 participant_id,
-                ParentId::Post(post_id)
+                ParentId::Post(post_id),
+                T::ReplyDeposit::get(),
             )
         );
 
@@ -283,6 +304,34 @@ benchmarks_instance! {
                 updated_text
             ).into());
     }
+
+    delete_replies {
+        let i in 1 .. T::PostsMaxNumber::get().try_into().unwrap();
+        let (account_id, participant_id) = member_funded_account::<T, I>("caller", 0);
+        let mut replies = Vec::new();
+        let hide = false;
+
+        for seq_num in 0..i {
+            let post_id = generate_post::<T, I>(seq_num.into());
+            let reply_id =
+                generate_reply::<T, I>(account_id.clone(), participant_id, post_id.clone());
+            replies.push(ReplyToDelete {post_id, reply_id, hide});
+        }
+
+        let origin = RawOrigin::Signed(account_id);
+    }: _(origin.clone(), participant_id, replies.clone())
+    verify {
+        for ReplyToDelete {post_id, reply_id, hide} in replies {
+            assert!(!<ReplyById<T, I>>::contains_key(post_id, reply_id));
+
+            assert_in_events::<T, I>(RawEvent::ReplyDeleted(
+                    participant_id,
+                    post_id,
+                    reply_id,
+                    hide,
+                ).into());
+        }
+    }
 }
 
 #[cfg(test)]
@@ -339,4 +388,11 @@ mod tests {
             assert_ok!(test_benchmark_edit_reply::<Runtime>());
         })
     }
+
+    #[test]
+    fn test_delete_replies() {
+        ExtBuilder::default().build().execute_with(|| {
+            assert_ok!(test_benchmark_delete_replies::<Runtime>());
+        })
+    }
 }

+ 7 - 3
runtime-modules/blog/src/errors.rs

@@ -25,10 +25,14 @@ decl_error! {
         /// Number of posts exceeds limits.
         PostLimitReached,
 
-        /// Number of maximum replies reached
-        RepliesLimitReached,
-
         /// Reaction doesn't exists
         InvalidReactionIndex,
+
+        /// Insuficient balance for reply creation
+        InsufficientBalanceForReply,
+
+        /// This error represent the invalid state where there is not enough funds in a post
+        /// account to pay off its delete
+        InsufficientBalanceInPostAccount,
     }
 }

+ 207 - 47
runtime-modules/blog/src/lib.rs

@@ -30,21 +30,27 @@
 //! - [unlock_post](./struct.Module.html#method.unlock_post)
 //! - [edit_post](./struct.Module.html#method.edit_post)
 //! - [create_reply](./struct.Module.html#method.create_reply)
-//! - [edit_reply](./struct.Module.html#method.create_reply)
+//! - [edit_reply](./struct.Module.html#method.edit_reply)
+//! - [delete_replies](./struct.Module.html#method.delete_replies)
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
 use codec::{Codec, Decode, Encode};
-use common::origin::MemberOriginValidator;
+use common::membership::MemberOriginValidator;
 use errors::Error;
 pub use frame_support::dispatch::{DispatchError, DispatchResult};
+use frame_support::traits::{Currency, ExistenceRequirement};
 use frame_support::weights::Weight;
 use frame_support::{
     decl_event, decl_module, decl_storage, ensure, traits::Get, Parameter, StorageDoubleMap,
 };
 use sp_arithmetic::traits::{BaseArithmetic, One};
-use sp_runtime::traits::{Hash, MaybeSerialize, Member};
 use sp_runtime::SaturatedConversion;
+use sp_runtime::{
+    traits::{AccountIdConversion, Hash, MaybeSerialize, Member, Saturating},
+    ModuleId,
+};
+use sp_std::collections::btree_map::BTreeMap;
 use sp_std::prelude::*;
 
 mod benchmarking;
@@ -61,6 +67,11 @@ pub type PostId = u64;
 /// Blogger participant ID alias for the member of the system.
 pub type ParticipantId<T> = common::MemberId<T>;
 
+/// Balance alias for `balances` module.
+pub type BalanceOf<T> = <T as balances::Trait>::Balance;
+
+type Balances<T> = balances::Module<T>;
+
 /// blog WeightInfo.
 /// Note: This was auto generated through the benchmark CLI using the `--weight-trait` flag
 pub trait WeightInfo {
@@ -71,10 +82,15 @@ pub trait WeightInfo {
     fn create_reply_to_post(t: u32) -> Weight;
     fn create_reply_to_reply(t: u32) -> Weight;
     fn edit_reply(t: u32) -> Weight;
+    fn delete_replies(i: u32) -> Weight;
 }
 
+type BlogWeightInfo<T, I> = <T as Trait<I>>::WeightInfo;
+
 // The pallet's configuration trait.
-pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait + common::Trait {
+pub trait Trait<I: Instance = DefaultInstance>:
+    frame_system::Trait + common::membership::Trait + balances::Trait
+{
     /// Origin from which participant must come.
     type ParticipantEnsureOrigin: MemberOriginValidator<
         Self::Origin,
@@ -88,9 +104,6 @@ pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait + common::Tr
     /// The maximum number of posts in a blog.
     type PostsMaxNumber: Get<MaxNumber>;
 
-    /// The maximum number of replies to a post.
-    type RepliesMaxNumber: Get<MaxNumber>;
-
     /// Type of identifier for replies.
     type ReplyId: Parameter
         + Member
@@ -105,6 +118,15 @@ pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait + common::Tr
 
     /// Weight information for extrinsics in this pallet.
     type WeightInfo: WeightInfo;
+
+    /// Deposit needed to create a reply
+    type ReplyDeposit: Get<Self::Balance>;
+
+    /// The forum module Id, used to derive the account Id to hold the thread bounty
+    type ModuleId: Get<ModuleId>;
+
+    /// Time a reply can live until it can be deleted by anyone
+    type ReplyLifetime: Get<Self::BlockNumber>;
 }
 
 /// Type, representing blog related post structure
@@ -231,6 +253,10 @@ pub struct Reply<T: Trait<I>, I: Instance> {
     owner: ParticipantId<T>,
     /// Reply`s parent id
     parent_id: ParentId<T::ReplyId, PostId>,
+    /// Pay off by deleting post
+    cleanup_pay_off: T::Balance,
+    /// Last time reply was edited
+    last_edited: T::BlockNumber,
 }
 
 // Note: we derive it by hand because the derive isn't working because of a Rust problem
@@ -242,6 +268,8 @@ impl<T: Trait<I>, I: Instance> sp_std::fmt::Debug for Reply<T, I> {
             .field("text_hash", &self.text_hash)
             .field("owner", &self.owner)
             .field("parent_id", &self.parent_id)
+            .field("cleanup_pay_off", &self.cleanup_pay_off)
+            .field("last_edited", &self.last_edited)
             .finish()
     }
 }
@@ -255,6 +283,8 @@ impl<T: Trait<I>, I: Instance> PartialEq for Reply<T, I> {
         self.text_hash == other.text_hash
             && self.owner == other.owner
             && self.parent_id == other.parent_id
+            && self.cleanup_pay_off == other.cleanup_pay_off
+            && self.last_edited == other.last_edited
     }
 }
 
@@ -268,6 +298,8 @@ impl<T: Trait<I>, I: Instance> Default for Reply<T, I> {
             text_hash: Default::default(),
             owner: Default::default(),
             parent_id: Default::default(),
+            cleanup_pay_off: Default::default(),
+            last_edited: Default::default(),
         }
     }
 }
@@ -278,11 +310,14 @@ impl<T: Trait<I>, I: Instance> Reply<T, I> {
         text: Vec<u8>,
         owner: ParticipantId<T>,
         parent_id: ParentId<T::ReplyId, PostId>,
+        cleanup_pay_off: T::Balance,
     ) -> Self {
         Self {
             text_hash: T::Hashing::hash(&text),
             owner,
             parent_id,
+            cleanup_pay_off,
+            last_edited: frame_system::Module::<T>::block_number(),
         }
     }
 
@@ -293,10 +328,22 @@ impl<T: Trait<I>, I: Instance> Reply<T, I> {
 
     /// Update reply`s text
     fn update(&mut self, new_text: Vec<u8>) {
-        self.text_hash = T::Hashing::hash(&new_text)
+        self.text_hash = T::Hashing::hash(&new_text);
+        self.last_edited = frame_system::Module::<T>::block_number()
     }
 }
 
+/// Represents a single reply that will be deleted by `delete_replies`
+#[derive(Encode, Decode, Clone, Debug, Default, PartialEq)]
+pub struct ReplyToDelete<ReplyId> {
+    /// Id of the post corresponding to the reply that will be deleted
+    post_id: PostId,
+    /// Id of the reply to be deleted
+    reply_id: ReplyId,
+    /// Whether to hide the reply or just remove it from the storage
+    hide: bool,
+}
+
 // Blog`s pallet storage items.
 decl_storage! {
     trait Store for Module<T: Trait<I>, I: Instance=DefaultInstance> as BlogModule {
@@ -336,10 +383,10 @@ decl_module! {
         /// - DB:
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::create_post(
+        #[weight = BlogWeightInfo::<T, I>::create_post(
                 title.len().saturated_into(),
                 body.len().saturated_into()
-            )]
+        )]
         pub fn create_post(origin, title: Vec<u8>, body: Vec<u8>) -> DispatchResult  {
 
             // Ensure blog -> owner relation exists
@@ -375,7 +422,7 @@ decl_module! {
         /// - DB:
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::lock_post()]
+        #[weight = BlogWeightInfo::<T, I>::lock_post()]
         pub fn lock_post(origin, post_id: PostId) -> DispatchResult {
 
             // Ensure blog -> owner relation exists
@@ -406,7 +453,7 @@ decl_module! {
         /// - DB:
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::unlock_post()]
+        #[weight = BlogWeightInfo::<T, I>::unlock_post()]
         pub fn unlock_post(origin, post_id: PostId) -> DispatchResult {
 
             // Ensure blog -> owner relation exists
@@ -484,9 +531,10 @@ decl_module! {
             participant_id: ParticipantId<T>,
             post_id: PostId,
             reply_id: Option<T::ReplyId>,
-            text: Vec<u8>
+            text: Vec<u8>,
+            editable: bool,
         ) -> DispatchResult {
-            Self::ensure_valid_participant(origin, participant_id)?;
+            let account_id = Self::ensure_valid_participant(origin, participant_id)?;
 
             // Ensure post with given id exists
             let post = Self::ensure_post_exists(post_id)?;
@@ -494,35 +542,60 @@ decl_module! {
             // Ensure post unlocked, so mutations can be performed
             Self::ensure_post_unlocked(&post)?;
 
-            // Ensure root replies limit not reached
-            Self::ensure_replies_limit_not_reached(&post)?;
+            if let Some(reply_id) = reply_id {
+                // Check parent existed at some point in time(whether it is in storage or not)
+                ensure!(reply_id < post.replies_count(), Error::<T, I>::ReplyNotFound);
+            }
 
-            // New reply creation
-            let reply = if let Some(reply_id) = reply_id {
-                // Check parent reply existance in case of direct reply
-                Self::ensure_reply_exists(post_id, reply_id)?;
-                Reply::<T, I>::new(text.clone(), participant_id, ParentId::Reply(reply_id))
-            } else {
-                Reply::<T, I>::new(text.clone(), participant_id, ParentId::Post(post_id))
-            };
+            if editable {
+                ensure!(
+                    Balances::<T>::usable_balance(&account_id) >= T::ReplyDeposit::get(),
+                    Error::<T, I>::InsufficientBalanceForReply
+                );
+            }
 
             //
             // == MUTATION SAFE ==
             //
 
+            if editable {
+                Self::transfer_to_state_cleanup_treasury_account(
+                    T::ReplyDeposit::get(),
+                    post_id,
+                    &account_id
+                )?;
+            }
+
             // Update runtime storage with new reply
             let post_replies_count = post.replies_count();
-            <ReplyById<T, I>>::insert(post_id, post_replies_count, reply);
 
             // Increment replies counter, associated with given post
             <PostById<T, I>>::mutate(post_id, |inner_post| inner_post.increment_replies_counter());
 
+            if editable {
+                let parent_id = if let Some(reply_id) = reply_id {
+                    ParentId::Reply(reply_id)
+                } else {
+                    ParentId::Post(post_id)
+                };
+
+
+                let reply = Reply::<T, I>::new(
+                    text.clone(),
+                    participant_id,
+                    parent_id,
+                    T::ReplyDeposit::get()
+                );
+
+                <ReplyById<T, I>>::insert(post_id, post_replies_count, reply);
+            }
+
             if let Some(reply_id) = reply_id {
                 // Trigger event
-                Self::deposit_event(RawEvent::DirectReplyCreated(participant_id, post_id, reply_id, post_replies_count, text));
+                Self::deposit_event(RawEvent::DirectReplyCreated(participant_id, post_id, reply_id, post_replies_count, text, editable));
             } else {
                 // Trigger event
-                Self::deposit_event(RawEvent::ReplyCreated(participant_id, post_id, post_replies_count, text));
+                Self::deposit_event(RawEvent::ReplyCreated(participant_id, post_id, post_replies_count, text, editable));
             }
             Ok(())
         }
@@ -538,7 +611,7 @@ decl_module! {
         /// - DB:
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::edit_reply(new_text.len().saturated_into())]
+        #[weight = BlogWeightInfo::<T, I>::edit_reply(new_text.len().saturated_into())]
         pub fn edit_reply(
             origin,
             participant_id: ParticipantId<T>,
@@ -576,36 +649,132 @@ decl_module! {
             Ok(())
         }
 
+        /// Remove reply from storage
+        ///
+        /// <weight>
+        ///
+        /// ## Weight
+        /// `O (R)` where
+        /// - R is the number of replies to be deleted
+        /// - DB:
+        ///    - O(R)
+        /// # </weight>
+        #[weight = BlogWeightInfo::<T, I>::delete_replies(replies.len().saturated_into())]
+        pub fn delete_replies(
+            origin,
+            participant_id: ParticipantId<T>,
+            replies: Vec<ReplyToDelete<T::ReplyId>>,
+        ) -> DispatchResult {
+            let account_id = Self::ensure_valid_participant(origin, participant_id)?;
+
+            let mut erase_replies = Vec::new();
+            let mut pay_off_map = BTreeMap::new();
+            for ReplyToDelete { post_id, reply_id, hide } in replies {
+                // Ensure post with given id exists
+                let post = Self::ensure_post_exists(post_id)?;
+
+                // Ensure post unlocked, so mutations can be performed
+                Self::ensure_post_unlocked(&post)?;
+
+                // Ensure reply with given id exists
+                let reply = Self::ensure_reply_exists(post_id, reply_id)?;
+
+                // Ensure reply -> owner relation exists if post lifetime hasn't ran out
+                if (frame_system::Module::<T>::block_number().saturating_sub(reply.last_edited)) <
+                    T::ReplyLifetime::get()
+                {
+                    Self::ensure_reply_ownership(&reply, &participant_id)?;
+                }
+
+                if !reply.is_owner(&participant_id) {
+                    ensure!(!hide, Error::<T, I>::ReplyOwnershipError);
+                }
+
+                *pay_off_map.entry(post_id).or_default() += reply.cleanup_pay_off;
+                erase_replies.push((post_id, reply_id, reply.cleanup_pay_off, hide));
+            }
+
+            for (post_id, post_deposit) in pay_off_map.into_iter() {
+                ensure!(
+                    Balances::<T>::usable_balance(
+                        &Self::get_treasury_account(post_id)
+                    ) >= post_deposit,
+                    Error::<T, I>::InsufficientBalanceInPostAccount
+
+                );
+            }
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            for (post_id, reply_id, cleanup_pay_off, hide) in erase_replies {
+                Self::pay_off(post_id, cleanup_pay_off, &account_id)?;
+
+                // Update reply with new text
+                <ReplyById<T, I>>::remove(post_id, reply_id);
+
+                // Trigger event
+                Self::deposit_event(RawEvent::ReplyDeleted(participant_id, post_id, reply_id, hide));
+            }
+            Ok(())
+        }
+
     }
 }
 
 impl<T: Trait<I>, I: Instance> Module<T, I> {
+    fn get_treasury_account(post_id: PostId) -> T::AccountId {
+        T::ModuleId::get().into_sub_account(post_id)
+    }
+
+    fn pay_off(post_id: PostId, amount: BalanceOf<T>, account_id: &T::AccountId) -> DispatchResult {
+        <Balances<T> as Currency<T::AccountId>>::transfer(
+            &Self::get_treasury_account(post_id),
+            account_id,
+            amount,
+            ExistenceRequirement::AllowDeath,
+        )
+    }
+
+    fn transfer_to_state_cleanup_treasury_account(
+        amount: BalanceOf<T>,
+        post_id: PostId,
+        account_id: &T::AccountId,
+    ) -> DispatchResult {
+        <Balances<T> as Currency<T::AccountId>>::transfer(
+            account_id,
+            &Self::get_treasury_account(post_id),
+            amount,
+            ExistenceRequirement::AllowDeath,
+        )
+    }
     // edit_post_weight
     fn edit_post_weight(title: &Option<Vec<u8>>, body: &Option<Vec<u8>>) -> Weight {
         let title_len: u32 = title.as_ref().map_or(0, |t| t.len().saturated_into());
         let body_len: u32 = body.as_ref().map_or(0, |b| b.len().saturated_into());
 
-        T::WeightInfo::edit_post(title_len, body_len)
+        BlogWeightInfo::<T, I>::edit_post(title_len, body_len)
     }
 
     // calculate create_reply weight
     fn create_reply_weight(text_len: usize) -> Weight {
         let text_len: u32 = text_len.saturated_into();
-        T::WeightInfo::create_reply_to_post(text_len)
-            .max(T::WeightInfo::create_reply_to_reply(text_len))
+        BlogWeightInfo::<T, I>::create_reply_to_post(text_len)
+            .max(BlogWeightInfo::<T, I>::create_reply_to_reply(text_len))
     }
 
     // Get participant id from origin
     fn ensure_valid_participant(
         origin: T::Origin,
         participant_id: ParticipantId<T>,
-    ) -> Result<(), DispatchError> {
+    ) -> Result<T::AccountId, DispatchError> {
         let account_id = frame_system::ensure_signed(origin)?;
         ensure!(
             T::ParticipantEnsureOrigin::is_member_controller_account(&participant_id, &account_id),
             Error::<T, I>::MembershipError
         );
-        Ok(())
+        Ok(account_id)
     }
 
     fn ensure_post_exists(post_id: PostId) -> Result<Post<T, I>, DispatchError> {
@@ -663,18 +832,6 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
 
         Ok(posts_count)
     }
-
-    fn ensure_replies_limit_not_reached(post: &Post<T, I>) -> Result<(), DispatchError> {
-        // Get replies count, associated with given post
-        let root_replies_count = post.replies_count();
-
-        ensure!(
-            root_replies_count < T::RepliesMaxNumber::get().into(),
-            Error::<T, I>::RepliesLimitReached
-        );
-
-        Ok(())
-    }
 }
 
 decl_event!(
@@ -701,10 +858,13 @@ decl_event!(
         PostEdited(PostId, UpdatedTitle, UpdatedBody),
 
         /// A reply to a post was created
-        ReplyCreated(ParticipantId, PostId, ReplyId, Text),
+        ReplyCreated(ParticipantId, PostId, ReplyId, Text, bool),
 
         /// A reply to a reply was created
-        DirectReplyCreated(ParticipantId, PostId, ReplyId, ReplyId, Text),
+        DirectReplyCreated(ParticipantId, PostId, ReplyId, ReplyId, Text, bool),
+
+        /// A reply was deleted from storage
+        ReplyDeleted(ParticipantId, PostId, ReplyId, bool),
 
         /// A reply was edited
         ReplyEdited(ParticipantId, PostId, ReplyId, Text),

+ 56 - 22
runtime-modules/blog/src/mock.rs

@@ -12,9 +12,9 @@ use sp_runtime::{
     DispatchResult, Perbill,
 };
 
-pub(crate) const FIRST_OWNER_ORIGIN: u64 = 0;
+pub(crate) const FIRST_OWNER_ORIGIN: u128 = 0;
 pub(crate) const FIRST_OWNER_PARTICIPANT_ID: u64 = 0;
-pub(crate) const SECOND_OWNER_ORIGIN: u64 = 2;
+pub(crate) const SECOND_OWNER_ORIGIN: u128 = 2;
 pub(crate) const SECOND_OWNER_PARTICIPANT_ID: u64 = 2;
 pub(crate) const BAD_MEMBER_ID: u64 = 100000;
 
@@ -55,7 +55,7 @@ impl frame_system::Trait for Runtime {
     type BlockNumber = u64;
     type Hash = H256;
     type Hashing = BlakeTwo256;
-    type AccountId = u64;
+    type AccountId = u128;
     type Lookup = IdentityLookup<Self::AccountId>;
     type Header = Header;
     type Event = TestEvent;
@@ -90,6 +90,7 @@ parameter_types! {
     pub const InviteMemberLockId: [u8; 8] = [9; 8];
     pub const StakingCandidateLockId: [u8; 8] = [10; 8];
     pub const MinimumPeriod: u64 = 5;
+    pub const ReferralCutMaximumPercent: u8 = 50;
     pub const CandidateStake: u64 = 100;
 }
 
@@ -100,6 +101,7 @@ impl membership::Trait for Runtime {
     type WorkingGroup = ();
     type WeightInfo = Weights;
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InviteMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -134,7 +136,7 @@ impl common::working_group::WorkingGroupBudgetHandler<Runtime> for () {
 impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
     fn ensure_worker_origin(
         _origin: <Runtime as frame_system::Trait>::Origin,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
+        _worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!()
     }
@@ -143,7 +145,7 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Runtime as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Runtime as common::membership::Trait>::MemberId> {
         unimplemented!()
     }
 
@@ -153,7 +155,7 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
 
     fn is_worker_account_id(
         _: &<Runtime as frame_system::Trait>::AccountId,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
+        _worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> bool {
         unimplemented!();
     }
@@ -220,17 +222,22 @@ impl membership::WeightInfo for Weights {
 parameter_types! {
     pub const PostsMaxNumber: u64 = 20;
     pub const RepliesMaxNumber: u64 = 100;
+    pub const ReplyDeposit: u64 = 500;
+    pub const BlogModuleId: ModuleId = ModuleId(*b"m00:blog"); // module : blog
+    pub const ReplyLifetime: <Runtime as frame_system::Trait>::BlockNumber = 10;
 }
 
 impl Trait for Runtime {
     type Event = TestEvent;
 
     type PostsMaxNumber = PostsMaxNumber;
-    type RepliesMaxNumber = RepliesMaxNumber;
     type ParticipantEnsureOrigin = MockEnsureParticipant;
     type WeightInfo = ();
 
     type ReplyId = u64;
+    type ReplyDeposit = ReplyDeposit;
+    type ModuleId = BlogModuleId;
+    type ReplyLifetime = ReplyLifetime;
 }
 
 impl WeightInfo for () {
@@ -255,6 +262,9 @@ impl WeightInfo for () {
     fn edit_reply(_: u32) -> Weight {
         unimplemented!()
     }
+    fn delete_replies(_: u32) -> Weight {
+        unimplemented!()
+    }
 }
 
 pub struct MockEnsureParticipant;
@@ -280,7 +290,7 @@ impl
     }
 }
 
-impl common::Trait for Runtime {
+impl common::membership::Trait for Runtime {
     type MemberId = u64;
     type ActorId = u64;
 }
@@ -288,17 +298,17 @@ impl common::Trait for Runtime {
 #[derive(Default)]
 pub struct ExtBuilder;
 
-impl ExtBuilder {
-    fn run_to_block(n: u64) {
-        while System::block_number() < n {
-            <System as OnFinalize<u64>>::on_finalize(System::block_number());
-            <crate::Module<Runtime> as OnFinalize<u64>>::on_finalize(System::block_number());
-            System::set_block_number(System::block_number() + 1);
-            <System as OnInitialize<u64>>::on_initialize(System::block_number());
-            <crate::Module<Runtime> as OnInitialize<u64>>::on_initialize(System::block_number());
-        }
+pub(crate) fn run_to_block(n: u64) {
+    while System::block_number() < n {
+        <System as OnFinalize<u64>>::on_finalize(System::block_number());
+        <crate::Module<Runtime> as OnFinalize<u64>>::on_finalize(System::block_number());
+        System::set_block_number(System::block_number() + 1);
+        <System as OnInitialize<u64>>::on_initialize(System::block_number());
+        <crate::Module<Runtime> as OnInitialize<u64>>::on_initialize(System::block_number());
     }
+}
 
+impl ExtBuilder {
     pub fn build(self) -> TestExternalities {
         let t = frame_system::GenesisConfig::default()
             .build_storage::<Runtime>()
@@ -307,7 +317,7 @@ impl ExtBuilder {
         let mut result: TestExternalities = t.into();
 
         // Make sure we are not in block 0 where no events are emitted - see https://substrate.dev/recipes/2-appetizers/4-events.html#emitting-events
-        result.execute_with(|| Self::run_to_block(1));
+        result.execute_with(|| run_to_block(1));
 
         result
     }
@@ -396,18 +406,24 @@ pub fn get_reply_text() -> Vec<u8> {
 }
 
 pub fn get_reply(
-    owner: <Runtime as frame_system::Trait>::AccountId,
+    owner: ParticipantId<Runtime>,
     parent_id: ParentId<<Runtime as Trait>::ReplyId, PostId>,
 ) -> Reply<Runtime, DefaultInstance> {
     let reply_text = get_reply_text();
-    Reply::new(reply_text, owner, parent_id)
+    Reply::new(
+        reply_text,
+        owner,
+        parent_id,
+        <Runtime as Trait>::ReplyDeposit::get(),
+    )
 }
 
 pub fn create_reply(
-    origin_id: u64,
+    origin_id: u128,
     participant_id: u64,
     post_id: PostId,
     reply_id: Option<<Runtime as Trait>::ReplyId>,
+    editable: bool,
 ) -> DispatchResult {
     let reply = get_reply_text();
     TestBlogModule::create_reply(
@@ -416,11 +432,29 @@ pub fn create_reply(
         post_id,
         reply_id,
         reply,
+        editable,
+    )
+}
+
+pub fn delete_reply(
+    origin_id: u128,
+    participant_id: u64,
+    post_id: PostId,
+    reply_id: <Runtime as Trait>::ReplyId,
+) -> DispatchResult {
+    TestBlogModule::delete_replies(
+        Origin::signed(origin_id),
+        participant_id,
+        vec![ReplyToDelete {
+            post_id,
+            reply_id,
+            hide: false,
+        }],
     )
 }
 
 pub fn edit_reply(
-    origin_id: u64,
+    origin_id: u128,
     participant_id: u64,
     post_id: PostId,
     reply_id: <Runtime as Trait>::ReplyId,

+ 461 - 54
runtime-modules/blog/src/tests.rs

@@ -3,7 +3,6 @@
 use crate::mock::*;
 use crate::*;
 use frame_support::assert_ok;
-use frame_system::ensure_signed;
 
 //Blog, post or reply id
 const FIRST_ID: u64 = 0;
@@ -39,7 +38,7 @@ fn assert_failure(
 
 fn ensure_replies_equality(
     reply: Option<Reply<Runtime, DefaultInstance>>,
-    reply_owner_id: <Runtime as frame_system::Trait>::AccountId,
+    reply_owner_id: ParticipantId<Runtime>,
     parent: ParentId<<Runtime as Trait>::ReplyId, PostId>,
 ) {
     // Ensure  stored reply is equal to expected one
@@ -405,12 +404,20 @@ fn post_editing_post_locked_error() {
 
 // Replies
 #[test]
-fn reply_creation_success() {
+fn editable_reply_creation_success() {
     ExtBuilder::default().build().execute_with(|| {
         // Create post for future replies
         create_post(Origin::root()).unwrap();
 
-        let reply_owner_id = ensure_signed(Origin::signed(SECOND_OWNER_ORIGIN)).unwrap();
+        Balances::<Runtime>::make_free_balance_be(
+            &SECOND_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
+
+        assert_eq!(
+            Balances::<Runtime>::usable_balance(&SECOND_OWNER_ORIGIN),
+            <Runtime as Trait>::ReplyDeposit::get()
+        );
 
         // Events number before tested call
         let number_of_events_before_call = System::events().len();
@@ -419,9 +426,12 @@ fn reply_creation_success() {
             SECOND_OWNER_ORIGIN,
             SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
-            None
+            None,
+            true
         ));
 
+        assert_eq!(Balances::<Runtime>::usable_balance(&SECOND_OWNER_ORIGIN), 0);
+
         // Check reply related state after extrinsic performed
 
         let post = post_by_id(FIRST_ID).unwrap();
@@ -429,37 +439,107 @@ fn reply_creation_success() {
         // Replies related storage updated succesfully
         let reply = reply_by_id(FIRST_ID, FIRST_ID);
 
-        ensure_replies_equality(reply, reply_owner_id, ParentId::Post(FIRST_ID));
+        ensure_replies_equality(reply, SECOND_OWNER_PARTICIPANT_ID, ParentId::Post(FIRST_ID));
 
-        // Overall post replies count
+        // Root replies counter updated
         assert_eq!(post.replies_count(), 1);
 
-        // Root replies counter updated
+        // Event checked
+        let reply_created_event = get_test_event(RawEvent::ReplyCreated(
+            SECOND_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            FIRST_ID,
+            get_reply_text(),
+            true,
+        ));
+        assert_event_success(reply_created_event, number_of_events_before_call + 4)
+    })
+}
+
+#[test]
+fn editable_reply_creation_fails_without_enough_funds() {
+    ExtBuilder::default().build().execute_with(|| {
+        // Create post for future replies
+        create_post(Origin::root()).unwrap();
+
+        Balances::<Runtime>::make_free_balance_be(
+            &SECOND_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get() - 1,
+        );
+
+        assert_eq!(
+            create_reply(
+                SECOND_OWNER_ORIGIN,
+                SECOND_OWNER_PARTICIPANT_ID,
+                FIRST_ID,
+                None,
+                true
+            ),
+            Err(Error::<Runtime, DefaultInstance>::InsufficientBalanceForReply.into())
+        );
+    })
+}
+
+#[test]
+fn non_editable_reply_creation_success() {
+    ExtBuilder::default().build().execute_with(|| {
+        // Create post for future replies
+        create_post(Origin::root()).unwrap();
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        assert_ok!(create_reply(
+            SECOND_OWNER_ORIGIN,
+            SECOND_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            None,
+            false,
+        ));
+
+        // Check reply related state after extrinsic performed
+
+        let post = post_by_id(FIRST_ID).unwrap();
+
+        assert!(!<ReplyById<Runtime>>::contains_key(FIRST_ID, FIRST_ID));
+
+        // Overall post replies count
         assert_eq!(post.replies_count(), 1);
 
         // Event checked
         let reply_created_event = get_test_event(RawEvent::ReplyCreated(
-            reply_owner_id,
+            SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
             FIRST_ID,
             get_reply_text(),
+            false,
         ));
         assert_event_success(reply_created_event, number_of_events_before_call + 1)
     })
 }
 
 #[test]
-fn direct_reply_creation_success() {
+fn editable_direct_reply_creation_success() {
     ExtBuilder::default().build().execute_with(|| {
         // Create post for future replies
         create_post(Origin::root()).unwrap();
-        let direct_reply_owner_id = ensure_signed(Origin::signed(SECOND_OWNER_ORIGIN)).unwrap();
+
+        Balances::<Runtime>::make_free_balance_be(
+            &FIRST_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
+
+        Balances::<Runtime>::make_free_balance_be(
+            &SECOND_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
 
         assert_ok!(create_reply(
             FIRST_OWNER_ORIGIN,
             FIRST_OWNER_PARTICIPANT_ID,
             FIRST_ID,
-            None
+            None,
+            true,
         ));
 
         // Events number before tested call
@@ -470,7 +550,8 @@ fn direct_reply_creation_success() {
             SECOND_OWNER_ORIGIN,
             SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
-            Some(FIRST_ID)
+            Some(FIRST_ID),
+            true
         ));
 
         // Check reply related state after extrinsic performed
@@ -485,16 +566,127 @@ fn direct_reply_creation_success() {
 
         // Event checked
         let reply_created_event = get_test_event(RawEvent::DirectReplyCreated(
-            direct_reply_owner_id,
+            SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
             FIRST_ID,
             SECOND_ID,
             get_reply_text(),
+            true,
+        ));
+
+        assert_event_success(reply_created_event, number_of_events_before_call + 2)
+    })
+}
+
+#[test]
+fn non_editable_direct_reply_creation_success() {
+    ExtBuilder::default().build().execute_with(|| {
+        // Create post for future replies
+        create_post(Origin::root()).unwrap();
+
+        Balances::<Runtime>::make_free_balance_be(
+            &FIRST_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
+
+        assert_ok!(create_reply(
+            FIRST_OWNER_ORIGIN,
+            FIRST_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            None,
+            true,
         ));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Create reply for direct replying
+        assert_ok!(create_reply(
+            SECOND_OWNER_ORIGIN,
+            SECOND_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            Some(FIRST_ID),
+            false
+        ));
+
+        // Check reply related state after extrinsic performed
+        let post = post_by_id(FIRST_ID).unwrap();
+
+        // Replies related storage updated succesfully
+        reply_by_id(FIRST_ID, FIRST_ID).expect("Reply not found");
+
+        // Overall post replies count
+        assert_eq!(post.replies_count(), 2);
+
+        // Event checked
+        let reply_created_event = get_test_event(RawEvent::DirectReplyCreated(
+            SECOND_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            FIRST_ID,
+            SECOND_ID,
+            get_reply_text(),
+            false,
+        ));
+
         assert_event_success(reply_created_event, number_of_events_before_call + 1)
     })
 }
 
+#[test]
+fn editable_direct_reply_to_non_editable_reply_creation_success() {
+    ExtBuilder::default().build().execute_with(|| {
+        // Create post for future replies
+        create_post(Origin::root()).unwrap();
+
+        Balances::<Runtime>::make_free_balance_be(
+            &SECOND_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
+
+        assert_ok!(create_reply(
+            FIRST_OWNER_ORIGIN,
+            FIRST_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            None,
+            false,
+        ));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Create reply for direct replying
+        assert_ok!(create_reply(
+            SECOND_OWNER_ORIGIN,
+            SECOND_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            Some(FIRST_ID),
+            true
+        ));
+
+        // Check reply related state after extrinsic performed
+
+        let post = post_by_id(FIRST_ID).unwrap();
+
+        // Replies related storage updated succesfully
+        reply_by_id(FIRST_ID, SECOND_ID).expect("Reply not found");
+
+        // Overall post replies count
+        assert_eq!(post.replies_count(), 2);
+
+        // Event checked
+        let reply_created_event = get_test_event(RawEvent::DirectReplyCreated(
+            SECOND_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            FIRST_ID,
+            SECOND_ID,
+            get_reply_text(),
+            true,
+        ));
+
+        assert_event_success(reply_created_event, number_of_events_before_call + 4)
+    })
+}
+
 #[test]
 fn reply_creation_post_locked_error() {
     ExtBuilder::default().build().execute_with(|| {
@@ -511,6 +703,7 @@ fn reply_creation_post_locked_error() {
             SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
             None,
+            true,
         );
 
         // Check if related replies storage left unchanged
@@ -536,6 +729,7 @@ fn reply_creation_post_not_found() {
             SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
             None,
+            true,
         );
 
         // Check if related replies storage left unchanged
@@ -550,43 +744,17 @@ fn reply_creation_post_not_found() {
     })
 }
 
-#[test]
-fn reply_creation_limit_reached() {
-    ExtBuilder::default().build().execute_with(|| {
-        // Create post for future replies
-        create_post(Origin::root()).unwrap();
-        loop {
-            // Events number before tested call
-            let number_of_events_before_call = System::events().len();
-            if let Err(create_reply_err) = create_reply(
-                FIRST_OWNER_ORIGIN,
-                FIRST_OWNER_PARTICIPANT_ID,
-                FIRST_ID,
-                None,
-            ) {
-                let post = post_by_id(FIRST_ID).unwrap();
-
-                // Root post replies counter & reply root max number contraint equality checked
-                assert_eq!(post.replies_count(), RepliesMaxNumber::get());
-
-                // Last reply creation, before limit reached, failure checked
-                assert_failure(
-                    Err(create_reply_err),
-                    Error::RepliesLimitReached,
-                    number_of_events_before_call,
-                );
-                break;
-            }
-        }
-    })
-}
-
 #[test]
 fn direct_reply_creation_reply_not_found() {
     ExtBuilder::default().build().execute_with(|| {
         // Create post for future replies
         create_post(Origin::root()).unwrap();
 
+        Balances::<Runtime>::make_free_balance_be(
+            &SECOND_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
+
         // Events number before tested call
         let number_of_events_before_call = System::events().len();
 
@@ -596,6 +764,7 @@ fn direct_reply_creation_reply_not_found() {
             SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
             Some(FIRST_ID),
+            true,
         );
 
         // Check if related runtime storage left unchanged
@@ -616,13 +785,17 @@ fn reply_editing_success() {
         // Create post for future replies
         create_post(Origin::root()).unwrap();
 
-        let reply_owner_id = ensure_signed(Origin::signed(SECOND_OWNER_ORIGIN)).unwrap();
+        Balances::<Runtime>::make_free_balance_be(
+            &SECOND_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
 
         create_reply(
             SECOND_OWNER_ORIGIN,
             SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
             None,
+            true,
         )
         .unwrap();
 
@@ -640,7 +813,7 @@ fn reply_editing_success() {
         // Reply after editing checked
         let reply = reply_by_id(FIRST_ID, FIRST_ID);
 
-        ensure_replies_equality(reply, reply_owner_id, ParentId::Post(FIRST_ID));
+        ensure_replies_equality(reply, SECOND_OWNER_PARTICIPANT_ID, ParentId::Post(FIRST_ID));
 
         // Event checked
         let reply_edited_event = get_test_event(RawEvent::ReplyEdited(
@@ -659,13 +832,17 @@ fn reply_editing_post_locked_error() {
         // Create post for future replies
         create_post(Origin::root()).unwrap();
 
-        let reply_owner_id = ensure_signed(Origin::signed(SECOND_OWNER_ORIGIN)).unwrap();
+        Balances::<Runtime>::make_free_balance_be(
+            &SECOND_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
 
         create_reply(
             SECOND_OWNER_ORIGIN,
             SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
             None,
+            true,
         )
         .unwrap();
 
@@ -686,7 +863,7 @@ fn reply_editing_post_locked_error() {
         let reply = reply_by_id(FIRST_ID, FIRST_ID);
 
         // Compare with default unedited reply
-        ensure_replies_equality(reply, reply_owner_id, ParentId::Post(FIRST_ID));
+        ensure_replies_equality(reply, SECOND_OWNER_PARTICIPANT_ID, ParentId::Post(FIRST_ID));
 
         // Failure checked
         assert_failure(
@@ -728,13 +905,17 @@ fn reply_editing_ownership_error() {
         // Create post for future replies
         create_post(Origin::root()).unwrap();
 
-        let reply_owner_id = ensure_signed(Origin::signed(SECOND_OWNER_ORIGIN)).unwrap();
+        Balances::<Runtime>::make_free_balance_be(
+            &SECOND_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
 
         create_reply(
             SECOND_OWNER_ORIGIN,
             SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
             None,
+            true,
         )
         .unwrap();
 
@@ -752,7 +933,7 @@ fn reply_editing_ownership_error() {
         let reply = reply_by_id(FIRST_ID, FIRST_ID);
 
         // Compare with default unedited reply
-        ensure_replies_equality(reply, reply_owner_id, ParentId::Post(FIRST_ID));
+        ensure_replies_equality(reply, SECOND_OWNER_PARTICIPANT_ID, ParentId::Post(FIRST_ID));
 
         // Failure checked
         assert_failure(
@@ -771,7 +952,7 @@ fn reply_participant_error() {
 
         let number_of_events_before_call = System::events().len();
 
-        let reply_result = create_reply(SECOND_OWNER_ORIGIN, BAD_MEMBER_ID, FIRST_ID, None);
+        let reply_result = create_reply(SECOND_OWNER_ORIGIN, BAD_MEMBER_ID, FIRST_ID, None, true);
 
         // Failure checked
         assert_failure(
@@ -788,13 +969,17 @@ fn reply_editing_participant_error() {
         // Create post for future replies
         create_post(Origin::root()).unwrap();
 
-        let reply_owner_id = ensure_signed(Origin::signed(SECOND_OWNER_ORIGIN)).unwrap();
+        Balances::<Runtime>::make_free_balance_be(
+            &SECOND_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
 
         create_reply(
             SECOND_OWNER_ORIGIN,
             SECOND_OWNER_PARTICIPANT_ID,
             FIRST_ID,
             None,
+            true,
         )
         .unwrap();
 
@@ -808,7 +993,7 @@ fn reply_editing_participant_error() {
         let reply = reply_by_id(FIRST_ID, FIRST_ID);
 
         // Compare with default unedited reply
-        ensure_replies_equality(reply, reply_owner_id, ParentId::Post(FIRST_ID));
+        ensure_replies_equality(reply, SECOND_OWNER_PARTICIPANT_ID, ParentId::Post(FIRST_ID));
 
         // Failure checked
         assert_failure(
@@ -819,6 +1004,228 @@ fn reply_editing_participant_error() {
     })
 }
 
+#[test]
+fn reply_delete_success() {
+    ExtBuilder::default().build().execute_with(|| {
+        // Create post for future replies
+        create_post(Origin::root()).unwrap();
+
+        Balances::<Runtime>::make_free_balance_be(
+            &FIRST_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
+
+        assert_eq!(
+            Balances::<Runtime>::usable_balance(&FIRST_OWNER_ORIGIN),
+            <Runtime as Trait>::ReplyDeposit::get()
+        );
+
+        assert_ok!(create_reply(
+            FIRST_OWNER_ORIGIN,
+            FIRST_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            None,
+            true,
+        ));
+
+        assert_eq!(Balances::<Runtime>::usable_balance(&FIRST_OWNER_ORIGIN), 0);
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Check reply related state after extrinsic performed
+
+        let post = post_by_id(FIRST_ID).unwrap();
+
+        // Replies related storage updated succesfully
+        reply_by_id(FIRST_ID, FIRST_ID).expect("Reply not found");
+
+        // Overall post replies count
+        assert_eq!(post.replies_count(), 1);
+
+        assert!(<ReplyById<Runtime, DefaultInstance>>::contains_key(
+            FIRST_ID, FIRST_ID
+        ));
+
+        assert_ok!(delete_reply(
+            FIRST_OWNER_ORIGIN,
+            FIRST_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            FIRST_ID,
+        ));
+
+        assert_eq!(
+            Balances::<Runtime>::usable_balance(&FIRST_OWNER_ORIGIN),
+            <Runtime as Trait>::ReplyDeposit::get()
+        );
+
+        // Overall post replies count
+        assert_eq!(post.replies_count(), 1);
+
+        assert!(!<ReplyById<Runtime, DefaultInstance>>::contains_key(
+            FIRST_ID, FIRST_ID
+        ));
+
+        // Event checked
+        let reply_created_event = get_test_event(RawEvent::ReplyDeleted(
+            FIRST_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            FIRST_ID,
+            false,
+        ));
+
+        assert_event_success(reply_created_event, number_of_events_before_call + 2)
+    })
+}
+
+#[test]
+fn reply_delete_fails_with_non_existant_post() {
+    ExtBuilder::default().build().execute_with(|| {
+        // Create post for future replies
+        create_post(Origin::root()).unwrap();
+
+        Balances::<Runtime>::make_free_balance_be(
+            &FIRST_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
+
+        assert_eq!(
+            delete_reply(
+                FIRST_OWNER_ORIGIN,
+                FIRST_OWNER_PARTICIPANT_ID,
+                FIRST_ID,
+                FIRST_ID,
+            ),
+            Err(Error::<Runtime, DefaultInstance>::ReplyNotFound.into())
+        );
+    })
+}
+
+#[test]
+fn reply_delete_fails_invalid_participant() {
+    ExtBuilder::default().build().execute_with(|| {
+        // Create post for future replies
+        create_post(Origin::root()).unwrap();
+
+        Balances::<Runtime>::make_free_balance_be(
+            &FIRST_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
+
+        assert_ok!(create_reply(
+            FIRST_OWNER_ORIGIN,
+            FIRST_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            None,
+            true,
+        ));
+
+        // Check reply related state after extrinsic performed
+        let post = post_by_id(FIRST_ID).unwrap();
+
+        // Replies related storage updated succesfully
+        reply_by_id(FIRST_ID, FIRST_ID).expect("Reply not found");
+
+        // Overall post replies count
+        assert_eq!(post.replies_count(), 1);
+
+        assert!(<ReplyById<Runtime, DefaultInstance>>::contains_key(
+            FIRST_ID, FIRST_ID
+        ));
+
+        assert_eq!(
+            delete_reply(
+                SECOND_OWNER_ORIGIN,
+                SECOND_OWNER_PARTICIPANT_ID,
+                FIRST_ID,
+                FIRST_ID,
+            ),
+            Err(Error::<Runtime, DefaultInstance>::ReplyOwnershipError.into())
+        );
+    })
+}
+
+#[test]
+fn reply_delete_success_with_other_participant() {
+    ExtBuilder::default().build().execute_with(|| {
+        // Create post for future replies
+        create_post(Origin::root()).unwrap();
+
+        Balances::<Runtime>::make_free_balance_be(
+            &FIRST_OWNER_ORIGIN,
+            <Runtime as Trait>::ReplyDeposit::get(),
+        );
+
+        assert_eq!(
+            Balances::<Runtime>::usable_balance(&FIRST_OWNER_ORIGIN),
+            <Runtime as Trait>::ReplyDeposit::get()
+        );
+
+        assert_ok!(create_reply(
+            FIRST_OWNER_ORIGIN,
+            FIRST_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            None,
+            true,
+        ));
+
+        assert_eq!(Balances::<Runtime>::usable_balance(&FIRST_OWNER_ORIGIN), 0);
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Check reply related state after extrinsic performed
+
+        let post = post_by_id(FIRST_ID).unwrap();
+
+        // Replies related storage updated succesfully
+        reply_by_id(FIRST_ID, FIRST_ID).expect("Reply not found");
+
+        // Overall post replies count
+        assert_eq!(post.replies_count(), 1);
+
+        assert!(<ReplyById<Runtime, DefaultInstance>>::contains_key(
+            FIRST_ID, FIRST_ID
+        ));
+
+        run_to_block(
+            frame_system::Module::<Runtime>::block_number()
+                + <Runtime as Trait>::ReplyLifetime::get(),
+        );
+
+        assert_eq!(Balances::<Runtime>::usable_balance(&SECOND_OWNER_ORIGIN), 0);
+
+        assert_ok!(delete_reply(
+            SECOND_OWNER_ORIGIN,
+            SECOND_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            FIRST_ID,
+        ));
+
+        assert_eq!(
+            Balances::<Runtime>::usable_balance(&SECOND_OWNER_ORIGIN),
+            <Runtime as Trait>::ReplyDeposit::get()
+        );
+
+        // Overall post replies count
+        assert_eq!(post.replies_count(), 1);
+
+        assert!(!<ReplyById<Runtime, DefaultInstance>>::contains_key(
+            FIRST_ID, FIRST_ID
+        ));
+
+        // Event checked
+        let reply_created_event = get_test_event(RawEvent::ReplyDeleted(
+            SECOND_OWNER_PARTICIPANT_ID,
+            FIRST_ID,
+            FIRST_ID,
+            false,
+        ));
+
+        assert_event_success(reply_created_event, number_of_events_before_call + 4)
+    })
+}
+
 fn replies_storage_unchanged(post_id: PostId, reply_id: <Runtime as Trait>::ReplyId) -> bool {
     match post_by_id(post_id) {
         Some(post) if post.replies_count() == 0 && reply_by_id(post_id, reply_id).is_none() => true,

+ 53 - 0
runtime-modules/bounty/Cargo.toml

@@ -0,0 +1,53 @@
+[package]
+name = 'pallet-bounty'
+version = '1.0.1'
+authors = ['Joystream contributors']
+edition = '2018'
+
+[dependencies]
+serde = { version = "1.0.101", optional = true, features = ["derive"] }
+codec = { package = 'parity-scale-codec', version = '1.3.4', default-features = false, features = ['derive'] }
+sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+frame-system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+common = { package = 'pallet-common', default-features = false, path = '../common'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
+
+# Benchmarking
+frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
+council = { package = 'pallet-council', default-features = false, path = '../council'}
+referendum = { package = 'pallet-referendum', default-features = false, path = '../referendum'}
+membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
+
+[dev-dependencies]
+pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
+council = { package = 'pallet-council', default-features = false, path = '../council'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
+
+[features]
+default = ['std']
+runtime-benchmarks = [
+	"frame-benchmarking",
+	"sp-runtime/runtime-benchmarks",
+	"council/runtime-benchmarks",
+	"membership/runtime-benchmarks"
+]
+std = [
+	'serde',
+	'codec/std',
+	'sp-arithmetic/std',
+	'sp-std/std',
+	'frame-support/std',
+	'frame-system/std',
+	'balances/std',
+	'sp-runtime/std',
+	'common/std',
+	'staking-handler/std',
+]

+ 171 - 0
runtime-modules/bounty/src/actors.rs

@@ -0,0 +1,171 @@
+//! This module contains the BountyActorManager - a bounty actor management helper.
+//! It simplifies the interface of dealing with two different actor types: members and council.
+//! BountyActorManager contains methods to validate actor origin, transfer funds to/from the bounty
+//! account, etc.
+
+use crate::{BalanceOf, BountyActor, Error, Module, Trait};
+
+use frame_support::dispatch::{DispatchError, DispatchResult};
+use frame_support::ensure;
+use frame_support::traits::Currency;
+use frame_system::ensure_root;
+use sp_arithmetic::traits::Saturating;
+
+use common::council::CouncilBudgetManager;
+use common::membership::{MemberId, MemberOriginValidator, MembershipInfoProvider};
+
+// Helper enum for the bounty management.
+pub(crate) enum BountyActorManager<T: Trait> {
+    // Bounty was created or funded by a council.
+    Council,
+
+    // Bounty was created or funded by a member.
+    Member(T::AccountId, MemberId<T>),
+}
+
+impl<T: Trait> BountyActorManager<T> {
+    // Construct BountyActor by extrinsic origin and optional member_id.
+    pub(crate) fn ensure_bounty_actor_manager(
+        origin: T::Origin,
+        actor: BountyActor<MemberId<T>>,
+    ) -> Result<BountyActorManager<T>, DispatchError> {
+        match actor {
+            BountyActor::Member(member_id) => {
+                let account_id =
+                    T::Membership::ensure_member_controller_account_origin(origin, member_id)?;
+
+                Ok(BountyActorManager::Member(account_id, member_id))
+            }
+            BountyActor::Council => {
+                ensure_root(origin)?;
+
+                Ok(BountyActorManager::Council)
+            }
+        }
+    }
+
+    // Construct BountyActor.
+    pub(crate) fn get_bounty_actor_manager(
+        actor: BountyActor<MemberId<T>>,
+    ) -> Result<BountyActorManager<T>, DispatchError> {
+        match actor {
+            BountyActor::Member(member_id) => {
+                let account_id = T::Membership::controller_account_id(member_id)?;
+
+                Ok(BountyActorManager::Member(account_id, member_id))
+            }
+            BountyActor::Council => Ok(BountyActorManager::Council),
+        }
+    }
+
+    // Validate balance is sufficient for the bounty
+    pub(crate) fn validate_balance_sufficiency(
+        &self,
+        required_balance: BalanceOf<T>,
+    ) -> DispatchResult {
+        let balance_is_sufficient = match self {
+            BountyActorManager::Council => {
+                BountyActorManager::<T>::check_council_budget(required_balance)
+            }
+            BountyActorManager::Member(account_id, _) => {
+                Module::<T>::check_balance_for_account(required_balance, account_id)
+            }
+        };
+
+        ensure!(
+            balance_is_sufficient,
+            Error::<T>::InsufficientBalanceForBounty
+        );
+
+        Ok(())
+    }
+
+    // Verifies that council budget is sufficient for a bounty.
+    fn check_council_budget(amount: BalanceOf<T>) -> bool {
+        T::CouncilBudgetManager::get_budget() >= amount
+    }
+
+    // Validate that provided actor relates to the initial BountyActor.
+    pub(crate) fn validate_actor(&self, actor: &BountyActor<MemberId<T>>) -> DispatchResult {
+        let initial_actor = match self {
+            BountyActorManager::Council => BountyActor::Council,
+            BountyActorManager::Member(_, member_id) => BountyActor::Member(*member_id),
+        };
+
+        ensure!(initial_actor == actor.clone(), Error::<T>::NotBountyActor);
+
+        Ok(())
+    }
+
+    // Transfer funds for the bounty creation.
+    pub(crate) fn transfer_funds_to_bounty_account(
+        &self,
+        bounty_id: T::BountyId,
+        required_balance: BalanceOf<T>,
+    ) -> DispatchResult {
+        match self {
+            BountyActorManager::Council => {
+                BountyActorManager::<T>::transfer_balance_from_council_budget(
+                    bounty_id,
+                    required_balance,
+                );
+            }
+            BountyActorManager::Member(account_id, _) => {
+                Module::<T>::transfer_funds_to_bounty_account(
+                    account_id,
+                    bounty_id,
+                    required_balance,
+                )?;
+            }
+        }
+
+        Ok(())
+    }
+
+    // Restore a balance for the bounty creator.
+    pub(crate) fn transfer_funds_from_bounty_account(
+        &self,
+        bounty_id: T::BountyId,
+        required_balance: BalanceOf<T>,
+    ) -> DispatchResult {
+        match self {
+            BountyActorManager::Council => {
+                BountyActorManager::<T>::transfer_balance_to_council_budget(
+                    bounty_id,
+                    required_balance,
+                );
+            }
+            BountyActorManager::Member(account_id, _) => {
+                Module::<T>::transfer_funds_from_bounty_account(
+                    account_id,
+                    bounty_id,
+                    required_balance,
+                )?;
+            }
+        }
+
+        Ok(())
+    }
+
+    // Remove some balance from the council budget and transfer it to the bounty account.
+    fn transfer_balance_from_council_budget(bounty_id: T::BountyId, amount: BalanceOf<T>) {
+        let budget = T::CouncilBudgetManager::get_budget();
+        let new_budget = budget.saturating_sub(amount);
+
+        T::CouncilBudgetManager::set_budget(new_budget);
+
+        let bounty_account_id = Module::<T>::bounty_account_id(bounty_id);
+        let _ = balances::Module::<T>::deposit_creating(&bounty_account_id, amount);
+    }
+
+    // Add some balance from the council budget and slash from the bounty account.
+    fn transfer_balance_to_council_budget(bounty_id: T::BountyId, amount: BalanceOf<T>) {
+        let bounty_account_id = Module::<T>::bounty_account_id(bounty_id);
+        let _ = balances::Module::<T>::slash(&bounty_account_id, amount);
+
+        let budget = T::CouncilBudgetManager::get_budget();
+        let new_budget = budget.saturating_add(amount);
+
+        T::CouncilBudgetManager::set_budget(new_budget);
+    }
+}

+ 1087 - 0
runtime-modules/bounty/src/benchmarking.rs

@@ -0,0 +1,1087 @@
+#![cfg(feature = "runtime-benchmarks")]
+
+use frame_benchmarking::{account, benchmarks};
+use frame_support::storage::StorageMap;
+use frame_support::traits::{Currency, Get, OnFinalize, OnInitialize};
+use frame_system::{EventRecord, RawOrigin};
+use sp_arithmetic::traits::{One, Zero};
+use sp_runtime::SaturatedConversion;
+use sp_std::boxed::Box;
+use sp_std::collections::btree_map::BTreeMap;
+use sp_std::collections::btree_set::BTreeSet;
+use sp_std::vec;
+use sp_std::vec::Vec;
+
+use crate::Module as Bounty;
+use balances::Module as Balances;
+use common::council::CouncilBudgetManager;
+use frame_system::Module as System;
+use membership::Module as Membership;
+
+use crate::{
+    AssuranceContractType, BalanceOf, Bounties, BountyActor, BountyCreationParameters,
+    BountyMilestone, Call, Entries, Event, FundingType, Module, OracleWorkEntryJudgment, Trait,
+};
+
+pub fn run_to_block<T: Trait>(target_block: T::BlockNumber) {
+    let mut current_block = System::<T>::block_number();
+    while current_block < target_block {
+        System::<T>::on_finalize(current_block);
+        Bounty::<T>::on_finalize(current_block);
+
+        current_block += One::one();
+        System::<T>::set_block_number(current_block);
+
+        System::<T>::on_initialize(current_block);
+        Bounty::<T>::on_initialize(current_block);
+    }
+}
+
+fn assert_last_event<T: Trait>(generic_event: <T as Trait>::Event) {
+    let events = System::<T>::events();
+    let system_event: <T as frame_system::Trait>::Event = generic_event.into();
+    // compare to the last event record
+    let EventRecord { event, .. } = &events[events.len() - 1];
+    assert_eq!(event, &system_event);
+}
+
+fn assert_was_fired<T: Trait>(generic_event: <T as Trait>::Event) {
+    let events = System::<T>::events();
+    let system_event: <T as frame_system::Trait>::Event = generic_event.into();
+
+    assert!(events.iter().any(|ev| ev.event == system_event));
+}
+
+fn get_byte(num: u32, byte_number: u8) -> u8 {
+    ((num & (0xff << (8 * byte_number))) >> 8 * byte_number) as u8
+}
+
+// Method to generate a distintic valid handle
+// for a membership. For each index.
+fn handle_from_id<T: Trait + membership::Trait>(id: u32) -> Vec<u8> {
+    let mut handle = vec![];
+
+    for i in 0..4 {
+        handle.push(get_byte(id, i));
+    }
+
+    handle
+}
+
+//defines initial balance
+fn initial_balance<T: Trait>() -> T::Balance {
+    1000000.into()
+}
+
+fn member_funded_account<T: Trait + membership::Trait>(
+    name: &'static str,
+    id: u32,
+) -> (T::AccountId, T::MemberId) {
+    let account_id = account::<T::AccountId>(name, id, SEED);
+    let handle = handle_from_id::<T>(id);
+
+    // Give balance for buying membership
+    let _ = Balances::<T>::make_free_balance_be(&account_id, initial_balance::<T>());
+
+    let params = membership::BuyMembershipParameters {
+        root_account: account_id.clone(),
+        controller_account: account_id.clone(),
+        handle: Some(handle),
+        metadata: Vec::new(),
+        referrer_id: None,
+    };
+
+    let new_member_id = Membership::<T>::members_created();
+
+    Membership::<T>::buy_membership(RawOrigin::Signed(account_id.clone()).into(), params).unwrap();
+
+    let _ = Balances::<T>::make_free_balance_be(&account_id, initial_balance::<T>());
+
+    Membership::<T>::add_staking_account_candidate(
+        RawOrigin::Signed(account_id.clone()).into(),
+        new_member_id,
+    )
+    .unwrap();
+
+    Membership::<T>::confirm_staking_account(
+        RawOrigin::Signed(account_id.clone()).into(),
+        new_member_id,
+        account_id.clone(),
+    )
+    .unwrap();
+
+    (account_id, new_member_id)
+}
+
+fn announce_entry_and_submit_work<T: Trait + membership::Trait>(
+    bounty_id: &T::BountyId,
+    index: u32,
+) -> T::EntryId {
+    let membership_index = 1000 + index;
+    let (account_id, member_id) = member_funded_account::<T>("work entrants", membership_index);
+
+    Bounty::<T>::announce_work_entry(
+        RawOrigin::Signed(account_id.clone()).into(),
+        member_id,
+        *bounty_id,
+        account_id.clone(),
+    )
+    .unwrap();
+
+    let entry_id: T::EntryId = Bounty::<T>::entry_count().into();
+
+    let work_data = b"work_data".to_vec();
+
+    Bounty::<T>::submit_work(
+        RawOrigin::Signed(account_id.clone()).into(),
+        member_id,
+        *bounty_id,
+        entry_id,
+        work_data,
+    )
+    .unwrap();
+
+    entry_id
+}
+
+fn create_max_funded_bounty<T: Trait>(params: BountyCreationParameters<T>) -> T::BountyId {
+    let funding_amount = match params.funding_type {
+        FundingType::Perpetual { target } => target,
+        FundingType::Limited {
+            max_funding_amount, ..
+        } => max_funding_amount,
+    };
+
+    create_funded_bounty::<T>(params.clone(), funding_amount)
+}
+
+fn create_min_funded_bounty<T: Trait>(params: BountyCreationParameters<T>) -> T::BountyId {
+    let funding_amount = match params.funding_type {
+        FundingType::Perpetual { target } => target,
+        FundingType::Limited {
+            min_funding_amount, ..
+        } => min_funding_amount,
+    };
+
+    create_funded_bounty::<T>(params.clone(), funding_amount)
+}
+
+fn create_funded_bounty<T: Trait>(
+    params: BountyCreationParameters<T>,
+    funding_amount: BalanceOf<T>,
+) -> T::BountyId {
+    T::CouncilBudgetManager::set_budget(params.cherry + funding_amount);
+
+    Bounty::<T>::create_bounty(RawOrigin::Root.into(), params, Vec::new()).unwrap();
+
+    let bounty_id: T::BountyId = Bounty::<T>::bounty_count().into();
+
+    assert!(Bounties::<T>::contains_key(bounty_id));
+
+    Bounty::<T>::fund_bounty(
+        RawOrigin::Root.into(),
+        BountyActor::Council,
+        bounty_id,
+        funding_amount,
+    )
+    .unwrap();
+
+    bounty_id
+}
+
+const MAX_BYTES: u32 = 50000;
+const SEED: u32 = 0;
+const MAX_WORK_ENTRIES: u32 = 100;
+
+benchmarks! {
+    where_clause {
+        where T: council::Trait,
+              T: balances::Trait,
+              T: membership::Trait,
+              T: Trait,
+    }
+    _{ }
+
+    create_bounty_by_council {
+        let i in 1 .. MAX_BYTES;
+        let j in 1 .. T::ClosedContractSizeLimit::get();
+
+        let metadata = vec![0u8].repeat(i as usize);
+        let cherry: BalanceOf<T> = 100.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+        let max_amount: BalanceOf<T> = 1000.into();
+
+        T::CouncilBudgetManager::set_budget(cherry);
+
+        let members = (1..=j)
+            .map(|id| id.saturated_into())
+            .collect::<BTreeSet<_>>();
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            cherry,
+            entrant_stake,
+            funding_type: FundingType::Perpetual{ target: max_amount },
+            contract_type: AssuranceContractType::Closed(members),
+            ..Default::default()
+        };
+
+    }: create_bounty (RawOrigin::Root, params.clone(), metadata.clone())
+    verify {
+        let bounty_id: T::BountyId = 1u32.into();
+
+        assert!(Bounties::<T>::contains_key(bounty_id));
+        assert_eq!(Bounty::<T>::bounty_count(), 1); // Bounty counter was updated.
+        assert_last_event::<T>(Event::<T>::BountyCreated(bounty_id, params, metadata).into());
+    }
+
+    create_bounty_by_member {
+        let i in 1 .. MAX_BYTES;
+        let j in 1 .. T::ClosedContractSizeLimit::get();
+
+        let metadata = vec![0u8].repeat(i as usize);
+        let cherry: BalanceOf<T> = 100.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+        let max_amount: BalanceOf<T> = 1000.into();
+
+        let (account_id, member_id) = member_funded_account::<T>("member1", 0);
+
+        T::CouncilBudgetManager::set_budget(cherry);
+
+        let members = (1..=j)
+            .map(|id| id.saturated_into())
+            .collect::<BTreeSet<_>>();
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            cherry,
+            entrant_stake,
+            creator: BountyActor::Member(member_id),
+            funding_type: FundingType::Perpetual{ target: max_amount },
+            contract_type: AssuranceContractType::Closed(members),
+            ..Default::default()
+        };
+
+    }: create_bounty (RawOrigin::Signed(account_id), params.clone(), metadata.clone())
+    verify {
+        let bounty_id: T::BountyId = 1u32.into();
+
+        assert!(Bounties::<T>::contains_key(bounty_id));
+        assert_eq!(Bounty::<T>::bounty_count(), 1); // Bounty counter was updated.
+        assert_last_event::<T>(Event::<T>::BountyCreated(bounty_id, params, metadata).into());
+    }
+
+    cancel_bounty_by_council {
+        let cherry: BalanceOf<T> = 100.into();
+        let max_amount: BalanceOf<T> = 1000.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        T::CouncilBudgetManager::set_budget(cherry);
+
+        let creator = BountyActor::Council;
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            cherry,
+            creator: creator.clone(),
+            // same complexity with limited funding and FundingExpired stage.
+            funding_type: FundingType::Perpetual{ target: max_amount },
+            entrant_stake,
+            ..Default::default()
+        };
+
+        Bounty::<T>::create_bounty(RawOrigin::Root.into(), params, Vec::new()).unwrap();
+
+        let bounty_id: T::BountyId = Bounty::<T>::bounty_count().into();
+
+        assert!(Bounties::<T>::contains_key(bounty_id));
+    }: cancel_bounty(RawOrigin::Root, creator.clone(), bounty_id)
+    verify {
+        assert!(!Bounties::<T>::contains_key(bounty_id));
+        assert_last_event::<T>(Event::<T>::BountyCanceled(bounty_id, creator).into());
+    }
+
+    cancel_bounty_by_member {
+        let cherry: BalanceOf<T> = 100.into();
+        let max_amount: BalanceOf<T> = 1000.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        let (account_id, member_id) = member_funded_account::<T>("member1", 0);
+
+        T::CouncilBudgetManager::set_budget(cherry);
+
+        let creator = BountyActor::Member(member_id);
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            cherry,
+            creator: creator.clone(),
+            // same complexity with limited funding and FundingExpired stage.
+            funding_type: FundingType::Perpetual{ target: max_amount },
+            entrant_stake,
+            ..Default::default()
+        };
+
+        Bounty::<T>::create_bounty(
+            RawOrigin::Signed(account_id.clone()).into(),
+            params,
+            Vec::new()
+        ).unwrap();
+
+        let bounty_id: T::BountyId = Bounty::<T>::bounty_count().into();
+
+        assert!(Bounties::<T>::contains_key(bounty_id));
+    }: cancel_bounty(RawOrigin::Signed(account_id), creator.clone(), bounty_id)
+    verify {
+        assert!(!Bounties::<T>::contains_key(bounty_id));
+        assert_last_event::<T>(Event::<T>::BountyCanceled(bounty_id, creator).into());
+    }
+
+    veto_bounty {
+        let max_amount: BalanceOf<T> = 1000.into();
+        let cherry: BalanceOf<T> = 100.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        T::CouncilBudgetManager::set_budget(cherry);
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            funding_type: FundingType::Perpetual{ target: max_amount },
+            cherry,
+            entrant_stake,
+            ..Default::default()
+        };
+
+        Bounty::<T>::create_bounty(RawOrigin::Root.into(), params, Vec::new()).unwrap();
+
+        let bounty_id: T::BountyId = Bounty::<T>::bounty_count().into();
+
+        assert!(Bounties::<T>::contains_key(bounty_id));
+    }: _ (RawOrigin::Root, bounty_id)
+    verify {
+        assert!(!Bounties::<T>::contains_key(bounty_id));
+        assert_last_event::<T>(Event::<T>::BountyVetoed(bounty_id).into());
+    }
+
+    fund_bounty_by_member {
+        let max_amount: BalanceOf<T> = 100.into();
+        let cherry: BalanceOf<T> = 100.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        T::CouncilBudgetManager::set_budget(cherry);
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            funding_type: FundingType::Perpetual{ target: max_amount },
+            cherry,
+            entrant_stake,
+            ..Default::default()
+        };
+        // should reach default max bounty funding amount
+        let amount: BalanceOf<T> = 100.into();
+
+        let (account_id, member_id) = member_funded_account::<T>("member1", 0);
+
+        Bounty::<T>::create_bounty(RawOrigin::Root.into(), params, Vec::new()).unwrap();
+
+        let bounty_id: T::BountyId = Bounty::<T>::bounty_count().into();
+
+        assert!(Bounties::<T>::contains_key(bounty_id));
+    }: fund_bounty (RawOrigin::Signed(account_id.clone()), BountyActor::Member(member_id), bounty_id, amount)
+    verify {
+        assert_eq!(
+            Balances::<T>::usable_balance(&account_id),
+            // included staking account deposit
+            initial_balance::<T>() - amount - T::CandidateStake::get()
+        );
+        assert_last_event::<T>(Event::<T>::BountyMaxFundingReached(bounty_id).into());
+    }
+
+    fund_bounty_by_council {
+        let max_amount: BalanceOf<T> = 100.into();
+        let cherry: BalanceOf<T> = 100.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        T::CouncilBudgetManager::set_budget(cherry + max_amount);
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            funding_type: FundingType::Perpetual{ target: max_amount },
+            cherry,
+            entrant_stake,
+            ..Default::default()
+        };
+        // should reach default max bounty funding amount
+        let amount: BalanceOf<T> = 100.into();
+
+        Bounty::<T>::create_bounty(RawOrigin::Root.into(), params, Vec::new()).unwrap();
+
+        let bounty_id: T::BountyId = Bounty::<T>::bounty_count().into();
+
+        assert!(Bounties::<T>::contains_key(bounty_id));
+    }: fund_bounty (RawOrigin::Root, BountyActor::Council, bounty_id, amount)
+    verify {
+        assert_eq!(T::CouncilBudgetManager::get_budget(), Zero::zero());
+        assert_last_event::<T>(Event::<T>::BountyMaxFundingReached(bounty_id).into());
+    }
+
+    withdraw_funding_by_member {
+        let funding_period = 1;
+        let bounty_amount: BalanceOf<T> = 200.into();
+        let cherry: BalanceOf<T> = 100.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        T::CouncilBudgetManager::set_budget(cherry);
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            funding_type: FundingType::Limited{
+                min_funding_amount: bounty_amount,
+                max_funding_amount: bounty_amount,
+                funding_period: funding_period.into(),
+            },
+            cherry,
+            entrant_stake,
+            ..Default::default()
+        };
+        // should reach default max bounty funding amount
+        let amount: BalanceOf<T> = 100.into();
+
+        let (account_id, member_id) = member_funded_account::<T>("member1", 0);
+
+        Bounty::<T>::create_bounty(RawOrigin::Root.into(), params, Vec::new()).unwrap();
+
+        let bounty_id: T::BountyId = Bounty::<T>::bounty_count().into();
+
+        assert!(Bounties::<T>::contains_key(bounty_id));
+
+        let funder = BountyActor::Member(member_id);
+
+        Bounty::<T>::fund_bounty(
+            RawOrigin::Signed(account_id.clone()).into(),
+            funder.clone(),
+            bounty_id,
+            amount
+        ).unwrap();
+
+        run_to_block::<T>((funding_period + 1).into());
+
+    }: withdraw_funding (RawOrigin::Signed(account_id.clone()), funder, bounty_id)
+    verify {
+        assert_eq!(
+            Balances::<T>::usable_balance(&account_id),
+            // included staking account deposit
+            initial_balance::<T>() - T::CandidateStake::get() + cherry
+        );
+
+        assert_last_event::<T>(Event::<T>::BountyRemoved(bounty_id).into());
+    }
+
+    withdraw_funding_by_council {
+        let funding_period = 1;
+        let bounty_amount: BalanceOf<T> = 200.into();
+        let cherry: BalanceOf<T> = 100.into();
+        let funding_amount: BalanceOf<T> = 100.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        T::CouncilBudgetManager::set_budget(cherry + funding_amount);
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            funding_type: FundingType::Limited{
+                min_funding_amount: bounty_amount,
+                max_funding_amount: bounty_amount,
+                funding_period: funding_period.into(),
+            },
+            cherry,
+            entrant_stake,
+            ..Default::default()
+        };
+
+        Bounty::<T>::create_bounty(RawOrigin::Root.into(), params, Vec::new()).unwrap();
+
+        let bounty_id: T::BountyId = Bounty::<T>::bounty_count().into();
+
+        assert!(Bounties::<T>::contains_key(bounty_id));
+
+        let funder = BountyActor::Council;
+
+        Bounty::<T>::fund_bounty(
+            RawOrigin::Root.into(),
+            funder.clone(),
+            bounty_id,
+            funding_amount
+        ).unwrap();
+
+        run_to_block::<T>((funding_period + 1).into());
+
+    }: withdraw_funding(RawOrigin::Root, funder, bounty_id)
+    verify {
+        assert_eq!(T::CouncilBudgetManager::get_budget(), cherry + funding_amount);
+        assert_last_event::<T>(Event::<T>::BountyRemoved(bounty_id).into());
+    }
+
+    announce_work_entry {
+        let i in 1 .. T::ClosedContractSizeLimit::get();
+
+        let cherry: BalanceOf<T> = 100.into();
+        let funding_amount: BalanceOf<T> = 100.into();
+        let stake: BalanceOf<T> = 100.into();
+
+        let member_ids = (0..i)
+            .into_iter()
+            .map(|id| id.saturated_into())
+            .collect::<BTreeSet<T::MemberId>>();
+
+        let contract_type = AssuranceContractType::Closed(member_ids);
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            funding_type: FundingType::Perpetual{ target: funding_amount },
+            cherry,
+            contract_type,
+            entrant_stake: stake,
+            ..Default::default()
+        };
+
+        let bounty_id = create_max_funded_bounty::<T>(params);
+
+        let (account_id, member_id) = member_funded_account::<T>("member1", 1);
+
+    }: _(
+        RawOrigin::Signed(account_id.clone()),
+        member_id,
+        bounty_id,
+        account_id.clone()
+    )
+    verify {
+        let entry_id: T::EntryId = Bounty::<T>::entry_count().into();
+
+        assert!(Entries::<T>::contains_key(entry_id));
+        assert_last_event::<T>(
+            Event::<T>::WorkEntryAnnounced(
+                bounty_id,
+                entry_id,
+                member_id,
+                account_id
+            ).into()
+        );
+    }
+
+    withdraw_work_entry {
+        let cherry: BalanceOf<T> = 100.into();
+        let funding_amount: BalanceOf<T> = 100.into();
+        let stake: BalanceOf<T> = 100.into();
+
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            cherry,
+            funding_type: FundingType::Perpetual{ target: funding_amount },
+            entrant_stake: stake,
+            ..Default::default()
+        };
+
+        let bounty_id = create_max_funded_bounty::<T>(params);
+
+        let (account_id, member_id) = member_funded_account::<T>("member1", 1);
+
+        Bounty::<T>::announce_work_entry(
+            RawOrigin::Signed(account_id.clone()).into(),
+            member_id,
+            bounty_id,
+            account_id.clone()
+        ).unwrap();
+
+        let entry_id: T::EntryId = Bounty::<T>::entry_count().into();
+
+    }: _(RawOrigin::Signed(account_id.clone()), member_id, bounty_id, entry_id)
+    verify {
+        assert!(!Entries::<T>::contains_key(entry_id));
+        assert_last_event::<T>(
+            Event::<T>::WorkEntryWithdrawn(bounty_id, entry_id, member_id).into()
+        );
+    }
+
+    submit_work {
+        let i in 0 .. MAX_BYTES;
+        let work_data = vec![0u8].repeat(i as usize);
+
+        let cherry: BalanceOf<T> = 100.into();
+        let funding_amount: BalanceOf<T> = 100.into();
+        let max_amount: BalanceOf<T> = 10000.into();
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+        let work_period: T::BlockNumber = One::one();
+        let judging_period: T::BlockNumber = One::one();
+        let funding_period: T::BlockNumber = One::one();
+
+        let params = BountyCreationParameters::<T>{
+            work_period,
+            judging_period,
+            cherry,
+            funding_type: FundingType::Limited{
+                min_funding_amount: funding_amount,
+                max_funding_amount: max_amount,
+                funding_period
+            },
+            entrant_stake,
+            ..Default::default()
+        };
+
+        let bounty_id = create_min_funded_bounty::<T>(params);
+
+        run_to_block::<T>((funding_period + One::one()).into());
+
+        let bounty = Bounty::<T>::bounties(bounty_id);
+        assert!(matches!(bounty.milestone, BountyMilestone::Created { has_contributions: true, ..}));
+
+        let (account_id, member_id) = member_funded_account::<T>("member1", 1);
+
+        Bounty::<T>::announce_work_entry(
+            RawOrigin::Signed(account_id.clone()).into(),
+            member_id,
+            bounty_id,
+            account_id.clone()
+        ).unwrap();
+
+        let entry_id: T::EntryId = Bounty::<T>::entry_count().into();
+
+    }: _(RawOrigin::Signed(account_id.clone()), member_id, bounty_id, entry_id, work_data.clone())
+    verify {
+        let entry = Bounty::<T>::entries(entry_id);
+
+        assert!(entry.work_submitted);
+        assert_last_event::<T>(
+            Event::<T>::WorkSubmitted(bounty_id, entry_id, member_id, work_data).into()
+        );
+    }
+
+    submit_oracle_judgment_by_council_all_winners {
+        let i in 1 .. MAX_WORK_ENTRIES;
+
+        let work_period: T::BlockNumber = One::one();
+        let cherry: BalanceOf<T> = 100.into();
+        let funding_amount: BalanceOf<T> = 10000000.into();
+        let oracle = BountyActor::Council;
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        let params = BountyCreationParameters::<T> {
+            work_period,
+            judging_period: One::one(),
+            creator: BountyActor::Council,
+            cherry,
+            entrant_stake,
+            funding_type: FundingType::Perpetual{ target: funding_amount },
+            oracle: oracle.clone(),
+            ..Default::default()
+        };
+
+        let bounty_id = create_max_funded_bounty::<T>(params);
+
+        let entry_ids = (0..i)
+            .into_iter()
+            .map(|i| { announce_entry_and_submit_work::<T>(&bounty_id, i)})
+            .collect::<Vec<_>>();
+
+        let winner_reward: BalanceOf<T> = (funding_amount / i.into()).into();
+        let correction = funding_amount - winner_reward * i.into(); // for total sum = 100%
+        let judgment = entry_ids
+            .iter()
+            .map(|entry_id| {
+                let corrected_winner_reward = if *entry_id == One::one() {
+                    winner_reward + correction
+                } else {
+                    winner_reward
+                };
+
+                (*entry_id, OracleWorkEntryJudgment::Winner {reward : corrected_winner_reward})
+            })
+            .collect::<BTreeMap<_, _>>();
+
+        run_to_block::<T>((work_period + One::one()).into());
+
+    }: submit_oracle_judgment(RawOrigin::Root, oracle.clone(), bounty_id, judgment.clone())
+    verify {
+        for entry_id in entry_ids {
+            let entry = Bounty::<T>::entries(entry_id);
+            let corrected_winner_reward = if entry_id == One::one() {
+                    winner_reward + correction
+                } else {
+                    winner_reward
+                };
+            assert_eq!(
+                entry.oracle_judgment_result,
+                Some(OracleWorkEntryJudgment::Winner {reward : corrected_winner_reward})
+            );
+        }
+        assert_last_event::<T>(
+            Event::<T>::OracleJudgmentSubmitted(bounty_id, oracle, judgment).into()
+        );
+    }
+
+    submit_oracle_judgment_by_council_all_rejected {
+        let i in 1 .. MAX_WORK_ENTRIES;
+
+        let work_period: T::BlockNumber = One::one();
+        let cherry: BalanceOf<T> = 100.into();
+        let funding_amount: BalanceOf<T> = 100.into();
+        let oracle = BountyActor::Council;
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        let params = BountyCreationParameters::<T> {
+            work_period,
+            judging_period: One::one(),
+            creator: BountyActor::Council,
+            cherry,
+            entrant_stake,
+            funding_type: FundingType::Perpetual{ target: funding_amount },
+            oracle: oracle.clone(),
+            ..Default::default()
+        };
+
+        let bounty_id = create_max_funded_bounty::<T>(params);
+
+        let entry_ids = (0..i)
+            .into_iter()
+            .map(|i| { announce_entry_and_submit_work::<T>(&bounty_id, i)})
+            .collect::<Vec<_>>();
+
+        let judgment = entry_ids.iter()
+            .map(|entry_id| (*entry_id, OracleWorkEntryJudgment::Rejected))
+            .collect::<BTreeMap<_, _>>();
+
+        run_to_block::<T>((work_period + One::one()).into());
+
+    }: submit_oracle_judgment(RawOrigin::Root, oracle.clone(), bounty_id, judgment.clone())
+    verify {
+        for entry_id in entry_ids {
+            assert!(!<Entries<T>>::contains_key(entry_id));
+        }
+        assert_last_event::<T>(
+            Event::<T>::OracleJudgmentSubmitted(bounty_id, oracle, judgment).into()
+        );
+    }
+
+    submit_oracle_judgment_by_member_all_winners {
+        let i in 1 .. MAX_WORK_ENTRIES;
+
+        let work_period: T::BlockNumber = One::one();
+        let cherry: BalanceOf<T> = 100.into();
+        let funding_amount: BalanceOf<T> = 10000000.into();
+        let work_period: T::BlockNumber = One::one();
+        let (oracle_account_id, oracle_member_id) = member_funded_account::<T>("oracle", 1);
+        let oracle = BountyActor::Member(oracle_member_id);
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        let params = BountyCreationParameters::<T> {
+            work_period,
+            judging_period: One::one(),
+            creator: BountyActor::Council,
+            cherry,
+            entrant_stake,
+            funding_type: FundingType::Perpetual{ target: funding_amount },
+            oracle: oracle.clone(),
+            ..Default::default()
+        };
+
+        let bounty_id = create_max_funded_bounty::<T>(params);
+
+        let entry_ids = (0..i)
+            .into_iter()
+            .map(|i| { announce_entry_and_submit_work::<T>(&bounty_id, i)})
+            .collect::<Vec<_>>();
+
+        let winner_reward: BalanceOf<T> = (funding_amount / i.into()).into();
+        let correction = funding_amount - winner_reward * i.into(); // for total sum = 100%
+        let judgment = entry_ids
+            .iter()
+            .map(|entry_id| {
+                let corrected_winner_reward = if *entry_id == One::one() {
+                    winner_reward + correction
+                } else {
+                    winner_reward
+                };
+
+                (*entry_id, OracleWorkEntryJudgment::Winner {reward : corrected_winner_reward})
+            })
+            .collect::<BTreeMap<_, _>>();
+
+        run_to_block::<T>((work_period + One::one()).into());
+
+    }: submit_oracle_judgment(
+        RawOrigin::Signed(oracle_account_id),
+        oracle.clone(),
+        bounty_id,
+        judgment.clone()
+    )
+    verify {
+        for entry_id in entry_ids {
+            let entry = Bounty::<T>::entries(entry_id);
+            let corrected_winner_reward = if entry_id == One::one() {
+                    winner_reward + correction
+                } else {
+                    winner_reward
+                };
+            assert_eq!(
+                entry.oracle_judgment_result,
+                Some(OracleWorkEntryJudgment::Winner {reward : corrected_winner_reward})
+            );
+        }
+        assert_last_event::<T>(
+            Event::<T>::OracleJudgmentSubmitted(bounty_id, oracle, judgment).into()
+        );
+    }
+
+    submit_oracle_judgment_by_member_all_rejected {
+        let i in 1 .. MAX_WORK_ENTRIES;
+
+        let work_period: T::BlockNumber = One::one();
+        let cherry: BalanceOf<T> = 100.into();
+        let funding_amount: BalanceOf<T> = 100.into();
+        let work_period: T::BlockNumber = One::one();
+        let (oracle_account_id, oracle_member_id) = member_funded_account::<T>("oracle", 1);
+        let oracle = BountyActor::Member(oracle_member_id);
+        let entrant_stake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        let params = BountyCreationParameters::<T> {
+            work_period,
+            judging_period: One::one(),
+            creator: BountyActor::Council,
+            cherry,
+            entrant_stake,
+            funding_type: FundingType::Perpetual{ target: funding_amount },
+            oracle: oracle.clone(),
+            ..Default::default()
+        };
+
+        let bounty_id = create_max_funded_bounty::<T>(params);
+
+        let entry_ids = (0..i)
+            .into_iter()
+            .map(|i| { announce_entry_and_submit_work::<T>(&bounty_id, i)})
+            .collect::<Vec<_>>();
+
+        let judgment = entry_ids.iter()
+            .map(|entry_id| (*entry_id, OracleWorkEntryJudgment::Rejected))
+            .collect::<BTreeMap<_, _>>();
+
+        run_to_block::<T>((work_period + One::one()).into());
+
+    }: submit_oracle_judgment(
+        RawOrigin::Signed(oracle_account_id),
+        oracle.clone(),
+        bounty_id,
+        judgment.clone()
+    )
+    verify {
+        for entry_id in entry_ids {
+            assert!(!<Entries<T>>::contains_key(entry_id));
+        }
+        assert_last_event::<T>(
+            Event::<T>::OracleJudgmentSubmitted(bounty_id, oracle, judgment).into()
+        );
+    }
+
+    withdraw_work_entrant_funds {
+        let work_period: T::BlockNumber = One::one();
+        let cherry: BalanceOf<T> = 100.into();
+        let funding_amount: BalanceOf<T> = 100.into();
+        let work_period: T::BlockNumber = One::one();
+        let (oracle_account_id, oracle_member_id) = member_funded_account::<T>("oracle", 1);
+        let oracle = BountyActor::Member(oracle_member_id);
+        let stake: BalanceOf<T> = 100.into();
+        let creator = BountyActor::Council;
+
+        let params = BountyCreationParameters::<T> {
+            work_period,
+            judging_period: One::one(),
+            creator: creator.clone(),
+            cherry,
+            funding_type: FundingType::Perpetual{ target: funding_amount },
+            oracle: oracle.clone(),
+            entrant_stake: stake,
+            ..Default::default()
+        };
+
+        let bounty_id = create_max_funded_bounty::<T>(params);
+
+        let (work_account_id, work_member_id) = member_funded_account::<T>("work entrants", 0);
+
+        Bounty::<T>::announce_work_entry(
+            RawOrigin::Signed(work_account_id.clone()).into(),
+            work_member_id,
+            bounty_id,
+            work_account_id.clone(),
+        )
+        .unwrap();
+
+        let entry_id: T::EntryId = Bounty::<T>::entry_count().into();
+
+        let work_data = b"work_data".to_vec();
+        Bounty::<T>::submit_work(
+            RawOrigin::Signed(work_account_id.clone()).into(),
+            work_member_id,
+            bounty_id,
+            entry_id,
+            work_data,
+        )
+        .unwrap();
+
+        let winner_reward: BalanceOf<T> = funding_amount;
+        let judgment = vec![entry_id].iter()
+            .map(|entry_id| (*entry_id, OracleWorkEntryJudgment::Winner {reward : winner_reward}))
+            .collect::<BTreeMap<_, _>>();
+
+        run_to_block::<T>((work_period + One::one()).into());
+
+        Bounty::<T>::submit_oracle_judgment(
+            RawOrigin::Signed(oracle_account_id).into(),
+            oracle.clone(),
+            bounty_id,
+            judgment.clone()
+        ).unwrap();
+
+    }: _ (RawOrigin::Signed(work_account_id), work_member_id, bounty_id, entry_id)
+    verify {
+        assert!(!<Entries<T>>::contains_key(entry_id));
+        assert_was_fired::<T>(
+            Event::<T>::WorkEntrantFundsWithdrawn(bounty_id, entry_id, work_member_id).into()
+        );
+        assert_last_event::<T>(Event::<T>::BountyRemoved(bounty_id).into());
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::mocks::{build_test_externalities, Test};
+    use frame_support::assert_ok;
+
+    #[test]
+    fn create_bounty_by_council() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_create_bounty_by_council::<Test>());
+        });
+    }
+
+    #[test]
+    fn create_bounty_by_member() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_create_bounty_by_member::<Test>());
+        });
+    }
+
+    #[test]
+    fn cancel_bounty_by_council() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_cancel_bounty_by_council::<Test>());
+        });
+    }
+
+    #[test]
+    fn cancel_bounty_by_member() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_cancel_bounty_by_member::<Test>());
+        });
+    }
+
+    #[test]
+    fn veto_bounty() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_veto_bounty::<Test>());
+        });
+    }
+
+    #[test]
+    fn fund_bounty_by_member() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_fund_bounty_by_member::<Test>());
+        });
+    }
+
+    #[test]
+    fn fund_bounty_by_council() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_fund_bounty_by_council::<Test>());
+        });
+    }
+
+    #[test]
+    fn withdraw_funding_by_member() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_withdraw_funding_by_member::<Test>());
+        });
+    }
+
+    #[test]
+    fn withdraw_funding_by_council() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_withdraw_funding_by_council::<Test>());
+        });
+    }
+
+    #[test]
+    fn announce_work_entry() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_announce_work_entry::<Test>());
+        });
+    }
+
+    #[test]
+    fn withdraw_work_entry() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_withdraw_work_entry::<Test>());
+        });
+    }
+
+    #[test]
+    fn submit_work() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_submit_work::<Test>());
+        });
+    }
+
+    #[test]
+    fn submit_oracle_judgment_by_council_all_winners() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_submit_oracle_judgment_by_council_all_winners::<Test>());
+        });
+    }
+
+    #[test]
+    fn submit_oracle_judgment_by_council_all_rejected() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_submit_oracle_judgment_by_council_all_rejected::<Test>());
+        });
+    }
+
+    #[test]
+    fn submit_oracle_judgment_by_member_all_winners() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_submit_oracle_judgment_by_member_all_winners::<Test>());
+        });
+    }
+
+    #[test]
+    fn submit_oracle_judgment_by_member_all_rejected() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_submit_oracle_judgment_by_member_all_rejected::<Test>());
+        });
+    }
+
+    #[test]
+    fn withdraw_work_entrant_funds() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_withdraw_work_entrant_funds::<Test>());
+        });
+    }
+}

+ 1731 - 0
runtime-modules/bounty/src/lib.rs

@@ -0,0 +1,1731 @@
+//! This pallet works with crowd funded bounties that allows a member, or the council, to crowd
+//! fund work on projects with a public benefit.
+//!
+//! ### Bounty stages
+//! - Funding - a bounty is being funded.
+//! - FundingExpired - a bounty is expired. It can be only canceled.
+//! - WorkSubmission - interested participants can submit their work.
+//! - Judgment - working periods ended and the oracle should provide their judgment.
+//! - SuccessfulBountyWithdrawal - work entrants' stakes and rewards can be withdrawn.
+//! - FailedBountyWithdrawal - contributors' funds can be withdrawn along with a split cherry.
+//!
+//! A detailed description could be found [here](https://github.com/Joystream/joystream/issues/1998).
+//!
+//! ### Supported extrinsics
+//! - [create_bounty](./struct.Module.html#method.create_bounty) - creates a bounty
+//!
+//! #### Funding stage
+//! - [cancel_bounty](./struct.Module.html#method.cancel_bounty) - cancels a bounty
+//! - [veto_bounty](./struct.Module.html#method.veto_bounty) - vetoes a bounty
+//! - [fund_bounty](./struct.Module.html#method.fund_bounty) - provide funding for a bounty
+//!
+//! #### FundingExpired stage
+//! - [cancel_bounty](./struct.Module.html#method.cancel_bounty) - cancels a bounty
+//!
+//! #### Work submission stage
+//! - [announce_work_entry](./struct.Module.html#method.announce_work_entry) - announce
+//! work entry for a successful bounty.
+//! - [withdraw_work_entry](./struct.Module.html#method.withdraw_work_entry) - withdraw
+//! work entry for a bounty.
+//! - [submit_work](./struct.Module.html#method.submit_work) - submit work for a bounty.
+//!
+//! #### Judgment stage
+//! - [submit_oracle_judgment](./struct.Module.html#method.submit_oracle_judgment) - submits an
+//! oracle judgment for a bounty.
+//!
+//! #### SuccessfulBountyWithdrawal stage
+//! - [withdraw_work_entrant_funds](./struct.Module.html#method.withdraw_work_entrant_funds) -
+//! withdraw work entrant funds.
+//!
+//! #### FailedBountyWithdrawal stage
+//! - [withdraw_funding](./struct.Module.html#method.withdraw_funding) - withdraw
+//! funding for a failed bounty.
+
+// Ensure we're `no_std` when compiling for Wasm.
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(test)]
+pub(crate) mod tests;
+
+mod actors;
+mod stages;
+
+#[cfg(feature = "runtime-benchmarks")]
+mod benchmarking;
+
+/// pallet_bounty WeightInfo.
+/// Note: This was auto generated through the benchmark CLI using the `--weight-trait` flag
+pub trait WeightInfo {
+    fn create_bounty_by_council(i: u32, j: u32) -> Weight;
+    fn create_bounty_by_member(i: u32, j: u32) -> Weight;
+    fn cancel_bounty_by_member() -> Weight;
+    fn cancel_bounty_by_council() -> Weight;
+    fn veto_bounty() -> Weight;
+    fn fund_bounty_by_member() -> Weight;
+    fn fund_bounty_by_council() -> Weight;
+    fn withdraw_funding_by_member() -> Weight;
+    fn withdraw_funding_by_council() -> Weight;
+    fn announce_work_entry(i: u32) -> Weight;
+    fn withdraw_work_entry() -> Weight;
+    fn submit_work(i: u32) -> Weight;
+    fn submit_oracle_judgment_by_council_all_winners(i: u32) -> Weight;
+    fn submit_oracle_judgment_by_council_all_rejected(i: u32) -> Weight;
+    fn submit_oracle_judgment_by_member_all_winners(i: u32) -> Weight;
+    fn submit_oracle_judgment_by_member_all_rejected(i: u32) -> Weight;
+    fn withdraw_work_entrant_funds() -> Weight;
+}
+
+type WeightInfoBounty<T> = <T as Trait>::WeightInfo;
+
+pub(crate) use actors::BountyActorManager;
+pub(crate) use stages::BountyStageCalculator;
+
+use codec::{Decode, Encode};
+#[cfg(feature = "std")]
+use serde::{Deserialize, Serialize};
+
+use frame_support::dispatch::{DispatchError, DispatchResult};
+use frame_support::traits::{Currency, ExistenceRequirement, Get};
+use frame_support::weights::Weight;
+use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure, Parameter};
+use frame_system::ensure_root;
+use sp_arithmetic::traits::{One, Saturating, Zero};
+use sp_runtime::{traits::AccountIdConversion, ModuleId};
+use sp_runtime::{Perbill, SaturatedConversion};
+use sp_std::collections::btree_map::BTreeMap;
+use sp_std::collections::btree_set::BTreeSet;
+use sp_std::vec::Vec;
+
+use common::council::CouncilBudgetManager;
+use common::membership::{
+    MemberId, MemberOriginValidator, MembershipInfoProvider, StakingAccountValidator,
+};
+use staking_handler::StakingHandler;
+
+/// Main pallet-bounty trait.
+pub trait Trait: frame_system::Trait + balances::Trait + common::membership::Trait {
+    /// Events
+    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
+
+    /// The bounty's module id, used for deriving its sovereign account ID.
+    type ModuleId: Get<ModuleId>;
+
+    /// Bounty Id type
+    type BountyId: From<u32> + Parameter + Default + Copy;
+
+    /// Validates staking account ownership for a member, member ID and origin combination and
+    /// providers controller id for a member.
+    type Membership: StakingAccountValidator<Self>
+        + MembershipInfoProvider<Self>
+        + MemberOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
+
+    /// Weight information for extrinsics in this pallet.
+    type WeightInfo: WeightInfo;
+
+    /// Provides an access for the council budget.
+    type CouncilBudgetManager: CouncilBudgetManager<BalanceOf<Self>>;
+
+    /// Provides stake logic implementation.
+    type StakingHandler: StakingHandler<Self::AccountId, BalanceOf<Self>, MemberId<Self>>;
+
+    /// Work entry Id type
+    type EntryId: From<u32> + Parameter + Default + Copy + Ord + One;
+
+    /// Defines max work entry number for a closed assurance type contract bounty.
+    type ClosedContractSizeLimit: Get<u32>;
+
+    /// Defines min cherry for a bounty.
+    type MinCherryLimit: Get<BalanceOf<Self>>;
+
+    /// Defines min funding amount for a bounty.
+    type MinFundingLimit: Get<BalanceOf<Self>>;
+
+    /// Defines min work entrant stake for a bounty.
+    type MinWorkEntrantStake: Get<BalanceOf<Self>>;
+}
+
+/// Alias type for the BountyParameters.
+pub type BountyCreationParameters<T> = BountyParameters<
+    BalanceOf<T>,
+    <T as frame_system::Trait>::BlockNumber,
+    <T as common::membership::Trait>::MemberId,
+>;
+
+/// Defines who can submit the work.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum AssuranceContractType<MemberId: Ord> {
+    /// Anyone can submit the work.
+    Open,
+
+    /// Only specific members can submit the work.
+    Closed(BTreeSet<MemberId>),
+}
+
+impl<MemberId: Ord> Default for AssuranceContractType<MemberId> {
+    fn default() -> Self {
+        AssuranceContractType::Open
+    }
+}
+
+/// Defines funding conditions.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum FundingType<BlockNumber, Balance> {
+    /// Funding has no time limits.
+    Perpetual {
+        /// Desired funding.
+        target: Balance,
+    },
+
+    /// Funding has a time limitation.
+    Limited {
+        /// Minimum amount of funds for a successful bounty.
+        min_funding_amount: Balance,
+
+        /// Upper boundary for a bounty funding.
+        max_funding_amount: Balance,
+
+        /// Maximum allowed funding period.
+        funding_period: BlockNumber,
+    },
+}
+
+impl<BlockNumber, Balance: Default> Default for FundingType<BlockNumber, Balance> {
+    fn default() -> Self {
+        Self::Perpetual {
+            target: Default::default(),
+        }
+    }
+}
+
+/// Defines parameters for the bounty creation.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct BountyParameters<Balance, BlockNumber, MemberId: Ord> {
+    /// Origin that will select winner(s), is either a given member or a council.
+    pub oracle: BountyActor<MemberId>,
+
+    /// Contract type defines who can submit the work.
+    pub contract_type: AssuranceContractType<MemberId>,
+
+    /// Bounty creator: could be a member or a council.
+    pub creator: BountyActor<MemberId>,
+
+    /// An amount of funding provided by the creator which will be split among all other
+    /// contributors should the bounty not be successful. If successful, cherry is returned to
+    /// the creator. When council is creating bounty, this comes out of their budget, when a member
+    /// does it, it comes from an account.
+    pub cherry: Balance,
+
+    /// Amount of stake required to enter bounty as entrant.
+    pub entrant_stake: Balance,
+
+    /// Defines parameters for different funding types.
+    pub funding_type: FundingType<BlockNumber, Balance>,
+
+    /// Number of blocks from end of funding period until people can no longer submit
+    /// bounty submissions.
+    pub work_period: BlockNumber,
+
+    /// Number of block from end of work period until oracle can no longer decide winners.
+    pub judging_period: BlockNumber,
+}
+
+/// Bounty actor to perform operations for a bounty.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum BountyActor<MemberId> {
+    /// Council performs operations for a bounty.
+    Council,
+
+    /// Member performs operations for a bounty.
+    Member(MemberId),
+}
+
+impl<MemberId> Default for BountyActor<MemberId> {
+    fn default() -> Self {
+        BountyActor::Council
+    }
+}
+
+/// Defines current bounty stage.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Copy)]
+pub enum BountyStage {
+    /// Bounty funding stage.
+    Funding {
+        /// Bounty has already some contributions.
+        has_contributions: bool,
+    },
+
+    /// Bounty funding period expired with no contributions.
+    FundingExpired,
+
+    /// A bounty has gathered necessary funds and ready to accept work submissions.
+    WorkSubmission,
+
+    /// Working periods ended and the oracle should provide their judgment.
+    Judgment,
+
+    /// Indicates a withdrawal on bounty success. Workers get rewards and their stake.
+    SuccessfulBountyWithdrawal,
+
+    /// Indicates a withdrawal on bounty failure. Workers get their stake back. Funders
+    /// get their contribution back as well as part of the cherry.
+    FailedBountyWithdrawal,
+}
+
+/// Defines current bounty state.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum BountyMilestone<BlockNumber> {
+    /// Bounty was created at given block number.
+    /// Boolean value defines whether the bounty has some funding contributions.
+    Created {
+        /// Bounty creation block.
+        created_at: BlockNumber,
+        /// Bounty has already some contributions.
+        has_contributions: bool,
+    },
+
+    /// A bounty funding was successful and it exceeded max funding amount.
+    BountyMaxFundingReached {
+        ///  A bounty funding was successful on the provided block.
+        max_funding_reached_at: BlockNumber,
+    },
+
+    /// Some work was submitted for a bounty.
+    WorkSubmitted {
+        ///  Starting block for the work period.
+        work_period_started_at: BlockNumber,
+    },
+
+    /// A judgment was submitted for a bounty.
+    JudgmentSubmitted {
+        /// The bounty judgment contains at least a single winner.
+        successful_bounty: bool,
+    },
+}
+
+impl<BlockNumber: Default> Default for BountyMilestone<BlockNumber> {
+    fn default() -> Self {
+        BountyMilestone::Created {
+            created_at: Default::default(),
+            has_contributions: false,
+        }
+    }
+}
+
+/// Alias type for the Bounty.
+pub type Bounty<T> = BountyRecord<
+    BalanceOf<T>,
+    <T as frame_system::Trait>::BlockNumber,
+    <T as common::membership::Trait>::MemberId,
+>;
+
+/// Crowdfunded bounty record.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct BountyRecord<Balance, BlockNumber, MemberId: Ord> {
+    /// Bounty creation parameters.
+    pub creation_params: BountyParameters<Balance, BlockNumber, MemberId>,
+
+    /// Total funding balance reached so far.
+    /// Includes initial funding by a creator and other members funding.
+    pub total_funding: Balance,
+
+    /// Bounty current milestone(state). It represents fact known about the bounty, eg.:
+    /// it was canceled or max funding amount was reached.
+    pub milestone: BountyMilestone<BlockNumber>,
+
+    /// Current active work entry counter.
+    pub active_work_entry_count: u32,
+}
+
+impl<Balance: PartialOrd + Clone, BlockNumber: Clone, MemberId: Ord>
+    BountyRecord<Balance, BlockNumber, MemberId>
+{
+    // Increments bounty active work entry counter.
+    fn increment_active_work_entry_counter(&mut self) {
+        self.active_work_entry_count += 1;
+    }
+
+    // Decrements bounty active work entry counter. Nothing happens on zero counter.
+    fn decrement_active_work_entry_counter(&mut self) {
+        if self.active_work_entry_count > 0 {
+            self.active_work_entry_count -= 1;
+        }
+    }
+
+    // Defines whether the maximum funding amount will be reached for the current funding type.
+    fn is_maximum_funding_reached(&self, total_funding: Balance) -> bool {
+        match self.creation_params.funding_type {
+            FundingType::Perpetual { ref target } => total_funding >= *target,
+            FundingType::Limited {
+                ref max_funding_amount,
+                ..
+            } => total_funding >= *max_funding_amount,
+        }
+    }
+
+    // Returns the maximum funding amount for the current funding type.
+    pub(crate) fn maximum_funding(&self) -> Balance {
+        match self.creation_params.funding_type.clone() {
+            FundingType::Perpetual { target } => target,
+            FundingType::Limited {
+                max_funding_amount, ..
+            } => max_funding_amount,
+        }
+    }
+}
+
+/// Alias type for the Entry.
+pub type Entry<T> = EntryRecord<
+    <T as frame_system::Trait>::AccountId,
+    <T as common::membership::Trait>::MemberId,
+    <T as frame_system::Trait>::BlockNumber,
+    BalanceOf<T>,
+>;
+
+/// Work entry.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct EntryRecord<AccountId, MemberId, BlockNumber, Balance> {
+    /// Work entrant member ID.
+    pub member_id: MemberId,
+
+    /// Account ID for staking lock.
+    pub staking_account_id: AccountId,
+
+    /// Work entry submission block.
+    pub submitted_at: BlockNumber,
+
+    /// Signifies that an entry has at least one submitted work.
+    pub work_submitted: bool,
+
+    /// Optional oracle judgment for the work entry.
+    /// Absent value means neither winner nor rejected entry - "legitimate user" that gets their
+    /// stake back without slashing but doesn't get a reward.
+    pub oracle_judgment_result: Option<OracleWorkEntryJudgment<Balance>>,
+}
+
+/// Defines the oracle judgment for the work entry.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Copy)]
+pub enum OracleWorkEntryJudgment<Balance> {
+    /// The work entry is selected as a winner.
+    Winner { reward: Balance },
+
+    /// The work entry is considered harmful. The stake will be slashed.
+    Rejected,
+}
+
+impl<Balance> Default for OracleWorkEntryJudgment<Balance> {
+    fn default() -> Self {
+        Self::Rejected
+    }
+}
+
+impl<Balance> OracleWorkEntryJudgment<Balance> {
+    // Work entry judgment helper. Returns true for winners.
+    pub(crate) fn is_winner(&self) -> bool {
+        matches!(*self, Self::Winner { .. })
+    }
+}
+
+/// Balance alias for `balances` module.
+pub type BalanceOf<T> = <T as balances::Trait>::Balance;
+
+// Entrant stake helper struct.
+struct RequiredStakeInfo<T: Trait> {
+    // stake amount
+    amount: BalanceOf<T>,
+    // staking_account_id
+    account_id: T::AccountId,
+}
+
+/// An alias for the OracleJudgment.
+pub type OracleJudgmentOf<T> = OracleJudgment<<T as Trait>::EntryId, BalanceOf<T>>;
+
+/// The collection of the oracle judgments for the work entries.
+pub type OracleJudgment<EntryId, Balance> = BTreeMap<EntryId, OracleWorkEntryJudgment<Balance>>;
+
+decl_storage! {
+    trait Store for Module<T: Trait> as Bounty {
+        /// Bounty storage.
+        pub Bounties get(fn bounties) : map hasher(blake2_128_concat) T::BountyId => Bounty<T>;
+
+        /// Double map for bounty funding. It stores a member or council funding for bounties.
+        pub BountyContributions get(fn contribution_by_bounty_by_actor): double_map
+            hasher(blake2_128_concat) T::BountyId,
+            hasher(blake2_128_concat) BountyActor<MemberId<T>> => BalanceOf<T>;
+
+        /// Count of all bounties that have been created.
+        pub BountyCount get(fn bounty_count): u32;
+
+        /// Work entry storage map.
+        pub Entries get(fn entries): map hasher(blake2_128_concat) T::EntryId => Entry<T>;
+
+        /// Count of all work entries that have been created.
+        pub EntryCount get(fn entry_count): u32;
+    }
+}
+
+decl_event! {
+    pub enum Event<T>
+    where
+        <T as Trait>::BountyId,
+        <T as Trait>::EntryId,
+        Balance = BalanceOf<T>,
+        MemberId = MemberId<T>,
+        <T as frame_system::Trait>::AccountId,
+        BountyCreationParameters = BountyCreationParameters<T>,
+        OracleJudgment = OracleJudgmentOf<T>,
+    {
+        /// A bounty was created.
+        /// Params:
+        /// - bounty ID
+        /// - creation parameters
+        /// - bounty metadata
+        BountyCreated(BountyId, BountyCreationParameters, Vec<u8>),
+
+        /// A bounty was canceled.
+        /// Params:
+        /// - bounty ID
+        /// - bounty creator
+        BountyCanceled(BountyId, BountyActor<MemberId>),
+
+        /// A bounty was vetoed.
+        /// Params:
+        /// - bounty ID
+        BountyVetoed(BountyId),
+
+        /// A bounty was funded by a member or a council.
+        /// Params:
+        /// - bounty ID
+        /// - bounty funder
+        /// - funding amount
+        BountyFunded(BountyId, BountyActor<MemberId>, Balance),
+
+        /// A bounty has reached its maximum funding amount.
+        /// Params:
+        /// - bounty ID
+        BountyMaxFundingReached(BountyId),
+
+        /// A member or a council has withdrawn the funding.
+        /// Params:
+        /// - bounty ID
+        /// - bounty funder
+        BountyFundingWithdrawal(BountyId, BountyActor<MemberId>),
+
+        /// A bounty creator has withdrawn the cherry (member or council).
+        /// Params:
+        /// - bounty ID
+        /// - bounty creator
+        BountyCreatorCherryWithdrawal(BountyId, BountyActor<MemberId>),
+
+        /// A bounty was removed.
+        /// Params:
+        /// - bounty ID
+        BountyRemoved(BountyId),
+
+        /// Work entry was announced.
+        /// Params:
+        /// - bounty ID
+        /// - created entry ID
+        /// - entrant member ID
+        /// - staking account ID
+        WorkEntryAnnounced(BountyId, EntryId, MemberId, AccountId),
+
+        /// Work entry was withdrawn.
+        /// Params:
+        /// - bounty ID
+        /// - entry ID
+        /// - entrant member ID
+        WorkEntryWithdrawn(BountyId, EntryId, MemberId),
+
+        /// Work entry was slashed.
+        /// Params:
+        /// - bounty ID
+        /// - entry ID
+        WorkEntrySlashed(BountyId, EntryId),
+
+        /// Submit work.
+        /// Params:
+        /// - bounty ID
+        /// - created entry ID
+        /// - entrant member ID
+        /// - work data (description, URL, BLOB, etc.)
+        WorkSubmitted(BountyId, EntryId, MemberId, Vec<u8>),
+
+        /// Submit oracle judgment.
+        /// Params:
+        /// - bounty ID
+        /// - oracle
+        /// - judgment data
+        OracleJudgmentSubmitted(BountyId, BountyActor<MemberId>, OracleJudgment),
+
+        /// Work entry was slashed.
+        /// Params:
+        /// - bounty ID
+        /// - entry ID
+        /// - entrant member ID
+        WorkEntrantFundsWithdrawn(BountyId, EntryId, MemberId),
+    }
+}
+
+decl_error! {
+    /// Bounty pallet predefined errors
+    pub enum Error for Module<T: Trait> {
+        /// Min funding amount cannot be greater than max amount.
+        MinFundingAmountCannotBeGreaterThanMaxAmount,
+
+        /// Bounty doesnt exist.
+        BountyDoesntExist,
+
+        /// Operation can be performed only by a bounty creator.
+        NotBountyActor,
+
+        /// Work period cannot be zero.
+        WorkPeriodCannotBeZero,
+
+        /// Judging period cannot be zero.
+        JudgingPeriodCannotBeZero,
+
+        /// Unexpected bounty stage for an operation: Funding.
+        InvalidStageUnexpectedFunding,
+
+        /// Unexpected bounty stage for an operation: FundingExpired.
+        InvalidStageUnexpectedFundingExpired,
+
+        /// Unexpected bounty stage for an operation: WorkSubmission.
+        InvalidStageUnexpectedWorkSubmission,
+
+        /// Unexpected bounty stage for an operation: Judgment.
+        InvalidStageUnexpectedJudgment,
+
+        /// Unexpected bounty stage for an operation: SuccessfulBountyWithdrawal.
+        InvalidStageUnexpectedSuccessfulBountyWithdrawal,
+
+        /// Unexpected bounty stage for an operation: FailedBountyWithdrawal.
+        InvalidStageUnexpectedFailedBountyWithdrawal,
+
+        /// Insufficient balance for a bounty cherry.
+        InsufficientBalanceForBounty,
+
+        /// Funding period is not expired for the bounty.
+        FundingPeriodNotExpired,
+
+        /// Cannot found bounty contribution.
+        NoBountyContributionFound,
+
+        /// There is nothing to withdraw.
+        NothingToWithdraw,
+
+        /// Incorrect funding amount.
+        ZeroFundingAmount,
+
+        /// There is not enough balance for a stake.
+        InsufficientBalanceForStake,
+
+        /// The conflicting stake discovered. Cannot stake.
+        ConflictingStakes,
+
+        /// Work entry doesnt exist.
+        WorkEntryDoesntExist,
+
+        /// Cannot add work entry because of the limit.
+        MaxWorkEntryLimitReached,
+
+        /// Cherry less then minimum allowed.
+        CherryLessThenMinimumAllowed,
+
+        /// Funding amount less then minimum allowed.
+        FundingLessThenMinimumAllowed,
+
+        /// Incompatible assurance contract type for a member: cannot submit work to the 'closed
+        /// assurance' bounty contract.
+        CannotSubmitWorkToClosedContractBounty,
+
+        /// Cannot create a 'closed assurance contract' bounty with empty member list.
+        ClosedContractMemberListIsEmpty,
+
+        /// Cannot create a 'closed assurance contract' bounty with member list larger
+        /// than allowed max work entry limit.
+        ClosedContractMemberListIsTooLarge,
+
+        /// Staking account doesn't belong to a member.
+        InvalidStakingAccountForMember,
+
+        /// Cannot set zero reward for winners.
+        ZeroWinnerReward,
+
+        /// The total reward for winners should be equal to total bounty funding.
+        TotalRewardShouldBeEqualToTotalFunding,
+
+        /// Cannot create a bounty with an entrant stake is less than required minimum.
+        EntrantStakeIsLessThanMininum,
+
+        /// Cannot create a bounty with zero funding amount parameter.
+        FundingAmountCannotBeZero,
+
+        /// Cannot create a bounty with zero funding period parameter.
+        FundingPeriodCannotBeZero,
+
+        /// Cannot submit a judgment without active work entries. A probable case for an error:
+        /// an entry with a single submission for a bounty was withdrawn.
+        NoActiveWorkEntries,
+
+        /// Invalid judgment - all winners should have work submissions.
+        WinnerShouldHasWorkSubmission,
+    }
+}
+
+decl_module! {
+    /// Bounty pallet Substrate Module
+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+        /// Predefined errors
+        type Error = Error<T>;
+
+        /// Emits an event. Default substrate implementation.
+        fn deposit_event() = default;
+
+        /// Exports const - max work entry number for a closed assurance type contract bounty.
+        const ClosedContractSizeLimit: u32 = T::ClosedContractSizeLimit::get();
+
+        /// Exports const - min cherry value limit for a bounty.
+        const MinCherryLimit: BalanceOf<T> = T::MinCherryLimit::get();
+
+        /// Exports const - min funding amount limit for a bounty.
+        const MinFundingLimit: BalanceOf<T> = T::MinFundingLimit::get();
+
+        /// Exports const - min work entrant stake for a bounty.
+        const MinWorkEntrantStake: BalanceOf<T> = T::MinWorkEntrantStake::get();
+
+        /// Creates a bounty. Metadata stored in the transaction log but discarded after that.
+        /// <weight>
+        ///
+        /// ## Weight
+        /// `O (W)` where:
+        /// - `W` is the _metadata length.
+        /// - `M` is closed contract member list length.
+        /// - DB:
+        ///    - O(M) (O(1) on open contract)
+        /// # </weight>
+        #[weight = Module::<T>::create_bounty_weight(&params, &metadata)]
+        pub fn create_bounty(origin, params: BountyCreationParameters<T>, metadata: Vec<u8>) {
+            let bounty_creator_manager = BountyActorManager::<T>::ensure_bounty_actor_manager(
+                origin,
+                params.creator.clone()
+            )?;
+
+            Self::ensure_create_bounty_parameters_valid(&params)?;
+
+            bounty_creator_manager.validate_balance_sufficiency(params.cherry)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            let next_bounty_count_value = Self::bounty_count() + 1;
+            let bounty_id = T::BountyId::from(next_bounty_count_value);
+
+            bounty_creator_manager.transfer_funds_to_bounty_account(bounty_id, params.cherry)?;
+
+            let created_bounty_milestone = BountyMilestone::Created {
+                created_at: Self::current_block(),
+                has_contributions: false, // just created - no contributions
+            };
+
+            let bounty = Bounty::<T> {
+                total_funding: Zero::zero(),
+                creation_params: params.clone(),
+                milestone: created_bounty_milestone,
+                active_work_entry_count: 0,
+            };
+
+            <Bounties<T>>::insert(bounty_id, bounty);
+            BountyCount::mutate(|count| {
+                *count = next_bounty_count_value
+            });
+            Self::deposit_event(RawEvent::BountyCreated(bounty_id, params, metadata));
+        }
+
+        /// Cancels a bounty.
+        /// It returns a cherry to creator and removes bounty.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (1)`
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight = WeightInfoBounty::<T>::cancel_bounty_by_member()
+              .max(WeightInfoBounty::<T>::cancel_bounty_by_council())]
+        pub fn cancel_bounty(origin, creator: BountyActor<MemberId<T>>, bounty_id: T::BountyId) {
+            let bounty_creator_manager = BountyActorManager::<T>::ensure_bounty_actor_manager(
+                origin,
+                creator.clone(),
+            )?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            bounty_creator_manager.validate_actor(&bounty.creation_params.creator)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+
+            Self::ensure_bounty_stage_for_canceling(current_bounty_stage)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            Self::return_bounty_cherry_to_creator(bounty_id, &bounty)?;
+
+            Self::remove_bounty(&bounty_id);
+
+            Self::deposit_event(RawEvent::BountyCanceled(bounty_id, creator));
+        }
+
+        /// Vetoes a bounty.
+        /// It returns a cherry to creator and removes bounty.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (1)`
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight = WeightInfoBounty::<T>::veto_bounty()]
+        pub fn veto_bounty(origin, bounty_id: T::BountyId) {
+            ensure_root(origin)?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+
+            Self::ensure_bounty_stage(
+                current_bounty_stage,
+                BountyStage::Funding { has_contributions: false }
+            )?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            Self::return_bounty_cherry_to_creator(bounty_id, &bounty)?;
+
+            Self::remove_bounty(&bounty_id);
+
+            Self::deposit_event(RawEvent::BountyVetoed(bounty_id));
+        }
+
+        /// Provides bounty funding.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (1)`
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight = WeightInfoBounty::<T>::fund_bounty_by_member()
+              .max(WeightInfoBounty::<T>::fund_bounty_by_council())]
+        pub fn fund_bounty(
+            origin,
+            funder: BountyActor<MemberId<T>>,
+            bounty_id: T::BountyId,
+            amount: BalanceOf<T>
+        ) {
+            let bounty_funder_manager = BountyActorManager::<T>::ensure_bounty_actor_manager(
+                origin,
+                funder.clone(),
+            )?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            ensure!(amount > Zero::zero(), Error::<T>::ZeroFundingAmount);
+
+            ensure!(amount >= T::MinFundingLimit::get(), Error::<T>::FundingLessThenMinimumAllowed);
+
+            bounty_funder_manager.validate_balance_sufficiency(amount)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+            ensure!(
+                matches!(current_bounty_stage, BountyStage::Funding{..}),
+                Self::unexpected_bounty_stage_error(current_bounty_stage),
+            );
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            let maximum_funding_reached = bounty.is_maximum_funding_reached(
+                bounty.total_funding.saturating_add(amount)
+            );
+
+            //
+            let actual_funding = if maximum_funding_reached {
+                bounty.maximum_funding().saturating_sub(bounty.total_funding)
+            } else {
+                amount
+            };
+
+            bounty_funder_manager.transfer_funds_to_bounty_account(bounty_id, actual_funding)?;
+
+
+            let new_milestone = Self::get_bounty_milestone_on_funding(
+                    maximum_funding_reached,
+                    bounty.milestone
+            );
+
+            // Update bounty record.
+            <Bounties<T>>::mutate(bounty_id, |bounty| {
+                bounty.total_funding = bounty.total_funding.saturating_add(actual_funding);
+                bounty.milestone = new_milestone;
+            });
+
+            // Update member funding record checking previous funding.
+            let funds_so_far = Self::contribution_by_bounty_by_actor(bounty_id, &funder);
+            let total_funding = funds_so_far.saturating_add(actual_funding);
+            <BountyContributions<T>>::insert(bounty_id, funder.clone(), total_funding);
+
+            // Fire events.
+            Self::deposit_event(RawEvent::BountyFunded(bounty_id, funder, actual_funding));
+            if  maximum_funding_reached{
+                Self::deposit_event(RawEvent::BountyMaxFundingReached(bounty_id));
+            }
+        }
+
+        /// Withdraw bounty funding by a member or a council.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (1)`
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight = WeightInfoBounty::<T>::withdraw_funding_by_member()
+              .max(WeightInfoBounty::<T>::withdraw_funding_by_council())]
+        pub fn withdraw_funding(
+            origin,
+            funder: BountyActor<MemberId<T>>,
+            bounty_id: T::BountyId,
+        ) {
+            let bounty_funder_manager = BountyActorManager::<T>::ensure_bounty_actor_manager(
+                origin,
+                funder.clone(),
+            )?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+            Self::ensure_bounty_stage(current_bounty_stage, BountyStage::FailedBountyWithdrawal)?;
+
+            ensure!(
+                <BountyContributions<T>>::contains_key(&bounty_id, &funder),
+                Error::<T>::NoBountyContributionFound,
+            );
+
+            let funding_amount = <BountyContributions<T>>::get(&bounty_id, &funder);
+            let cherry_fraction = Self::get_cherry_fraction_for_member(&bounty, funding_amount);
+            let withdrawal_amount = funding_amount + cherry_fraction;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            bounty_funder_manager.transfer_funds_from_bounty_account(bounty_id, withdrawal_amount)?;
+
+            <BountyContributions<T>>::remove(&bounty_id, &funder);
+
+            Self::deposit_event(RawEvent::BountyFundingWithdrawal(bounty_id, funder));
+
+            if Self::withdrawal_completed(&current_bounty_stage, &bounty_id) {
+                Self::remove_bounty(&bounty_id);
+            }
+        }
+
+
+        /// Announce work entry for a successful bounty.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (1)`
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight = WeightInfoBounty::<T>::announce_work_entry(T::ClosedContractSizeLimit::get()
+            .saturated_into())]
+        pub fn announce_work_entry(
+            origin,
+            member_id: MemberId<T>,
+            bounty_id: T::BountyId,
+            staking_account_id: T::AccountId,
+        ) {
+            T::Membership::ensure_member_controller_account_origin(origin, member_id)?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+
+            Self::ensure_bounty_stage(current_bounty_stage, BountyStage::WorkSubmission)?;
+
+            let stake = Self::validate_entrant_stake(
+                member_id,
+                &bounty,
+                staking_account_id.clone()
+            )?;
+
+            Self::ensure_valid_contract_type(&bounty, &member_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            let next_entry_count_value = Self::entry_count() + 1;
+            let entry_id = T::EntryId::from(next_entry_count_value);
+
+            // Lock stake balance for bounty if the stake is required.
+            if let Some(stake) = stake {
+                T::StakingHandler::lock(&stake.account_id, stake.amount);
+            }
+
+            let entry = Entry::<T> {
+                member_id,
+                staking_account_id: staking_account_id.clone(),
+                submitted_at: Self::current_block(),
+                work_submitted: false,
+                oracle_judgment_result: None,
+            };
+
+            <Entries<T>>::insert(entry_id, entry);
+            EntryCount::mutate(|count| {
+                *count = next_entry_count_value
+            });
+
+            // Increment work entry counter and update bounty record.
+            <Bounties<T>>::mutate(bounty_id, |bounty| {
+                bounty.increment_active_work_entry_counter();
+            });
+
+            Self::deposit_event(RawEvent::WorkEntryAnnounced(
+                bounty_id,
+                entry_id,
+                member_id,
+                staking_account_id,
+            ));
+        }
+
+        /// Withdraw work entry for a bounty. Existing stake could be partially slashed.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (1)`
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight = WeightInfoBounty::<T>::withdraw_work_entry()]
+        pub fn withdraw_work_entry(
+            origin,
+            member_id: MemberId<T>,
+            bounty_id: T::BountyId,
+            entry_id: T::EntryId,
+        ) {
+            T::Membership::ensure_member_controller_account_origin(origin, member_id)?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+
+            Self::ensure_bounty_stage(current_bounty_stage, BountyStage::WorkSubmission)?;
+
+            let entry = Self::ensure_work_entry_exists(&entry_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            Self::unlock_work_entry_stake_with_possible_penalty(&bounty, &entry);
+
+            Self::remove_work_entry(&bounty_id, &entry_id);
+
+            Self::deposit_event(RawEvent::WorkEntryWithdrawn(bounty_id, entry_id, member_id));
+        }
+
+        /// Submit work for a bounty.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (N)`
+        /// - `N` is the work_data length,
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight =  WeightInfoBounty::<T>::submit_work(work_data.len().saturated_into())]
+        pub fn submit_work(
+            origin,
+            member_id: MemberId<T>,
+            bounty_id: T::BountyId,
+            entry_id: T::EntryId,
+            work_data: Vec<u8>
+        ) {
+            T::Membership::ensure_member_controller_account_origin(origin, member_id)?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+
+            Self::ensure_bounty_stage(current_bounty_stage, BountyStage::WorkSubmission)?;
+
+            Self::ensure_work_entry_exists(&entry_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Update entry
+            <Entries<T>>::mutate(entry_id, |entry| {
+                entry.work_submitted = true;
+            });
+
+            let new_milestone = Self::get_bounty_milestone_on_work_submitting(&bounty);
+
+            // Update bounty record.
+            <Bounties<T>>::mutate(bounty_id, |bounty| {
+                bounty.milestone = new_milestone;
+            });
+
+            Self::deposit_event(RawEvent::WorkSubmitted(bounty_id, entry_id, member_id, work_data));
+        }
+
+        /// Submits an oracle judgment for a bounty.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (N)`
+        /// - `N` is the work_data length,
+        /// - db:
+        ///    - `O(N)`
+        /// # </weight>
+        #[weight = Module::<T>::submit_oracle_judgement_weight(&judgment)]
+        pub fn submit_oracle_judgment(
+            origin,
+            oracle: BountyActor<MemberId<T>>,
+            bounty_id: T::BountyId,
+            judgment: OracleJudgment<T::EntryId, BalanceOf<T>>
+        ) {
+            let bounty_oracle_manager = BountyActorManager::<T>::ensure_bounty_actor_manager(
+                origin,
+                oracle.clone(),
+            )?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            bounty_oracle_manager.validate_actor(&bounty.creation_params.oracle)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+
+            Self::ensure_bounty_stage(current_bounty_stage, BountyStage::Judgment)?;
+
+            ensure!(bounty.active_work_entry_count != 0, Error::<T>::NoActiveWorkEntries);
+
+            Self::validate_judgment(&bounty, &judgment)?;
+
+            // Lookup for any winners in the judgment.
+            let successful_bounty = Self::judgment_has_winners(&judgment);
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Return a cherry to a creator.
+            if successful_bounty {
+                Self::return_bounty_cherry_to_creator(bounty_id, &bounty)?;
+            }
+
+            // Update bounty record.
+            <Bounties<T>>::mutate(bounty_id, |bounty| {
+                bounty.milestone = BountyMilestone::JudgmentSubmitted {
+                    successful_bounty
+                };
+            });
+
+            // Judgments triage.
+            for (entry_id, work_entry_judgment) in judgment.iter() {
+                // Update work entries for winners.
+                if matches!(*work_entry_judgment, OracleWorkEntryJudgment::Winner{ .. }) {
+                    <Entries<T>>::mutate(entry_id, |entry| {
+                        entry.oracle_judgment_result = Some(*work_entry_judgment);
+                    });
+                } else {
+                    let entry = Self::entries(entry_id);
+
+                    Self::slash_work_entry_stake(&entry);
+
+                    Self::remove_work_entry(&bounty_id, &entry_id);
+
+                    Self::deposit_event(RawEvent::WorkEntrySlashed(bounty_id, *entry_id));
+                }
+            }
+
+            // Fire a judgment event.
+            Self::deposit_event(RawEvent::OracleJudgmentSubmitted(bounty_id, oracle, judgment));
+        }
+
+        /// Withdraw work entrant funds.
+        /// Both legitimate participants and winners get their stake unlocked. Winners also get a
+        /// bounty reward.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (1)`
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight = WeightInfoBounty::<T>::withdraw_work_entrant_funds()]
+        pub fn withdraw_work_entrant_funds(
+            origin,
+            member_id: MemberId<T>,
+            bounty_id: T::BountyId,
+            entry_id: T::EntryId,
+        ) {
+            let controller_account_id =
+                T::Membership::ensure_member_controller_account_origin(origin, member_id)?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+
+            // Ensure withdrawal for successful or failed bounty.
+            ensure!(
+                current_bounty_stage == BountyStage::FailedBountyWithdrawal ||
+                current_bounty_stage == BountyStage::SuccessfulBountyWithdrawal,
+                Self::unexpected_bounty_stage_error(current_bounty_stage)
+            );
+
+            let entry = Self::ensure_work_entry_exists(&entry_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Claim the winner reward.
+            if let Some(OracleWorkEntryJudgment::Winner { reward }) = entry.oracle_judgment_result {
+                Self::transfer_funds_from_bounty_account(
+                    &controller_account_id,
+                    bounty_id,
+                    reward
+                )?;
+            }
+
+            // Unstake the full work entry state.
+            T::StakingHandler::unlock(&entry.staking_account_id);
+
+            // Delete the work entry record from the storage.
+            Self::remove_work_entry(&bounty_id, &entry_id);
+
+            // Fire an event.
+            Self::deposit_event(RawEvent::WorkEntrantFundsWithdrawn(bounty_id, entry_id, member_id));
+
+            // Remove the bounty in case of the last withdrawal operation.
+            if Self::withdrawal_completed(&current_bounty_stage, &bounty_id) {
+                Self::remove_bounty(&bounty_id);
+            }
+        }
+    }
+}
+
+impl<T: Trait> Module<T> {
+    // Wrapper-function over System::block_number()
+    pub(crate) fn current_block() -> T::BlockNumber {
+        <frame_system::Module<T>>::block_number()
+    }
+
+    // Validates parameters for a bounty creation.
+    fn ensure_create_bounty_parameters_valid(
+        params: &BountyCreationParameters<T>,
+    ) -> DispatchResult {
+        ensure!(
+            params.work_period != Zero::zero(),
+            Error::<T>::WorkPeriodCannotBeZero
+        );
+
+        ensure!(
+            params.judging_period != Zero::zero(),
+            Error::<T>::JudgingPeriodCannotBeZero
+        );
+
+        match params.funding_type {
+            FundingType::Perpetual { target } => {
+                ensure!(
+                    target != Zero::zero(),
+                    Error::<T>::FundingAmountCannotBeZero
+                );
+            }
+            FundingType::Limited {
+                min_funding_amount,
+                max_funding_amount,
+                funding_period,
+            } => {
+                ensure!(
+                    min_funding_amount != Zero::zero(),
+                    Error::<T>::FundingAmountCannotBeZero
+                );
+
+                ensure!(
+                    max_funding_amount != Zero::zero(),
+                    Error::<T>::FundingAmountCannotBeZero
+                );
+
+                ensure!(
+                    funding_period != Zero::zero(),
+                    Error::<T>::FundingPeriodCannotBeZero
+                );
+
+                ensure!(
+                    min_funding_amount <= max_funding_amount,
+                    Error::<T>::MinFundingAmountCannotBeGreaterThanMaxAmount
+                );
+            }
+        }
+
+        ensure!(
+            params.cherry >= T::MinCherryLimit::get(),
+            Error::<T>::CherryLessThenMinimumAllowed
+        );
+
+        ensure!(
+            params.entrant_stake >= T::MinWorkEntrantStake::get(),
+            Error::<T>::EntrantStakeIsLessThanMininum
+        );
+
+        if let AssuranceContractType::Closed(ref member_ids) = params.contract_type {
+            ensure!(
+                !member_ids.is_empty(),
+                Error::<T>::ClosedContractMemberListIsEmpty
+            );
+
+            ensure!(
+                member_ids.len() <= T::ClosedContractSizeLimit::get().saturated_into(),
+                Error::<T>::ClosedContractMemberListIsTooLarge
+            );
+        }
+
+        Ok(())
+    }
+
+    // Verifies that member balance is sufficient for a bounty.
+    fn check_balance_for_account(amount: BalanceOf<T>, account_id: &T::AccountId) -> bool {
+        balances::Module::<T>::usable_balance(account_id) >= amount
+    }
+
+    // Transfer funds from the member account to the bounty account.
+    fn transfer_funds_to_bounty_account(
+        account_id: &T::AccountId,
+        bounty_id: T::BountyId,
+        amount: BalanceOf<T>,
+    ) -> DispatchResult {
+        let bounty_account_id = Self::bounty_account_id(bounty_id);
+
+        <balances::Module<T> as Currency<T::AccountId>>::transfer(
+            account_id,
+            &bounty_account_id,
+            amount,
+            ExistenceRequirement::AllowDeath,
+        )
+    }
+
+    // Transfer funds from the bounty account to the member account.
+    fn transfer_funds_from_bounty_account(
+        account_id: &T::AccountId,
+        bounty_id: T::BountyId,
+        amount: BalanceOf<T>,
+    ) -> DispatchResult {
+        let bounty_account_id = Self::bounty_account_id(bounty_id);
+
+        <balances::Module<T> as Currency<T::AccountId>>::transfer(
+            &bounty_account_id,
+            account_id,
+            amount,
+            ExistenceRequirement::AllowDeath,
+        )
+    }
+
+    // Verifies bounty existence and retrieves a bounty from the storage.
+    fn ensure_bounty_exists(bounty_id: &T::BountyId) -> Result<Bounty<T>, DispatchError> {
+        ensure!(
+            <Bounties<T>>::contains_key(bounty_id),
+            Error::<T>::BountyDoesntExist
+        );
+
+        let bounty = <Bounties<T>>::get(bounty_id);
+
+        Ok(bounty)
+    }
+
+    // Calculate cherry fraction to reward member for an unsuccessful bounty.
+    // Cherry fraction = cherry * (member funding / total funding).
+    fn get_cherry_fraction_for_member(
+        bounty: &Bounty<T>,
+        funding_amount: BalanceOf<T>,
+    ) -> BalanceOf<T> {
+        let funding_share =
+            Perbill::from_rational_approximation(funding_amount, bounty.total_funding);
+
+        // cherry share
+        funding_share * bounty.creation_params.cherry
+    }
+
+    // Remove bounty and all related info from the storage.
+    fn remove_bounty(bounty_id: &T::BountyId) {
+        <Bounties<T>>::remove(bounty_id);
+        <BountyContributions<T>>::remove_prefix(bounty_id);
+
+        // Slash remaining funds.
+        let bounty_account_id = Self::bounty_account_id(*bounty_id);
+        let all = balances::Module::<T>::usable_balance(&bounty_account_id);
+        if all != Zero::zero() {
+            let _ = balances::Module::<T>::slash(&bounty_account_id, all);
+        }
+
+        Self::deposit_event(RawEvent::BountyRemoved(*bounty_id));
+    }
+
+    // Verifies that the bounty has no pending fund withdrawals left.
+    fn withdrawal_completed(stage: &BountyStage, bounty_id: &T::BountyId) -> bool {
+        let has_no_contributions = !Self::contributions_exist(bounty_id);
+        let has_no_work_entries = !Self::work_entries_exist(bounty_id);
+
+        match stage {
+            BountyStage::SuccessfulBountyWithdrawal => {
+                // All work entrants withdrew their stakes and rewards.
+                has_no_work_entries
+            }
+            BountyStage::FailedBountyWithdrawal => {
+                // All work entrants withdrew their stakes and all funders withdrew cherry and
+                // provided funds.
+                has_no_contributions && has_no_work_entries
+            }
+            // Not withdrawal stage
+            _ => false,
+        }
+    }
+
+    // Verifies that bounty has some contribution to withdraw.
+    // Should be O(1) because of the single inner call of the next() function of the iterator.
+    pub(crate) fn contributions_exist(bounty_id: &T::BountyId) -> bool {
+        <BountyContributions<T>>::iter_prefix_values(bounty_id)
+            .peekable()
+            .peek()
+            .is_some()
+    }
+
+    // Verifies that bounty has some work entries to withdraw.
+    pub(crate) fn work_entries_exist(bounty_id: &T::BountyId) -> bool {
+        Self::bounties(bounty_id).active_work_entry_count > 0
+    }
+
+    // The account ID of a bounty account. Tests require AccountID type to be at least u128.
+    pub(crate) fn bounty_account_id(bounty_id: T::BountyId) -> T::AccountId {
+        T::ModuleId::get().into_sub_account(bounty_id)
+    }
+
+    // Calculates bounty milestone on member funding.
+    fn get_bounty_milestone_on_funding(
+        maximum_funding_reached: bool,
+        previous_milestone: BountyMilestone<T::BlockNumber>,
+    ) -> BountyMilestone<T::BlockNumber> {
+        let now = Self::current_block();
+
+        if maximum_funding_reached {
+            // Bounty maximum funding reached.
+            BountyMilestone::BountyMaxFundingReached {
+                max_funding_reached_at: now,
+            }
+        // No previous contributions.
+        } else if let BountyMilestone::Created {
+            created_at,
+            has_contributions: false,
+        } = previous_milestone
+        {
+            // The bounty has some contributions now.
+            BountyMilestone::Created {
+                created_at,
+                has_contributions: true,
+            }
+        } else {
+            // No changes.
+            previous_milestone
+        }
+    }
+
+    // Calculates bounty milestone on work submitting.
+    fn get_bounty_milestone_on_work_submitting(
+        bounty: &Bounty<T>,
+    ) -> BountyMilestone<T::BlockNumber> {
+        let previous_milestone = bounty.milestone.clone();
+
+        match bounty.milestone.clone() {
+            BountyMilestone::Created { created_at, .. } => {
+                match bounty.creation_params.funding_type {
+                    FundingType::Perpetual { .. } => previous_milestone,
+                    FundingType::Limited { funding_period, .. } => BountyMilestone::WorkSubmitted {
+                        work_period_started_at: created_at + funding_period,
+                    },
+                }
+            }
+            BountyMilestone::BountyMaxFundingReached {
+                max_funding_reached_at,
+            } => BountyMilestone::WorkSubmitted {
+                work_period_started_at: max_funding_reached_at,
+            },
+            _ => previous_milestone,
+        }
+    }
+
+    // Validates stake on announcing the work entry.
+    fn validate_entrant_stake(
+        member_id: MemberId<T>,
+        bounty: &Bounty<T>,
+        staking_account_id: T::AccountId,
+    ) -> Result<Option<RequiredStakeInfo<T>>, DispatchError> {
+        let staking_balance = bounty.creation_params.entrant_stake;
+
+        ensure!(
+            T::Membership::is_member_staking_account(&member_id, &staking_account_id),
+            Error::<T>::InvalidStakingAccountForMember
+        );
+
+        ensure!(
+            T::StakingHandler::is_account_free_of_conflicting_stakes(&staking_account_id),
+            Error::<T>::ConflictingStakes
+        );
+
+        ensure!(
+            T::StakingHandler::is_enough_balance_for_stake(&staking_account_id, staking_balance),
+            Error::<T>::InsufficientBalanceForStake
+        );
+
+        Ok(Some(RequiredStakeInfo {
+            amount: staking_balance,
+            account_id: staking_account_id,
+        }))
+    }
+
+    // Verifies work entry existence and retrieves an entry from the storage.
+    fn ensure_work_entry_exists(entry_id: &T::EntryId) -> Result<Entry<T>, DispatchError> {
+        ensure!(
+            <Entries<T>>::contains_key(entry_id),
+            Error::<T>::WorkEntryDoesntExist
+        );
+
+        let entry = Self::entries(entry_id);
+
+        Ok(entry)
+    }
+
+    // Unlocks the work entry stake.
+    // It also calculates and slashes the stake on work entry withdrawal.
+    // The slashing amount depends on the entry active period.
+    fn unlock_work_entry_stake_with_possible_penalty(bounty: &Bounty<T>, entry: &Entry<T>) {
+        let staking_account_id = &entry.staking_account_id;
+
+        let now = Self::current_block();
+        let staking_balance = bounty.creation_params.entrant_stake;
+
+        let entry_was_active_period = now.saturating_sub(entry.submitted_at);
+
+        let slashing_share = Perbill::from_rational_approximation(
+            entry_was_active_period,
+            bounty.creation_params.work_period,
+        );
+
+        // No more than staking_balance.
+        let slashing_amount = (slashing_share * staking_balance).min(staking_balance);
+
+        if slashing_amount > Zero::zero() {
+            T::StakingHandler::slash(staking_account_id, Some(slashing_amount));
+        }
+
+        T::StakingHandler::unlock(staking_account_id);
+    }
+
+    // Slashed the work entry stake.
+    fn slash_work_entry_stake(entry: &Entry<T>) {
+        T::StakingHandler::slash(&entry.staking_account_id, None);
+    }
+
+    // Validates the contract type for a bounty
+    fn ensure_valid_contract_type(bounty: &Bounty<T>, member_id: &MemberId<T>) -> DispatchResult {
+        if let AssuranceContractType::Closed(ref valid_members) =
+            bounty.creation_params.contract_type
+        {
+            ensure!(
+                valid_members.contains(member_id),
+                Error::<T>::CannotSubmitWorkToClosedContractBounty
+            );
+        }
+
+        Ok(())
+    }
+
+    // Computes the stage of a bounty based on its creation parameters and the current state.
+    pub(crate) fn get_bounty_stage(bounty: &Bounty<T>) -> BountyStage {
+        let sc = BountyStageCalculator::<T> {
+            now: Self::current_block(),
+            bounty,
+        };
+
+        sc.get_bounty_stage()
+    }
+
+    // Validates oracle judgment.
+    fn validate_judgment(bounty: &Bounty<T>, judgment: &OracleJudgmentOf<T>) -> DispatchResult {
+        // Total judgment reward accumulator.
+        let mut reward_sum_from_judgment: BalanceOf<T> = Zero::zero();
+
+        // Validate all work entry judgements.
+        for (entry_id, work_entry_judgment) in judgment.iter() {
+            if let OracleWorkEntryJudgment::Winner { reward } = work_entry_judgment {
+                // Check for zero reward.
+                ensure!(*reward != Zero::zero(), Error::<T>::ZeroWinnerReward);
+
+                reward_sum_from_judgment += *reward;
+            }
+
+            // Check winner work submission.
+            let entry = Self::ensure_work_entry_exists(entry_id)?;
+            ensure!(
+                entry.work_submitted,
+                Error::<T>::WinnerShouldHasWorkSubmission
+            );
+        }
+
+        // Check for invalid total sum for successful bounty.
+        if reward_sum_from_judgment != Zero::zero() {
+            ensure!(
+                reward_sum_from_judgment == bounty.total_funding, // 100% bounty distribution
+                Error::<T>::TotalRewardShouldBeEqualToTotalFunding
+            );
+        }
+
+        Ok(())
+    }
+
+    // Removes the work entry and decrements active entry count in a bounty.
+    fn remove_work_entry(bounty_id: &T::BountyId, entry_id: &T::EntryId) {
+        <Entries<T>>::remove(entry_id);
+
+        // Decrement work entry counter and update bounty record.
+        <Bounties<T>>::mutate(bounty_id, |bounty| {
+            bounty.decrement_active_work_entry_counter();
+        });
+    }
+
+    // Calculates weight for submit_oracle_judgement extrinsic.
+    fn submit_oracle_judgement_weight(judgement: &OracleJudgmentOf<T>) -> Weight {
+        let collection_length: u32 = judgement.len().saturated_into();
+
+        WeightInfoBounty::<T>::submit_oracle_judgment_by_council_all_winners(collection_length)
+            .max(
+                WeightInfoBounty::<T>::submit_oracle_judgment_by_council_all_rejected(
+                    collection_length,
+                ),
+            )
+            .max(
+                WeightInfoBounty::<T>::submit_oracle_judgment_by_member_all_winners(
+                    collection_length,
+                ),
+            )
+            .max(
+                WeightInfoBounty::<T>::submit_oracle_judgment_by_member_all_rejected(
+                    collection_length,
+                ),
+            )
+    }
+
+    // Bounty stage validator.
+    fn ensure_bounty_stage(
+        actual_stage: BountyStage,
+        expected_stage: BountyStage,
+    ) -> DispatchResult {
+        ensure!(
+            actual_stage == expected_stage,
+            Self::unexpected_bounty_stage_error(actual_stage)
+        );
+
+        Ok(())
+    }
+
+    // Bounty stage validator for cancel_bounty() extrinsic.
+    fn ensure_bounty_stage_for_canceling(actual_stage: BountyStage) -> DispatchResult {
+        let funding_stage_with_no_contributions = BountyStage::Funding {
+            has_contributions: false,
+        };
+
+        ensure!(
+            actual_stage == funding_stage_with_no_contributions
+                || actual_stage == BountyStage::FundingExpired,
+            Self::unexpected_bounty_stage_error(actual_stage)
+        );
+
+        Ok(())
+    }
+
+    // Provides fined-grained errors for a bounty stages
+    fn unexpected_bounty_stage_error(unexpected_stage: BountyStage) -> DispatchError {
+        match unexpected_stage {
+            BountyStage::Funding { .. } => Error::<T>::InvalidStageUnexpectedFunding.into(),
+            BountyStage::FundingExpired => Error::<T>::InvalidStageUnexpectedFundingExpired.into(),
+            BountyStage::WorkSubmission => Error::<T>::InvalidStageUnexpectedWorkSubmission.into(),
+            BountyStage::Judgment => Error::<T>::InvalidStageUnexpectedJudgment.into(),
+            BountyStage::SuccessfulBountyWithdrawal => {
+                Error::<T>::InvalidStageUnexpectedSuccessfulBountyWithdrawal.into()
+            }
+            BountyStage::FailedBountyWithdrawal => {
+                Error::<T>::InvalidStageUnexpectedFailedBountyWithdrawal.into()
+            }
+        }
+    }
+
+    // Oracle judgment helper. Returns true if a judgement contains at least one winner.
+    pub(crate) fn judgment_has_winners(judgment: &OracleJudgmentOf<T>) -> bool {
+        judgment.iter().any(|(_, j)| j.is_winner())
+    }
+
+    // Transfers cherry back to the bounty creator and fires an event.
+    fn return_bounty_cherry_to_creator(
+        bounty_id: T::BountyId,
+        bounty: &Bounty<T>,
+    ) -> DispatchResult {
+        let bounty_creator_manager = BountyActorManager::<T>::get_bounty_actor_manager(
+            bounty.creation_params.creator.clone(),
+        )?;
+
+        bounty_creator_manager
+            .transfer_funds_from_bounty_account(bounty_id, bounty.creation_params.cherry)?;
+
+        Self::deposit_event(RawEvent::BountyCreatorCherryWithdrawal(
+            bounty_id,
+            bounty.creation_params.creator.clone(),
+        ));
+
+        Ok(())
+    }
+
+    // Calculates weight for create_bounty extrinsic.
+    fn create_bounty_weight(params: &BountyCreationParameters<T>, metadata: &[u8]) -> Weight {
+        let metadata_length = metadata.len().saturated_into();
+        let member_list_length =
+            if let AssuranceContractType::Closed(ref members) = params.contract_type {
+                members.len().saturated_into()
+            } else {
+                1 // consider open contract member list as one.
+            };
+
+        WeightInfoBounty::<T>::create_bounty_by_member(metadata_length, member_list_length).max(
+            WeightInfoBounty::<T>::create_bounty_by_council(metadata_length, member_list_length),
+        )
+    }
+}

+ 182 - 0
runtime-modules/bounty/src/stages.rs

@@ -0,0 +1,182 @@
+//! This module contains the BountyStageCalculator - a bounty stage calculation helper.
+//! It allows to get a bounty stage based on the current bounty state and the current system block.
+
+use crate::{Bounty, BountyMilestone, BountyStage, FundingType, Trait};
+
+// Bounty stage helper.
+pub(crate) struct BountyStageCalculator<'a, T: Trait> {
+    // current block number
+    pub now: T::BlockNumber,
+    // bounty object
+    pub bounty: &'a Bounty<T>,
+}
+
+impl<'a, T: Trait> BountyStageCalculator<'a, T> {
+    // Calculates the current bounty stage.
+    pub(crate) fn get_bounty_stage(&self) -> BountyStage {
+        self.is_funding_stage()
+            .or_else(|| self.is_funding_expired_stage())
+            .or_else(|| self.is_work_submission_stage())
+            .or_else(|| self.is_judgment_stage())
+            .or_else(|| self.is_successful_bounty_withdrawal_stage())
+            .unwrap_or(BountyStage::FailedBountyWithdrawal)
+    }
+
+    // Calculates funding stage of the bounty.
+    // Returns None if conditions are not met.
+    fn is_funding_stage(&self) -> Option<BountyStage> {
+        // Bounty was created. There can be some contributions. Funding period is not over.
+        if let BountyMilestone::Created {
+            has_contributions,
+            created_at,
+        } = self.bounty.milestone.clone()
+        {
+            let funding_period_is_not_expired = !self.funding_period_expired(created_at);
+
+            if funding_period_is_not_expired {
+                return Some(BountyStage::Funding { has_contributions });
+            }
+        }
+
+        None
+    }
+
+    // Calculates 'funding expired' stage of the bounty.
+    // Returns None if conditions are not met.
+    fn is_funding_expired_stage(&self) -> Option<BountyStage> {
+        // Bounty was created. There can be some contributions. Funding period is not over.
+        if let BountyMilestone::Created {
+            has_contributions,
+            created_at,
+        } = self.bounty.milestone.clone()
+        {
+            let funding_period_is_expired = self.funding_period_expired(created_at);
+
+            if funding_period_is_expired && !has_contributions {
+                return Some(BountyStage::FundingExpired);
+            }
+        }
+
+        None
+    }
+
+    // Calculates work submission stage of the bounty.
+    // Returns None if conditions are not met.
+    fn is_work_submission_stage(&self) -> Option<BountyStage> {
+        match self.bounty.milestone.clone() {
+            // Funding period is over. Minimum funding reached. Work period is not expired.
+            BountyMilestone::Created { created_at, .. } => {
+                match self.bounty.creation_params.funding_type {
+                    // Perpetual funding is not reached its target yet.
+                    FundingType::Perpetual { .. } => return None,
+                    FundingType::Limited { funding_period, .. } => {
+                        let minimum_funding_reached = self.minimum_funding_reached();
+                        let funding_period_expired = self.funding_period_expired(created_at);
+                        let working_period_is_not_expired =
+                            !self.work_period_expired(created_at + funding_period);
+
+                        if minimum_funding_reached
+                            && funding_period_expired
+                            && working_period_is_not_expired
+                        {
+                            return Some(BountyStage::WorkSubmission);
+                        }
+                    }
+                }
+            }
+            // Maximum funding reached. Work period is not expired.
+            BountyMilestone::BountyMaxFundingReached {
+                max_funding_reached_at,
+            } => {
+                let work_period_is_not_expired = !self.work_period_expired(max_funding_reached_at);
+
+                if work_period_is_not_expired {
+                    return Some(BountyStage::WorkSubmission);
+                }
+            }
+            // Work in progress. Work period is not expired.
+            BountyMilestone::WorkSubmitted {
+                work_period_started_at,
+            } => {
+                let work_period_is_not_expired = !self.work_period_expired(work_period_started_at);
+
+                if work_period_is_not_expired {
+                    return Some(BountyStage::WorkSubmission);
+                }
+            }
+            _ => return None,
+        }
+
+        None
+    }
+
+    // Calculates judgment stage of the bounty.
+    // Returns None if conditions are not met.
+    fn is_judgment_stage(&self) -> Option<BountyStage> {
+        // Can be judged only if there are work submissions.
+        if let BountyMilestone::WorkSubmitted {
+            work_period_started_at,
+        } = self.bounty.milestone
+        {
+            let work_period_expired = self.work_period_expired(work_period_started_at);
+
+            let judgment_period_is_not_expired =
+                !self.judgment_period_expired(work_period_started_at);
+
+            if work_period_expired && judgment_period_is_not_expired {
+                return Some(BountyStage::Judgment);
+            }
+        }
+
+        None
+    }
+
+    // Calculates withdrawal stage for the successful bounty.
+    // Returns None if conditions are not met.
+    fn is_successful_bounty_withdrawal_stage(&self) -> Option<BountyStage> {
+        // The bounty judgment was submitted and the bounty is successful (there are some winners).
+        if let BountyMilestone::JudgmentSubmitted { successful_bounty } =
+            self.bounty.milestone.clone()
+        {
+            if successful_bounty {
+                return Some(BountyStage::SuccessfulBountyWithdrawal);
+            }
+        }
+
+        None
+    }
+
+    // Checks whether the minimum funding reached for the bounty.
+    fn minimum_funding_reached(&self) -> bool {
+        match self.bounty.creation_params.funding_type {
+            // There is no minimum for the perpetual funding type - only maximum (target).
+            FundingType::Perpetual { .. } => false,
+            FundingType::Limited {
+                min_funding_amount, ..
+            } => self.bounty.total_funding >= min_funding_amount,
+        }
+    }
+
+    // Checks whether the work period expired by now starting from the provided block number.
+    fn work_period_expired(&self, work_period_started_at: T::BlockNumber) -> bool {
+        work_period_started_at + self.bounty.creation_params.work_period < self.now
+    }
+
+    // Checks whether the funding period expired by now starting from the provided block number.
+    fn funding_period_expired(&self, created_at: T::BlockNumber) -> bool {
+        match self.bounty.creation_params.funding_type {
+            // Never expires
+            FundingType::Perpetual { .. } => false,
+            FundingType::Limited { funding_period, .. } => created_at + funding_period < self.now,
+        }
+    }
+
+    // Checks whether the judgment period expired by now when work period start from the provided
+    // block number.
+    fn judgment_period_expired(&self, work_period_started_at: T::BlockNumber) -> bool {
+        work_period_started_at
+            + self.bounty.creation_params.work_period
+            + self.bounty.creation_params.judging_period
+            < self.now
+    }
+}

+ 816 - 0
runtime-modules/bounty/src/tests/fixtures.rs

@@ -0,0 +1,816 @@
+use frame_support::dispatch::DispatchResult;
+use frame_support::storage::StorageMap;
+use frame_support::traits::{Currency, OnFinalize, OnInitialize};
+use frame_system::{EventRecord, Phase, RawOrigin};
+use sp_runtime::offchain::storage_lock::BlockNumberProvider;
+use sp_std::collections::btree_set::BTreeSet;
+use sp_std::iter::FromIterator;
+
+use super::mocks::{Balances, Bounty, System, Test, TestEvent};
+use crate::{
+    AssuranceContractType, BountyActor, BountyCreationParameters, BountyMilestone, BountyRecord,
+    Entry, FundingType, OracleJudgmentOf, RawEvent,
+};
+use common::council::CouncilBudgetManager;
+
+// Recommendation from Parity on testing on_finalize
+// https://substrate.dev/docs/en/next/development/module/tests
+pub fn run_to_block(n: u64) {
+    while System::block_number() < n {
+        <System as OnFinalize<u64>>::on_finalize(System::block_number());
+        <Bounty as OnFinalize<u64>>::on_finalize(System::block_number());
+        System::set_block_number(System::block_number() + 1);
+        <System as OnInitialize<u64>>::on_initialize(System::block_number());
+        <Bounty as OnInitialize<u64>>::on_initialize(System::block_number());
+    }
+}
+
+pub fn set_council_budget(new_budget: u64) {
+    <super::mocks::CouncilBudgetManager as CouncilBudgetManager<u64>>::set_budget(new_budget);
+}
+
+pub fn get_council_budget() -> u64 {
+    <super::mocks::CouncilBudgetManager as CouncilBudgetManager<u64>>::get_budget()
+}
+
+pub fn increase_total_balance_issuance_using_account_id(account_id: u128, balance: u64) {
+    let initial_balance = Balances::total_issuance();
+    {
+        let _ = Balances::deposit_creating(&account_id, balance);
+    }
+    assert_eq!(Balances::total_issuance(), initial_balance + balance);
+}
+
+pub fn increase_account_balance(account_id: &u128, balance: u64) {
+    let _ = Balances::deposit_creating(&account_id, balance);
+}
+
+pub struct EventFixture;
+impl EventFixture {
+    pub fn assert_last_crate_event(
+        expected_raw_event: RawEvent<
+            u64,
+            u64,
+            u64,
+            u64,
+            u128,
+            BountyCreationParameters<Test>,
+            OracleJudgmentOf<Test>,
+        >,
+    ) {
+        let converted_event = TestEvent::bounty(expected_raw_event);
+
+        Self::assert_last_global_event(converted_event)
+    }
+
+    pub fn contains_crate_event(
+        expected_raw_event: RawEvent<
+            u64,
+            u64,
+            u64,
+            u64,
+            u128,
+            BountyCreationParameters<Test>,
+            OracleJudgmentOf<Test>,
+        >,
+    ) {
+        let converted_event = TestEvent::bounty(expected_raw_event);
+
+        Self::contains_global_event(converted_event)
+    }
+
+    pub fn assert_last_global_event(expected_event: TestEvent) {
+        let expected_event = EventRecord {
+            phase: Phase::Initialization,
+            event: expected_event,
+            topics: vec![],
+        };
+
+        assert_eq!(System::events().pop().unwrap(), expected_event);
+    }
+
+    fn contains_global_event(expected_event: TestEvent) {
+        let expected_event = EventRecord {
+            phase: Phase::Initialization,
+            event: expected_event,
+            topics: vec![],
+        };
+
+        assert!(System::events().iter().any(|ev| *ev == expected_event));
+    }
+}
+
+pub const DEFAULT_BOUNTY_CHERRY: u64 = 10;
+pub const DEFAULT_BOUNTY_ENTRANT_STAKE: u64 = 10;
+pub const DEFAULT_BOUNTY_MAX_AMOUNT: u64 = 1000;
+pub const DEFAULT_BOUNTY_MIN_AMOUNT: u64 = 1;
+pub const DEFAULT_BOUNTY_FUNDING_PERIOD: u64 = 1;
+
+pub struct CreateBountyFixture {
+    origin: RawOrigin<u128>,
+    metadata: Vec<u8>,
+    creator: BountyActor<u64>,
+    funding_type: FundingType<u64, u64>,
+    work_period: u64,
+    judging_period: u64,
+    cherry: u64,
+    expected_milestone: Option<BountyMilestone<u64>>,
+    entrant_stake: u64,
+    contract_type: AssuranceContractType<u64>,
+    oracle: BountyActor<u64>,
+}
+
+impl CreateBountyFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Root,
+            metadata: Vec::new(),
+            creator: BountyActor::Council,
+            funding_type: FundingType::Perpetual {
+                target: DEFAULT_BOUNTY_MAX_AMOUNT,
+            },
+            work_period: 1,
+            judging_period: 1,
+            cherry: DEFAULT_BOUNTY_CHERRY,
+            expected_milestone: None,
+            entrant_stake: DEFAULT_BOUNTY_ENTRANT_STAKE,
+            contract_type: AssuranceContractType::Open,
+            oracle: BountyActor::Council,
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_creator_member_id(self, member_id: u64) -> Self {
+        Self {
+            creator: BountyActor::Member(member_id),
+            ..self
+        }
+    }
+
+    pub fn with_oracle_member_id(self, member_id: u64) -> Self {
+        Self {
+            oracle: BountyActor::Member(member_id),
+            ..self
+        }
+    }
+
+    pub fn with_metadata(self, metadata: Vec<u8>) -> Self {
+        Self { metadata, ..self }
+    }
+
+    pub fn with_work_period(self, work_period: u64) -> Self {
+        Self {
+            work_period,
+            ..self
+        }
+    }
+
+    pub fn with_limited_funding(
+        self,
+        min_funding_amount: u64,
+        max_funding_amount: u64,
+        funding_period: u64,
+    ) -> Self {
+        Self {
+            funding_type: FundingType::Limited {
+                funding_period,
+                min_funding_amount,
+                max_funding_amount,
+            },
+            ..self
+        }
+    }
+
+    pub fn with_perpetual_funding(self, target: u64) -> Self {
+        Self {
+            funding_type: FundingType::Perpetual { target },
+            ..self
+        }
+    }
+
+    pub fn with_funding_period(self, funding_period: u64) -> Self {
+        Self {
+            funding_type: FundingType::Limited {
+                funding_period,
+                min_funding_amount: DEFAULT_BOUNTY_MIN_AMOUNT,
+                max_funding_amount: DEFAULT_BOUNTY_MAX_AMOUNT,
+            },
+            ..self
+        }
+    }
+
+    pub fn with_max_funding_amount(self, max_funding_amount: u64) -> Self {
+        Self {
+            funding_type: FundingType::Limited {
+                funding_period: DEFAULT_BOUNTY_FUNDING_PERIOD,
+                min_funding_amount: DEFAULT_BOUNTY_MIN_AMOUNT,
+                max_funding_amount,
+            },
+            ..self
+        }
+    }
+
+    pub fn with_judging_period(self, judging_period: u64) -> Self {
+        Self {
+            judging_period,
+            ..self
+        }
+    }
+
+    pub fn with_cherry(self, cherry: u64) -> Self {
+        Self { cherry, ..self }
+    }
+
+    pub fn with_entrant_stake(self, entrant_stake: u64) -> Self {
+        Self {
+            entrant_stake,
+            ..self
+        }
+    }
+
+    pub fn with_closed_contract(self, member_ids: Vec<u64>) -> Self {
+        let member_id_set = BTreeSet::from_iter(member_ids.into_iter());
+
+        Self {
+            contract_type: AssuranceContractType::Closed(member_id_set),
+            ..self
+        }
+    }
+
+    pub fn get_bounty_creation_parameters(&self) -> BountyCreationParameters<Test> {
+        BountyCreationParameters::<Test> {
+            creator: self.creator.clone(),
+            funding_type: self.funding_type.clone(),
+            work_period: self.work_period.clone(),
+            judging_period: self.judging_period.clone(),
+            cherry: self.cherry,
+            entrant_stake: self.entrant_stake,
+            contract_type: self.contract_type.clone(),
+            oracle: self.oracle.clone(),
+            ..Default::default()
+        }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let params = self.get_bounty_creation_parameters();
+
+        let next_bounty_count_value = Bounty::bounty_count() + 1;
+        let bounty_id: u64 = next_bounty_count_value.into();
+
+        let actual_result = Bounty::create_bounty(
+            self.origin.clone().into(),
+            params.clone(),
+            self.metadata.clone(),
+        );
+
+        assert_eq!(actual_result, expected_result);
+
+        if actual_result.is_ok() {
+            assert_eq!(next_bounty_count_value, Bounty::bounty_count());
+            assert!(<crate::Bounties<Test>>::contains_key(&bounty_id));
+
+            let expected_milestone = if let Some(milestone) = self.expected_milestone.clone() {
+                milestone
+            } else {
+                BountyMilestone::Created {
+                    created_at: System::block_number(),
+                    has_contributions: false,
+                }
+            };
+
+            let expected_bounty = BountyRecord::<u64, u64, u64> {
+                creation_params: params.clone(),
+                total_funding: 0,
+                milestone: expected_milestone,
+                active_work_entry_count: 0,
+            };
+
+            assert_eq!(expected_bounty, Bounty::bounties(bounty_id));
+        } else {
+            assert_eq!(next_bounty_count_value - 1, Bounty::bounty_count());
+            assert!(!<crate::Bounties<Test>>::contains_key(&bounty_id));
+        }
+    }
+}
+
+pub struct CancelBountyFixture {
+    origin: RawOrigin<u128>,
+    creator: BountyActor<u64>,
+    bounty_id: u64,
+}
+
+impl CancelBountyFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Root,
+            creator: BountyActor::Council,
+            bounty_id: 1,
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_creator_member_id(self, member_id: u64) -> Self {
+        Self {
+            creator: BountyActor::Member(member_id),
+            ..self
+        }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result = Bounty::cancel_bounty(
+            self.origin.clone().into(),
+            self.creator.clone(),
+            self.bounty_id.clone(),
+        );
+
+        assert_eq!(actual_result, expected_result);
+
+        if actual_result.is_ok() {
+            assert!(!<crate::Bounties<Test>>::contains_key(&self.bounty_id));
+        }
+    }
+}
+
+pub struct VetoBountyFixture {
+    origin: RawOrigin<u128>,
+    bounty_id: u64,
+}
+
+impl VetoBountyFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Root,
+            bounty_id: 1,
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result = Bounty::veto_bounty(self.origin.clone().into(), self.bounty_id.clone());
+
+        assert_eq!(actual_result, expected_result);
+
+        if actual_result.is_ok() {
+            assert!(!<crate::Bounties<Test>>::contains_key(&self.bounty_id));
+        }
+    }
+}
+
+pub struct FundBountyFixture {
+    origin: RawOrigin<u128>,
+    funder: BountyActor<u64>,
+    bounty_id: u64,
+    amount: u64,
+}
+
+impl FundBountyFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(1),
+            funder: BountyActor::Member(1),
+            bounty_id: 1,
+            amount: 100,
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_member_id(self, member_id: u64) -> Self {
+        Self {
+            funder: BountyActor::Member(member_id),
+            ..self
+        }
+    }
+
+    pub fn with_council(self) -> Self {
+        Self {
+            funder: BountyActor::Council,
+            ..self
+        }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn with_amount(self, amount: u64) -> Self {
+        Self { amount, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let old_bounty_funding =
+            Bounty::contribution_by_bounty_by_actor(self.bounty_id, &self.funder);
+
+        let old_bounty = Bounty::bounties(self.bounty_id);
+
+        let actual_result = Bounty::fund_bounty(
+            self.origin.clone().into(),
+            self.funder.clone(),
+            self.bounty_id.clone(),
+            self.amount.clone(),
+        );
+
+        assert_eq!(actual_result, expected_result);
+
+        let new_bounty_funding =
+            Bounty::contribution_by_bounty_by_actor(self.bounty_id, &self.funder);
+        if actual_result.is_ok() {
+            let new_bounty = Bounty::bounties(self.bounty_id);
+            if new_bounty.total_funding == new_bounty.maximum_funding() {
+                assert_eq!(
+                    new_bounty_funding,
+                    old_bounty_funding + old_bounty.maximum_funding() - old_bounty.total_funding
+                );
+            } else {
+                assert_eq!(new_bounty_funding, old_bounty_funding + self.amount);
+            }
+        } else {
+            assert_eq!(new_bounty_funding, old_bounty_funding);
+        }
+    }
+}
+
+pub struct WithdrawFundingFixture {
+    origin: RawOrigin<u128>,
+    funder: BountyActor<u64>,
+    bounty_id: u64,
+}
+
+impl WithdrawFundingFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(1),
+            funder: BountyActor::Member(1),
+            bounty_id: 1,
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_member_id(self, member_id: u64) -> Self {
+        Self {
+            funder: BountyActor::Member(member_id),
+            ..self
+        }
+    }
+
+    pub fn with_council(self) -> Self {
+        Self {
+            funder: BountyActor::Council,
+            ..self
+        }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result = Bounty::withdraw_funding(
+            self.origin.clone().into(),
+            self.funder.clone(),
+            self.bounty_id.clone(),
+        );
+
+        assert_eq!(actual_result, expected_result);
+    }
+}
+
+pub struct AnnounceWorkEntryFixture {
+    origin: RawOrigin<u128>,
+    bounty_id: u64,
+    member_id: u64,
+    staking_account_id: u128,
+}
+
+impl AnnounceWorkEntryFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(1),
+            bounty_id: 1,
+            member_id: 1,
+            staking_account_id: 1,
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_member_id(self, member_id: u64) -> Self {
+        Self { member_id, ..self }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn with_staking_account_id(self, staking_account_id: u128) -> Self {
+        Self {
+            staking_account_id,
+            ..self
+        }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let old_bounty = Bounty::bounties(self.bounty_id);
+        let next_entry_count_value = Bounty::entry_count() + 1;
+        let entry_id: u64 = next_entry_count_value.into();
+
+        let actual_result = Bounty::announce_work_entry(
+            self.origin.clone().into(),
+            self.member_id,
+            self.bounty_id,
+            self.staking_account_id,
+        );
+
+        assert_eq!(actual_result, expected_result);
+
+        let new_bounty = Bounty::bounties(self.bounty_id);
+        if actual_result.is_ok() {
+            assert_eq!(next_entry_count_value, Bounty::entry_count());
+            assert!(<crate::Entries<Test>>::contains_key(&entry_id));
+
+            let expected_entry = Entry::<Test> {
+                member_id: self.member_id,
+                staking_account_id: self.staking_account_id,
+                submitted_at: System::current_block_number(),
+                work_submitted: false,
+                oracle_judgment_result: None,
+            };
+
+            assert_eq!(expected_entry, Bounty::entries(entry_id));
+
+            assert_eq!(
+                new_bounty.active_work_entry_count,
+                old_bounty.active_work_entry_count + 1
+            );
+        } else {
+            assert_eq!(next_entry_count_value - 1, Bounty::entry_count());
+            assert!(!<crate::Entries<Test>>::contains_key(&entry_id));
+
+            assert_eq!(
+                new_bounty.active_work_entry_count,
+                old_bounty.active_work_entry_count
+            );
+        }
+    }
+}
+
+pub struct WithdrawWorkEntryFixture {
+    origin: RawOrigin<u128>,
+    entry_id: u64,
+    bounty_id: u64,
+    member_id: u64,
+}
+
+impl WithdrawWorkEntryFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(1),
+            entry_id: 1,
+            bounty_id: 1,
+            member_id: 1,
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_member_id(self, member_id: u64) -> Self {
+        Self { member_id, ..self }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn with_entry_id(self, entry_id: u64) -> Self {
+        Self { entry_id, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let old_bounty = Bounty::bounties(self.bounty_id);
+        let actual_result = Bounty::withdraw_work_entry(
+            self.origin.clone().into(),
+            self.member_id,
+            self.bounty_id,
+            self.entry_id,
+        );
+
+        assert_eq!(actual_result, expected_result);
+
+        if actual_result.is_ok() {
+            assert!(!<crate::Entries<Test>>::contains_key(&self.entry_id));
+
+            let new_bounty = Bounty::bounties(self.bounty_id);
+            assert_eq!(
+                new_bounty.active_work_entry_count,
+                old_bounty.active_work_entry_count - 1
+            );
+        }
+    }
+}
+
+pub struct SubmitWorkFixture {
+    origin: RawOrigin<u128>,
+    entry_id: u64,
+    bounty_id: u64,
+    member_id: u64,
+    work_data: Vec<u8>,
+}
+
+impl SubmitWorkFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(1),
+            entry_id: 1,
+            bounty_id: 1,
+            member_id: 1,
+            work_data: Vec::new(),
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_member_id(self, member_id: u64) -> Self {
+        Self { member_id, ..self }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn with_entry_id(self, entry_id: u64) -> Self {
+        Self { entry_id, ..self }
+    }
+
+    pub fn with_work_data(self, work_data: Vec<u8>) -> Self {
+        Self { work_data, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let old_entry = Bounty::entries(self.entry_id);
+        let actual_result = Bounty::submit_work(
+            self.origin.clone().into(),
+            self.member_id,
+            self.bounty_id,
+            self.entry_id,
+            self.work_data.clone(),
+        );
+
+        assert_eq!(actual_result, expected_result);
+
+        let new_entry = Bounty::entries(self.entry_id);
+
+        if actual_result.is_ok() {
+            assert!(new_entry.work_submitted);
+        } else {
+            assert_eq!(new_entry, old_entry);
+        }
+    }
+}
+
+pub struct SubmitJudgmentFixture {
+    origin: RawOrigin<u128>,
+    bounty_id: u64,
+    oracle: BountyActor<u64>,
+    judgment: OracleJudgmentOf<Test>,
+}
+
+impl SubmitJudgmentFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Root,
+            bounty_id: 1,
+            oracle: BountyActor::Council,
+            judgment: Default::default(),
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn with_oracle_member_id(self, member_id: u64) -> Self {
+        Self {
+            oracle: BountyActor::Member(member_id),
+            ..self
+        }
+    }
+
+    pub fn with_judgment(self, judgment: OracleJudgmentOf<Test>) -> Self {
+        Self { judgment, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let old_bounty = Bounty::bounties(self.bounty_id);
+        let actual_result = Bounty::submit_oracle_judgment(
+            self.origin.clone().into(),
+            self.oracle.clone(),
+            self.bounty_id,
+            self.judgment.clone(),
+        );
+
+        assert_eq!(actual_result, expected_result);
+
+        let new_bounty = Bounty::bounties(self.bounty_id);
+
+        if actual_result.is_ok() {
+            assert_eq!(
+                new_bounty.milestone,
+                BountyMilestone::JudgmentSubmitted {
+                    successful_bounty: Bounty::judgment_has_winners(&self.judgment)
+                }
+            );
+        } else {
+            assert_eq!(new_bounty, old_bounty);
+        }
+    }
+}
+
+pub struct WithdrawWorkEntrantFundsFixture {
+    origin: RawOrigin<u128>,
+    entry_id: u64,
+    bounty_id: u64,
+    member_id: u64,
+}
+
+impl WithdrawWorkEntrantFundsFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(1),
+            entry_id: 1,
+            bounty_id: 1,
+            member_id: 1,
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u128>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_member_id(self, member_id: u64) -> Self {
+        Self { member_id, ..self }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn with_entry_id(self, entry_id: u64) -> Self {
+        Self { entry_id, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let old_bounty = Bounty::bounties(self.bounty_id);
+        let actual_result = Bounty::withdraw_work_entrant_funds(
+            self.origin.clone().into(),
+            self.member_id,
+            self.bounty_id,
+            self.entry_id,
+        );
+
+        assert_eq!(actual_result, expected_result);
+
+        if actual_result.is_ok() {
+            assert!(!<crate::Entries<Test>>::contains_key(&self.entry_id));
+
+            if <crate::Bounties<Test>>::contains_key(self.bounty_id) {
+                let new_bounty = Bounty::bounties(self.bounty_id);
+                assert_eq!(
+                    new_bounty.active_work_entry_count,
+                    old_bounty.active_work_entry_count - 1
+                );
+            }
+        }
+    }
+}

+ 559 - 0
runtime-modules/bounty/src/tests/mocks.rs

@@ -0,0 +1,559 @@
+#![cfg(test)]
+
+use frame_support::dispatch::{DispatchError, DispatchResult};
+use frame_support::traits::{Currency, LockIdentifier};
+use frame_support::weights::Weight;
+use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
+use frame_system::{EnsureOneOf, EnsureRoot, EnsureSigned};
+use sp_core::H256;
+use sp_runtime::{
+    testing::Header,
+    traits::{BlakeTwo256, IdentityLookup},
+    ModuleId, Perbill,
+};
+
+use crate::{Module, Trait};
+use staking_handler::{LockComparator, StakingManager};
+
+impl_outer_origin! {
+    pub enum Origin for Test {}
+}
+
+mod bounty {
+    pub use crate::Event;
+}
+
+mod membership_mod {
+    pub use membership::Event;
+}
+
+mod council_mod {
+    pub use council::Event;
+}
+
+mod referendum_mod {
+    pub use referendum::Event;
+    pub use referendum::Instance1;
+}
+
+impl_outer_event! {
+    pub enum TestEvent for Test {
+        bounty<T>,
+        frame_system<T>,
+        balances<T>,
+        membership_mod<T>,
+        council_mod<T>,
+        referendum_mod Instance1 <T>,
+    }
+}
+
+// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Test;
+parameter_types! {
+    pub const BlockHashCount: u64 = 250;
+    pub const MaximumBlockWeight: u32 = 1024;
+    pub const MaximumBlockLength: u32 = 2 * 1024;
+    pub const AvailableBlockRatio: Perbill = Perbill::one();
+    pub const BountyModuleId: ModuleId = ModuleId(*b"m:bounty"); // module : bounty
+    pub const BountyLockId: [u8; 8] = [12; 8];
+    pub const ClosedContractSizeLimit: u32 = 3;
+    pub const MinCherryLimit: u64 = 10;
+    pub const MinFundingLimit: u64 = 50;
+    pub const MinWorkEntrantStake: u64 = 10;
+}
+
+impl frame_system::Trait for Test {
+    type BaseCallFilter = ();
+    type Origin = Origin;
+    type Call = ();
+    type Index = u64;
+    type BlockNumber = u64;
+    type Hash = H256;
+    type Hashing = BlakeTwo256;
+    type AccountId = u128;
+    type Lookup = IdentityLookup<Self::AccountId>;
+    type Header = Header;
+    type Event = TestEvent;
+    type BlockHashCount = BlockHashCount;
+    type MaximumBlockWeight = MaximumBlockWeight;
+    type DbWeight = ();
+    type BlockExecutionWeight = ();
+    type ExtrinsicBaseWeight = ();
+    type MaximumExtrinsicWeight = ();
+    type MaximumBlockLength = MaximumBlockLength;
+    type AvailableBlockRatio = AvailableBlockRatio;
+    type Version = ();
+    type AccountData = balances::AccountData<u64>;
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
+    type PalletInfo = ();
+    type SystemWeightInfo = ();
+}
+
+impl Trait for Test {
+    type Event = TestEvent;
+    type ModuleId = BountyModuleId;
+    type BountyId = u64;
+    type Membership = ();
+    type WeightInfo = ();
+    type CouncilBudgetManager = CouncilBudgetManager;
+    type StakingHandler = StakingManager<Test, BountyLockId>;
+    type EntryId = u64;
+    type ClosedContractSizeLimit = ClosedContractSizeLimit;
+    type MinCherryLimit = MinCherryLimit;
+    type MinFundingLimit = MinFundingLimit;
+    type MinWorkEntrantStake = MinWorkEntrantStake;
+}
+
+pub const STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER: u128 = 10000;
+impl common::StakingAccountValidator<Test> for () {
+    fn is_member_staking_account(_: &u64, account_id: &u128) -> bool {
+        *account_id != STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER
+    }
+}
+
+impl common::membership::MembershipInfoProvider<Test> for () {
+    fn controller_account_id(member_id: u64) -> Result<u128, DispatchError> {
+        if member_id < 10 {
+            return Ok(member_id as u128); // similar account_id
+        }
+
+        Err(membership::Error::<Test>::MemberProfileNotFound.into())
+    }
+}
+
+pub const COUNCIL_BUDGET_ACCOUNT_ID: u128 = 90000000;
+pub struct CouncilBudgetManager;
+impl common::council::CouncilBudgetManager<u64> for CouncilBudgetManager {
+    fn get_budget() -> u64 {
+        balances::Module::<Test>::usable_balance(&COUNCIL_BUDGET_ACCOUNT_ID)
+    }
+
+    fn set_budget(budget: u64) {
+        let old_budget = Self::get_budget();
+
+        if budget > old_budget {
+            let _ = balances::Module::<Test>::deposit_creating(
+                &COUNCIL_BUDGET_ACCOUNT_ID,
+                budget - old_budget,
+            );
+        }
+
+        if budget < old_budget {
+            let _ =
+                balances::Module::<Test>::slash(&COUNCIL_BUDGET_ACCOUNT_ID, old_budget - budget);
+        }
+    }
+}
+
+impl crate::WeightInfo for () {
+    fn create_bounty_by_council(_i: u32, _j: u32) -> u64 {
+        0
+    }
+    fn create_bounty_by_member(_i: u32, _j: u32) -> u64 {
+        0
+    }
+    fn cancel_bounty_by_member() -> u64 {
+        0
+    }
+    fn cancel_bounty_by_council() -> u64 {
+        0
+    }
+    fn veto_bounty() -> u64 {
+        0
+    }
+    fn fund_bounty_by_member() -> u64 {
+        0
+    }
+    fn fund_bounty_by_council() -> u64 {
+        0
+    }
+    fn withdraw_funding_by_member() -> u64 {
+        0
+    }
+    fn withdraw_funding_by_council() -> u64 {
+        0
+    }
+    fn announce_work_entry(_i: u32) -> u64 {
+        0
+    }
+    fn withdraw_work_entry() -> u64 {
+        0
+    }
+    fn submit_work(_i: u32) -> u64 {
+        0
+    }
+    fn submit_oracle_judgment_by_council_all_winners(_i: u32) -> u64 {
+        0
+    }
+    fn submit_oracle_judgment_by_council_all_rejected(_i: u32) -> u64 {
+        0
+    }
+    fn submit_oracle_judgment_by_member_all_winners(_i: u32) -> u64 {
+        0
+    }
+    fn submit_oracle_judgment_by_member_all_rejected(_i: u32) -> u64 {
+        0
+    }
+    fn withdraw_work_entrant_funds() -> u64 {
+        0
+    }
+}
+
+impl common::membership::Trait for Test {
+    type MemberId = u64;
+    type ActorId = u64;
+}
+
+impl common::membership::MemberOriginValidator<Origin, u64, u128> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        _member_id: u64,
+    ) -> Result<u128, DispatchError> {
+        let signed_account_id = frame_system::ensure_signed(origin)?;
+
+        Ok(signed_account_id)
+    }
+
+    fn is_member_controller_account(_member_id: &u64, _account_id: &u128) -> bool {
+        true
+    }
+}
+
+parameter_types! {
+    pub const DefaultMembershipPrice: u64 = 100;
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
+    pub const ReferralCutMaximumPercent: u8 = 50;
+    pub const MinimumStakeForOpening: u32 = 50;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
+    pub const StakingCandidateLockId: [u8; 8] = [3; 8];
+    pub const DefaultInitialInvitationBalance: u64 = 100;
+    pub const CandidateStake: u64 = 130;
+}
+
+// Weights info stub
+pub struct Weights;
+impl membership::WeightInfo for Weights {
+    fn buy_membership_without_referrer(_: u32, _: u32) -> Weight {
+        unimplemented!()
+    }
+    fn buy_membership_with_referrer(_: u32, _: u32) -> Weight {
+        unimplemented!()
+    }
+    fn update_profile(_: u32) -> Weight {
+        unimplemented!()
+    }
+    fn update_accounts_none() -> Weight {
+        unimplemented!()
+    }
+    fn update_accounts_root() -> Weight {
+        unimplemented!()
+    }
+    fn update_accounts_controller() -> Weight {
+        unimplemented!()
+    }
+    fn update_accounts_both() -> Weight {
+        unimplemented!()
+    }
+    fn set_referral_cut() -> Weight {
+        unimplemented!()
+    }
+    fn transfer_invites() -> Weight {
+        unimplemented!()
+    }
+    fn invite_member(_: u32, _: u32) -> Weight {
+        unimplemented!()
+    }
+    fn set_membership_price() -> Weight {
+        unimplemented!()
+    }
+    fn update_profile_verification() -> Weight {
+        unimplemented!()
+    }
+    fn set_leader_invitation_quota() -> Weight {
+        unimplemented!()
+    }
+    fn set_initial_invitation_balance() -> Weight {
+        unimplemented!()
+    }
+    fn set_initial_invitation_count() -> Weight {
+        unimplemented!()
+    }
+    fn add_staking_account_candidate() -> Weight {
+        unimplemented!()
+    }
+    fn confirm_staking_account() -> Weight {
+        unimplemented!()
+    }
+    fn remove_staking_account() -> Weight {
+        unimplemented!()
+    }
+}
+
+impl pallet_timestamp::Trait for Test {
+    type Moment = u64;
+    type OnTimestampSet = ();
+    type MinimumPeriod = MinimumPeriod;
+    type WeightInfo = ();
+}
+
+impl membership::Trait for Test {
+    type Event = TestEvent;
+    type DefaultMembershipPrice = DefaultMembershipPrice;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
+    type WorkingGroup = ();
+    type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type StakingCandidateStakingHandler =
+        staking_handler::StakingManager<Self, StakingCandidateLockId>;
+    type WeightInfo = Weights;
+    type CandidateStake = CandidateStake;
+}
+
+impl LockComparator<<Test as balances::Trait>::Balance> for Test {
+    fn are_locks_conflicting(new_lock: &LockIdentifier, existing_locks: &[LockIdentifier]) -> bool {
+        if *new_lock != BountyLockId::get() {
+            return false;
+        }
+
+        existing_locks.contains(new_lock)
+    }
+}
+
+impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
+    fn get_budget() -> u64 {
+        unimplemented!()
+    }
+
+    fn set_budget(_new_value: u64) {
+        unimplemented!()
+    }
+}
+
+impl common::working_group::WorkingGroupAuthenticator<Test> for () {
+    fn ensure_worker_origin(
+        _origin: <Test as frame_system::Trait>::Origin,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
+    ) -> DispatchResult {
+        unimplemented!();
+    }
+
+    fn ensure_leader_origin(_origin: <Test as frame_system::Trait>::Origin) -> DispatchResult {
+        unimplemented!()
+    }
+
+    fn get_leader_member_id() -> Option<<Test as common::membership::Trait>::MemberId> {
+        unimplemented!();
+    }
+
+    fn is_leader_account_id(_account_id: &<Test as frame_system::Trait>::AccountId) -> bool {
+        unimplemented!()
+    }
+
+    fn is_worker_account_id(
+        _account_id: &<Test as frame_system::Trait>::AccountId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
+    ) -> bool {
+        unimplemented!()
+    }
+}
+
+parameter_types! {
+    pub const ExistentialDeposit: u32 = 0;
+    pub const MinimumPeriod: u64 = 5;
+}
+
+impl balances::Trait for Test {
+    type Balance = u64;
+    type DustRemoval = ();
+    type Event = TestEvent;
+    type ExistentialDeposit = ExistentialDeposit;
+    type AccountStore = System;
+    type WeightInfo = ();
+    type MaxLocks = ();
+}
+
+parameter_types! {
+    pub const MinNumberOfExtraCandidates: u64 = 1;
+    pub const AnnouncingPeriodDuration: u64 = 15;
+    pub const IdlePeriodDuration: u64 = 27;
+    pub const CouncilSize: u64 = 3;
+    pub const MinCandidateStake: u64 = 11000;
+    pub const CandidacyLockId: LockIdentifier = *b"council1";
+    pub const CouncilorLockId: LockIdentifier = *b"council2";
+    pub const ElectedMemberRewardPeriod: u64 = 10;
+    pub const BudgetRefillAmount: u64 = 1000;
+    // intentionally high number that prevents side-effecting tests other than  budget refill tests
+    pub const BudgetRefillPeriod: u64 = 1000;
+}
+
+pub type ReferendumInstance = referendum::Instance1;
+
+impl council::Trait for Test {
+    type Event = TestEvent;
+    type Referendum = referendum::Module<Test, ReferendumInstance>;
+    type MinNumberOfExtraCandidates = MinNumberOfExtraCandidates;
+    type CouncilSize = CouncilSize;
+    type AnnouncingPeriodDuration = AnnouncingPeriodDuration;
+    type IdlePeriodDuration = IdlePeriodDuration;
+    type MinCandidateStake = MinCandidateStake;
+    type CandidacyLock = StakingManager<Self, CandidacyLockId>;
+    type CouncilorLock = StakingManager<Self, CouncilorLockId>;
+    type ElectedMemberRewardPeriod = ElectedMemberRewardPeriod;
+    type BudgetRefillPeriod = BudgetRefillPeriod;
+    type StakingAccountValidator = ();
+    type WeightInfo = CouncilWeightInfo;
+    type MemberOriginValidator = ();
+
+    fn new_council_elected(_: &[council::CouncilMemberOf<Self>]) {}
+}
+
+pub struct CouncilWeightInfo;
+impl council::WeightInfo for CouncilWeightInfo {
+    fn try_process_budget() -> Weight {
+        0
+    }
+    fn try_progress_stage_idle() -> Weight {
+        0
+    }
+    fn try_progress_stage_announcing_start_election(_: u32) -> Weight {
+        0
+    }
+    fn try_progress_stage_announcing_restart() -> Weight {
+        0
+    }
+    fn announce_candidacy() -> Weight {
+        0
+    }
+    fn release_candidacy_stake() -> Weight {
+        0
+    }
+    fn set_candidacy_note(_: u32) -> Weight {
+        0
+    }
+    fn withdraw_candidacy() -> Weight {
+        0
+    }
+    fn set_budget() -> Weight {
+        0
+    }
+    fn plan_budget_refill() -> Weight {
+        0
+    }
+    fn set_budget_increment() -> Weight {
+        0
+    }
+    fn set_councilor_reward() -> Weight {
+        0
+    }
+    fn funding_request(_: u32) -> Weight {
+        0
+    }
+}
+
+parameter_types! {
+    pub const VoteStageDuration: u64 = 19;
+    pub const RevealStageDuration: u64 = 23;
+    pub const MinimumVotingStake: u64 = 10000;
+    pub const MaxSaltLength: u64 = 32; // use some multiple of 8 for ez testing
+    pub const VotingLockId: LockIdentifier = *b"referend";
+    pub const MaxWinnerTargetCount: u64 = 10;
+}
+
+impl referendum::Trait<ReferendumInstance> for Test {
+    type Event = TestEvent;
+    type MaxSaltLength = MaxSaltLength;
+    type StakingHandler = staking_handler::StakingManager<Self, VotingLockId>;
+    type ManagerOrigin =
+        EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
+    type VotePower = u64;
+    type VoteStageDuration = VoteStageDuration;
+    type RevealStageDuration = RevealStageDuration;
+    type MinimumStake = MinimumVotingStake;
+    type WeightInfo = ReferendumWeightInfo;
+    type MaxWinnerTargetCount = MaxWinnerTargetCount;
+
+    fn calculate_vote_power(
+        _: &<Self as frame_system::Trait>::AccountId,
+        _: &Self::Balance,
+    ) -> Self::VotePower {
+        1
+    }
+
+    fn can_unlock_vote_stake(
+        _: &referendum::CastVote<Self::Hash, Self::Balance, Self::MemberId>,
+    ) -> bool {
+        true
+    }
+
+    fn process_results(winners: &[referendum::OptionResult<Self::MemberId, Self::VotePower>]) {
+        let tmp_winners: Vec<referendum::OptionResult<Self::MemberId, Self::VotePower>> = winners
+            .iter()
+            .map(|item| referendum::OptionResult {
+                option_id: item.option_id,
+                vote_power: item.vote_power.into(),
+            })
+            .collect();
+        <council::Module<Test> as council::ReferendumConnection<Test>>::recieve_referendum_results(
+            tmp_winners.as_slice(),
+        );
+    }
+
+    fn is_valid_option_id(option_index: &u64) -> bool {
+        <council::Module<Test> as council::ReferendumConnection<Test>>::is_valid_candidate_id(
+            option_index,
+        )
+    }
+
+    fn get_option_power(option_id: &u64) -> Self::VotePower {
+        <council::Module<Test> as council::ReferendumConnection<Test>>::get_option_power(option_id)
+    }
+
+    fn increase_option_power(option_id: &u64, amount: &Self::VotePower) {
+        <council::Module<Test> as council::ReferendumConnection<Test>>::increase_option_power(
+            option_id, amount,
+        );
+    }
+}
+
+pub struct ReferendumWeightInfo;
+impl referendum::WeightInfo for ReferendumWeightInfo {
+    fn on_initialize_revealing(_: u32) -> Weight {
+        0
+    }
+    fn on_initialize_voting() -> Weight {
+        0
+    }
+    fn vote() -> Weight {
+        0
+    }
+    fn reveal_vote_space_for_new_winner(_: u32) -> Weight {
+        0
+    }
+    fn reveal_vote_space_not_in_winners(_: u32) -> Weight {
+        0
+    }
+    fn reveal_vote_space_replace_last_winner(_: u32) -> Weight {
+        0
+    }
+    fn reveal_vote_already_existing(_: u32) -> Weight {
+        0
+    }
+    fn release_vote_stake() -> Weight {
+        0
+    }
+}
+
+pub fn build_test_externalities() -> sp_io::TestExternalities {
+    let t = frame_system::GenesisConfig::default()
+        .build_storage::<Test>()
+        .unwrap();
+
+    t.into()
+}
+
+pub type System = frame_system::Module<Test>;
+pub type Bounty = Module<Test>;
+pub type Balances = balances::Module<Test>;

+ 3419 - 0
runtime-modules/bounty/src/tests/mod.rs

@@ -0,0 +1,3419 @@
+#![cfg(test)]
+
+pub(crate) mod fixtures;
+pub(crate) mod mocks;
+
+use frame_support::storage::StorageMap;
+use frame_support::traits::Currency;
+use frame_system::RawOrigin;
+use sp_runtime::DispatchError;
+use sp_std::collections::btree_map::BTreeMap;
+
+use crate::{
+    Bounties, BountyActor, BountyCreationParameters, BountyMilestone, BountyRecord, BountyStage,
+    Entries, Error, FundingType, OracleWorkEntryJudgment, RawEvent,
+};
+use fixtures::{
+    get_council_budget, increase_account_balance, increase_total_balance_issuance_using_account_id,
+    run_to_block, set_council_budget, AnnounceWorkEntryFixture, CancelBountyFixture,
+    CreateBountyFixture, EventFixture, FundBountyFixture, SubmitJudgmentFixture, SubmitWorkFixture,
+    VetoBountyFixture, WithdrawFundingFixture, WithdrawWorkEntrantFundsFixture,
+    WithdrawWorkEntryFixture, DEFAULT_BOUNTY_CHERRY,
+};
+use mocks::{
+    build_test_externalities, Balances, Bounty, ClosedContractSizeLimit, MinFundingLimit, System,
+    Test, COUNCIL_BUDGET_ACCOUNT_ID, STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER,
+};
+
+const DEFAULT_WINNER_REWARD: u64 = 10;
+
+#[test]
+fn validate_funding_bounty_stage() {
+    build_test_externalities().execute_with(|| {
+        let created_at = 10;
+
+        // Perpetual funding period
+        // No contributions.
+        let bounty = BountyRecord {
+            creation_params: BountyCreationParameters::<Test> {
+                ..Default::default()
+            },
+            milestone: BountyMilestone::Created {
+                created_at,
+                has_contributions: false,
+            },
+            ..Default::default()
+        };
+
+        assert_eq!(
+            Bounty::get_bounty_stage(&bounty),
+            BountyStage::Funding {
+                has_contributions: false
+            }
+        );
+
+        // Has contributions
+        let bounty = BountyRecord {
+            creation_params: BountyCreationParameters::<Test> {
+                ..Default::default()
+            },
+            milestone: BountyMilestone::Created {
+                created_at,
+                has_contributions: true,
+            },
+            ..Default::default()
+        };
+
+        assert_eq!(
+            Bounty::get_bounty_stage(&bounty),
+            BountyStage::Funding {
+                has_contributions: true
+            }
+        );
+
+        // Limited funding period
+        let funding_period = 10;
+
+        let bounty = BountyRecord {
+            creation_params: BountyCreationParameters::<Test> {
+                funding_type: FundingType::Limited {
+                    funding_period,
+                    min_funding_amount: 10,
+                    max_funding_amount: 10,
+                },
+                ..Default::default()
+            },
+            milestone: BountyMilestone::Created {
+                created_at,
+                has_contributions: false,
+            },
+            ..Default::default()
+        };
+
+        System::set_block_number(created_at + 1);
+
+        assert_eq!(
+            Bounty::get_bounty_stage(&bounty),
+            BountyStage::Funding {
+                has_contributions: false
+            }
+        );
+
+        let bounty = BountyRecord {
+            creation_params: BountyCreationParameters::<Test> {
+                funding_type: FundingType::Limited {
+                    funding_period,
+                    min_funding_amount: 10,
+                    max_funding_amount: 10,
+                },
+                ..Default::default()
+            },
+            milestone: BountyMilestone::Created {
+                created_at,
+                has_contributions: true,
+            },
+            ..Default::default()
+        };
+
+        System::set_block_number(created_at + 2);
+
+        assert_eq!(
+            Bounty::get_bounty_stage(&bounty),
+            BountyStage::Funding {
+                has_contributions: true
+            }
+        );
+    });
+}
+
+#[test]
+fn validate_funding_expired_bounty_stage() {
+    build_test_externalities().execute_with(|| {
+        let created_at = 10;
+        let funding_period = 10;
+
+        // Limited funding period
+        // No contributions.
+        let bounty = BountyRecord {
+            creation_params: BountyCreationParameters::<Test> {
+                funding_type: FundingType::Limited {
+                    funding_period,
+                    min_funding_amount: 10,
+                    max_funding_amount: 10,
+                },
+                ..Default::default()
+            },
+            milestone: BountyMilestone::Created {
+                created_at,
+                has_contributions: false,
+            },
+            ..Default::default()
+        };
+
+        System::set_block_number(created_at + funding_period + 1);
+
+        assert_eq!(
+            Bounty::get_bounty_stage(&bounty),
+            BountyStage::FundingExpired
+        );
+    });
+}
+
+#[test]
+fn validate_work_submission_bounty_stage() {
+    build_test_externalities().execute_with(|| {
+        let created_at = 10;
+        let funding_period = 10;
+        let work_period = 10;
+        let judging_period = 10;
+        let min_funding_amount = 100;
+        let work_period_started_at = created_at + funding_period;
+
+        // Limited funding period
+        let params = BountyCreationParameters::<Test> {
+            funding_type: FundingType::Limited {
+                funding_period,
+                min_funding_amount,
+                max_funding_amount: 10,
+            },
+            work_period,
+            ..Default::default()
+        };
+
+        let bounty = BountyRecord {
+            creation_params: params.clone(),
+            milestone: BountyMilestone::Created {
+                created_at,
+                has_contributions: true,
+            },
+            total_funding: min_funding_amount,
+            ..Default::default()
+        };
+
+        System::set_block_number(created_at + funding_period + 1);
+
+        assert_eq!(
+            Bounty::get_bounty_stage(&bounty),
+            BountyStage::WorkSubmission
+        );
+
+        // Max funding reached.
+        let max_funding_reached_at = 30;
+
+        let bounty = BountyRecord {
+            creation_params: params,
+            milestone: BountyMilestone::BountyMaxFundingReached {
+                max_funding_reached_at,
+            },
+            ..Default::default()
+        };
+
+        System::set_block_number(max_funding_reached_at + 1);
+
+        assert_eq!(
+            Bounty::get_bounty_stage(&bounty),
+            BountyStage::WorkSubmission
+        );
+
+        // Work period is not expired.
+        let bounty = BountyRecord {
+            creation_params: BountyCreationParameters::<Test> {
+                funding_type: FundingType::Limited {
+                    funding_period,
+                    min_funding_amount,
+                    max_funding_amount: 10,
+                },
+                work_period,
+                judging_period,
+                ..Default::default()
+            },
+            milestone: BountyMilestone::WorkSubmitted {
+                work_period_started_at,
+            },
+            ..Default::default()
+        };
+
+        System::set_block_number(work_period_started_at + 1);
+
+        assert_eq!(
+            Bounty::get_bounty_stage(&bounty),
+            BountyStage::WorkSubmission
+        );
+    });
+}
+
+#[test]
+fn validate_judgment_bounty_stage() {
+    build_test_externalities().execute_with(|| {
+        let created_at = 10;
+        let funding_period = 10;
+        let work_period = 10;
+        let judging_period = 10;
+        let min_funding_amount = 100;
+        let work_period_started_at = created_at + funding_period;
+
+        // Work period is not expired.
+        let bounty = BountyRecord {
+            creation_params: BountyCreationParameters::<Test> {
+                funding_type: FundingType::Limited {
+                    funding_period,
+                    min_funding_amount,
+                    max_funding_amount: 10,
+                },
+                work_period,
+                judging_period,
+                ..Default::default()
+            },
+            milestone: BountyMilestone::WorkSubmitted {
+                work_period_started_at,
+            },
+            ..Default::default()
+        };
+
+        System::set_block_number(work_period_started_at + work_period + 1);
+
+        assert_eq!(Bounty::get_bounty_stage(&bounty), BountyStage::Judgment);
+    });
+}
+
+#[test]
+fn validate_successful_withdrawal_bounty_stage() {
+    build_test_externalities().execute_with(|| {
+        // Judging was submitted.
+        let successful_bounty = true;
+        let bounty = BountyRecord {
+            milestone: BountyMilestone::JudgmentSubmitted { successful_bounty },
+            ..Default::default()
+        };
+
+        assert_eq!(
+            Bounty::get_bounty_stage(&bounty),
+            BountyStage::SuccessfulBountyWithdrawal
+        );
+    });
+}
+
+#[test]
+fn create_bounty_succeeds() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let text = b"Bounty text".to_vec();
+
+        let create_bounty_fixture = CreateBountyFixture::default().with_metadata(text.clone());
+        create_bounty_fixture.call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyCreated(
+            bounty_id,
+            create_bounty_fixture.get_bounty_creation_parameters(),
+            text,
+        ));
+    });
+}
+
+#[test]
+fn create_bounty_fails_with_invalid_closed_contract() {
+    build_test_externalities().execute_with(|| {
+        CreateBountyFixture::default()
+            .with_closed_contract(Vec::new())
+            .call_and_assert(Err(Error::<Test>::ClosedContractMemberListIsEmpty.into()));
+
+        let large_member_id_list: Vec<u64> = (1..(ClosedContractSizeLimit::get() + 10))
+            .map(|x| x.into())
+            .collect();
+
+        CreateBountyFixture::default()
+            .with_closed_contract(large_member_id_list)
+            .call_and_assert(Err(Error::<Test>::ClosedContractMemberListIsTooLarge.into()));
+    });
+}
+
+#[test]
+fn create_bounty_fails_with_insufficient_cherry_value() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        CreateBountyFixture::default()
+            .with_cherry(0)
+            .call_and_assert(Err(Error::<Test>::CherryLessThenMinimumAllowed.into()));
+    });
+}
+
+#[test]
+fn create_bounty_transfers_member_balance_correctly() {
+    build_test_externalities().execute_with(|| {
+        let member_id = 1;
+        let account_id = 1;
+        let cherry = 100;
+        let initial_balance = 500;
+
+        increase_total_balance_issuance_using_account_id(account_id, initial_balance);
+
+        // Insufficient member controller account balance.
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_creator_member_id(member_id)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&account_id),
+            initial_balance - cherry
+        );
+
+        let bounty_id = 1;
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            cherry
+        );
+    });
+}
+
+#[test]
+fn create_bounty_transfers_the_council_balance_correctly() {
+    build_test_externalities().execute_with(|| {
+        let cherry = 100;
+        let initial_balance = 500;
+
+        set_council_budget(initial_balance);
+
+        // Insufficient member controller account balance.
+        CreateBountyFixture::default()
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(get_council_budget(), initial_balance - cherry);
+
+        let bounty_id = 1;
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            cherry
+        );
+    });
+}
+
+#[test]
+fn create_bounty_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        // For a council bounty.
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Signed(1))
+            .call_and_assert(Err(DispatchError::BadOrigin));
+
+        // For a member bounty.
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .with_creator_member_id(1)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn create_bounty_fails_with_invalid_funding_parameters() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        CreateBountyFixture::default()
+            .with_limited_funding(0, 1, 1)
+            .call_and_assert(Err(Error::<Test>::FundingAmountCannotBeZero.into()));
+
+        CreateBountyFixture::default()
+            .with_limited_funding(1, 0, 1)
+            .call_and_assert(Err(Error::<Test>::FundingAmountCannotBeZero.into()));
+
+        CreateBountyFixture::default()
+            .with_limited_funding(1, 1, 0)
+            .call_and_assert(Err(Error::<Test>::FundingPeriodCannotBeZero.into()));
+
+        CreateBountyFixture::default()
+            .with_perpetual_funding(0)
+            .call_and_assert(Err(Error::<Test>::FundingAmountCannotBeZero.into()));
+
+        CreateBountyFixture::default()
+            .with_limited_funding(100, 1, 100)
+            .call_and_assert(Err(
+                Error::<Test>::MinFundingAmountCannotBeGreaterThanMaxAmount.into(),
+            ));
+    });
+}
+
+#[test]
+fn create_bounty_fails_with_invalid_entrant_stake() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let invalid_stake = 1;
+        CreateBountyFixture::default()
+            .with_entrant_stake(invalid_stake)
+            .call_and_assert(Err(Error::<Test>::EntrantStakeIsLessThanMininum.into()));
+    });
+}
+
+#[test]
+fn create_bounty_fails_with_invalid_periods() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        CreateBountyFixture::default()
+            .with_work_period(0)
+            .call_and_assert(Err(Error::<Test>::WorkPeriodCannotBeZero.into()));
+
+        CreateBountyFixture::default()
+            .with_judging_period(0)
+            .call_and_assert(Err(Error::<Test>::JudgingPeriodCannotBeZero.into()));
+    });
+}
+
+#[test]
+fn create_bounty_fails_with_insufficient_balances() {
+    build_test_externalities().execute_with(|| {
+        let member_id = 1;
+        let account_id = 1;
+        let cherry = 100;
+
+        // Insufficient council budget.
+        CreateBountyFixture::default()
+            .with_cherry(cherry)
+            .call_and_assert(Err(Error::<Test>::InsufficientBalanceForBounty.into()));
+
+        // Insufficient member controller account balance.
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_creator_member_id(member_id)
+            .with_cherry(cherry)
+            .call_and_assert(Err(Error::<Test>::InsufficientBalanceForBounty.into()));
+    });
+}
+
+#[test]
+fn cancel_bounty_succeeds_full_test() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let cherry = 100;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        assert_eq!(get_council_budget(), initial_balance - cherry);
+
+        CancelBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(get_council_budget(), initial_balance);
+
+        EventFixture::contains_crate_event(RawEvent::BountyCreatorCherryWithdrawal(
+            bounty_id,
+            BountyActor::Council,
+        ));
+
+        EventFixture::contains_crate_event(RawEvent::BountyRemoved(bounty_id));
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyCanceled(
+            bounty_id,
+            BountyActor::Council,
+        ));
+    });
+}
+
+#[test]
+fn cancel_bounty_succeeds_at_funding_expired_stage() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let funding_period = 10;
+        CreateBountyFixture::default()
+            .with_funding_period(funding_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        run_to_block(funding_period + 1);
+
+        // Funding period expired with no contribution.
+        CancelBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+    });
+}
+
+#[test]
+fn cancel_bounty_by_member_succeeds() {
+    build_test_externalities().execute_with(|| {
+        let member_id = 1;
+        let account_id = 1;
+        let initial_balance = 500;
+
+        increase_total_balance_issuance_using_account_id(account_id, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_creator_member_id(member_id)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        CancelBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_creator_member_id(member_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+    });
+}
+
+#[test]
+fn cancel_bounty_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        CancelBountyFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn cancel_bounty_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        let member_id = 1;
+        let account_id = 1;
+        let initial_balance = 500;
+
+        increase_account_balance(&account_id, initial_balance);
+        set_council_budget(initial_balance);
+
+        // Created by council - try to cancel with bad origin
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+        CancelBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Err(DispatchError::BadOrigin));
+
+        // Created by a member - try to cancel with invalid member_id
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_creator_member_id(member_id)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 2u64;
+        let invalid_member_id = 2;
+
+        CancelBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_creator_member_id(invalid_member_id)
+            .call_and_assert(Err(Error::<Test>::NotBountyActor.into()));
+
+        // Created by a member - try to cancel with bad origin
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_creator_member_id(member_id)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 3u64;
+
+        CancelBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::None)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+
+        // Created by a member  - try to cancel by council
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_creator_member_id(member_id)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 4u64;
+
+        CancelBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Err(Error::<Test>::NotBountyActor.into()));
+    });
+}
+
+#[test]
+fn cancel_bounty_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        // Test bounty with funding.
+        CreateBountyFixture::default().call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(MinFundingLimit::get())
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        CancelBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(Error::<Test>::InvalidStageUnexpectedFunding.into()));
+    });
+}
+
+#[test]
+fn veto_bounty_succeeds() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let cherry = 100;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        assert_eq!(get_council_budget(), initial_balance - cherry);
+
+        VetoBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(get_council_budget(), initial_balance);
+
+        EventFixture::contains_crate_event(RawEvent::BountyCreatorCherryWithdrawal(
+            bounty_id,
+            BountyActor::Council,
+        ));
+
+        EventFixture::contains_crate_event(RawEvent::BountyRemoved(bounty_id));
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyVetoed(bounty_id));
+    });
+}
+
+#[test]
+fn veto_bounty_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        VetoBountyFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn veto_bounty_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let account_id = 1;
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        VetoBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn veto_bounty_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        // Test bounty with funding.
+        CreateBountyFixture::default().call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(MinFundingLimit::get())
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        VetoBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(Error::<Test>::InvalidStageUnexpectedFunding.into()));
+    });
+}
+
+#[test]
+fn fund_bounty_succeeds_by_member() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let max_amount = 500;
+        let amount = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let initial_balance = 500;
+        let cherry = DEFAULT_BOUNTY_CHERRY;
+
+        increase_total_balance_issuance_using_account_id(account_id, initial_balance);
+        increase_total_balance_issuance_using_account_id(
+            COUNCIL_BUDGET_ACCOUNT_ID,
+            initial_balance,
+        );
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(amount)
+            .with_member_id(member_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&account_id),
+            initial_balance - amount
+        );
+
+        assert_eq!(
+            crate::Module::<Test>::contribution_by_bounty_by_actor(
+                bounty_id,
+                BountyActor::Member(member_id)
+            ),
+            amount
+        );
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            amount + cherry
+        );
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyFunded(
+            bounty_id,
+            BountyActor::Member(member_id),
+            amount,
+        ));
+    });
+}
+
+#[test]
+fn fund_bounty_succeeds_by_council() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let max_amount = 500;
+        let amount = 100;
+        let initial_balance = 500;
+        let cherry = DEFAULT_BOUNTY_CHERRY;
+
+        increase_account_balance(&COUNCIL_BUDGET_ACCOUNT_ID, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&COUNCIL_BUDGET_ACCOUNT_ID),
+            initial_balance - amount - cherry
+        );
+
+        assert_eq!(
+            crate::Module::<Test>::contribution_by_bounty_by_actor(bounty_id, BountyActor::Council),
+            amount
+        );
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            amount + cherry
+        );
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyFunded(
+            bounty_id,
+            BountyActor::Council,
+            amount,
+        ));
+    });
+}
+
+#[test]
+fn fund_bounty_succeeds_with_reaching_max_funding_amount() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let max_amount = 50;
+        let amount = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let initial_balance = 500;
+
+        increase_total_balance_issuance_using_account_id(account_id, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(amount)
+            .with_member_id(member_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&account_id),
+            initial_balance - max_amount
+        );
+
+        let bounty = Bounty::bounties(&bounty_id);
+        assert_eq!(
+            bounty.milestone,
+            BountyMilestone::BountyMaxFundingReached {
+                max_funding_reached_at: starting_block,
+            }
+        );
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyMaxFundingReached(bounty_id));
+    });
+}
+
+#[test]
+fn multiple_fund_bounty_succeed() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let max_amount = 5000;
+        let amount = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let initial_balance = 500;
+        let cherry = DEFAULT_BOUNTY_CHERRY;
+
+        increase_total_balance_issuance_using_account_id(account_id, initial_balance);
+        increase_total_balance_issuance_using_account_id(
+            COUNCIL_BUDGET_ACCOUNT_ID,
+            initial_balance,
+        );
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(amount)
+            .with_member_id(member_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Ok(()));
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(amount)
+            .with_member_id(member_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&account_id),
+            initial_balance - 2 * amount
+        );
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            2 * amount + cherry
+        );
+    });
+}
+
+#[test]
+fn fund_bounty_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        FundBountyFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn fund_bounty_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn fund_bounty_fails_with_insufficient_balance() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let member_id = 1;
+        let account_id = 1;
+        let amount = 100;
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_amount(amount)
+            .call_and_assert(Err(Error::<Test>::InsufficientBalanceForBounty.into()));
+    });
+}
+
+#[test]
+fn fund_bounty_fails_with_zero_amount() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let member_id = 1;
+        let account_id = 1;
+        let amount = 0;
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_amount(amount)
+            .call_and_assert(Err(Error::<Test>::ZeroFundingAmount.into()));
+    });
+}
+
+#[test]
+fn fund_bounty_fails_with_less_than_minimum_amount() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let member_id = 1;
+        let account_id = 1;
+        let amount = 10;
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_amount(amount)
+            .call_and_assert(Err(Error::<Test>::FundingLessThenMinimumAllowed.into()));
+    });
+}
+
+#[test]
+fn fund_bounty_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let max_amount = 100;
+        let amount = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let initial_balance = 500;
+
+        increase_total_balance_issuance_using_account_id(account_id, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        // Fund to maximum.
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .call_and_assert(Ok(()));
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_bounty_id(bounty_id)
+            .with_amount(amount)
+            .call_and_assert(Err(
+                Error::<Test>::InvalidStageUnexpectedWorkSubmission.into()
+            ));
+    });
+}
+
+#[test]
+fn fund_bounty_fails_with_expired_funding_period() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        let amount = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let initial_balance = 500;
+        let funding_period = 10;
+
+        increase_total_balance_issuance_using_account_id(account_id, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .with_funding_period(funding_period)
+            .call_and_assert(Ok(()));
+
+        run_to_block(funding_period + 1);
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_amount(amount)
+            .call_and_assert(Err(
+                Error::<Test>::InvalidStageUnexpectedFundingExpired.into()
+            ));
+    });
+}
+
+#[test]
+fn withdraw_member_funding_succeeds() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let max_amount = 500;
+        let amount = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let initial_balance = 500;
+        let cherry = 200;
+        let funding_period = 10;
+
+        increase_account_balance(&COUNCIL_BUDGET_ACCOUNT_ID, initial_balance);
+        increase_account_balance(&account_id, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_limited_funding(max_amount, max_amount, funding_period)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_amount(amount)
+            .call_and_assert(Ok(()));
+
+        run_to_block(funding_period + starting_block + 1);
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_member_id(member_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&account_id),
+            initial_balance + cherry
+        );
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&COUNCIL_BUDGET_ACCOUNT_ID),
+            initial_balance - cherry
+        );
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            0
+        );
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyRemoved(bounty_id));
+    });
+}
+
+#[test]
+fn withdraw_council_funding_succeeds() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let max_amount = 500;
+        let amount = 100;
+        let initial_balance = 500;
+        let cherry = 200;
+        let funding_period = 10;
+
+        increase_account_balance(&COUNCIL_BUDGET_ACCOUNT_ID, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_limited_funding(max_amount, max_amount, funding_period)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .with_council()
+            .with_amount(amount)
+            .call_and_assert(Ok(()));
+
+        run_to_block(funding_period + starting_block + 1);
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&COUNCIL_BUDGET_ACCOUNT_ID),
+            initial_balance
+        );
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            0
+        );
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyRemoved(bounty_id));
+    });
+}
+
+#[test]
+fn withdraw_member_funding_with_half_cherry() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let max_amount = 500;
+        let amount = 100;
+        let account_id1 = 1;
+        let member_id1 = 1;
+        let account_id2 = 2;
+        let member_id2 = 2;
+        let initial_balance = 500;
+        let cherry = 200;
+        let funding_period = 10;
+
+        increase_account_balance(&COUNCIL_BUDGET_ACCOUNT_ID, initial_balance);
+        increase_account_balance(&account_id1, initial_balance);
+        increase_account_balance(&account_id2, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_limited_funding(max_amount, max_amount, funding_period)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_amount(amount)
+            .call_and_assert(Ok(()));
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id2))
+            .with_member_id(member_id2)
+            .with_amount(amount)
+            .call_and_assert(Ok(()));
+
+        run_to_block(funding_period + starting_block + 1);
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_member_id(member_id1)
+            .with_origin(RawOrigin::Signed(account_id1))
+            .call_and_assert(Ok(()));
+
+        // A half of the cherry
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&account_id1),
+            initial_balance + cherry / 2
+        );
+
+        // On funding amount + creation funding + half of the cherry left.
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            amount + cherry / 2
+        );
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyFundingWithdrawal(
+            bounty_id,
+            BountyActor::Member(member_id1),
+        ));
+    });
+}
+
+#[test]
+fn withdraw_member_funding_removes_bounty() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let max_amount = 500;
+        let amount = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let initial_balance = 500;
+        let cherry = 200;
+        let funding_period = 10;
+
+        increase_account_balance(&COUNCIL_BUDGET_ACCOUNT_ID, initial_balance);
+        increase_account_balance(&account_id, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_limited_funding(max_amount, max_amount, funding_period)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_amount(amount)
+            .call_and_assert(Ok(()));
+
+        run_to_block(funding_period + starting_block + 1);
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_member_id(member_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Ok(()));
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyRemoved(bounty_id));
+    });
+}
+
+#[test]
+fn withdraw_member_funding_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn withdraw_member_funding_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn withdraw_member_funding_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        let max_amount = 500;
+        let amount = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let initial_balance = 500;
+        let cherry = 200;
+        let funding_period = 10;
+
+        increase_account_balance(&COUNCIL_BUDGET_ACCOUNT_ID, initial_balance);
+        increase_account_balance(&account_id, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_limited_funding(max_amount, max_amount, funding_period)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_amount(amount)
+            .call_and_assert(Ok(()));
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_member_id(member_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Err(Error::<Test>::InvalidStageUnexpectedFunding.into()));
+    });
+}
+
+#[test]
+fn withdraw_member_funding_fails_with_invalid_bounty_funder() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let max_amount = 500;
+        let amount = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let initial_balance = 500;
+        let cherry = 200;
+        let funding_period = 10;
+
+        increase_account_balance(&COUNCIL_BUDGET_ACCOUNT_ID, initial_balance);
+        increase_account_balance(&account_id, initial_balance);
+
+        CreateBountyFixture::default()
+            .with_limited_funding(max_amount, max_amount, funding_period)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        FundBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_amount(amount)
+            .call_and_assert(Ok(()));
+
+        // Bounty failed because of the funding period
+        run_to_block(starting_block + funding_period + 1);
+
+        let invalid_account_id = 2;
+        let invalid_member_id = 2;
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_member_id(invalid_member_id)
+            .with_origin(RawOrigin::Signed(invalid_account_id))
+            .call_and_assert(Err(Error::<Test>::NoBountyContributionFound.into()));
+    });
+}
+
+#[test]
+fn withdraw_member_funding_fails_with_successful_bounty() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let winner_reward = max_amount;
+        let entrant_stake = 37;
+        let work_period = 1;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_work_period(work_period)
+            .with_entrant_stake(entrant_stake)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let member_id1 = 1;
+        let account_id1 = 1;
+        increase_account_balance(&account_id1, initial_balance);
+
+        // Winner
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_staking_account_id(account_id1)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id1),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id1 = 1;
+
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_entry_id(entry_id1)
+            .call_and_assert(Ok(()));
+
+        // Judgment
+        let mut judgment = BTreeMap::new();
+        judgment.insert(
+            entry_id1,
+            OracleWorkEntryJudgment::Winner {
+                reward: winner_reward,
+            },
+        );
+
+        run_to_block(starting_block + work_period + 1);
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment)
+            .call_and_assert(Ok(()));
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Err(
+                Error::<Test>::InvalidStageUnexpectedSuccessfulBountyWithdrawal.into(),
+            ));
+    });
+}
+
+#[test]
+fn bounty_removal_succeeds() {
+    build_test_externalities().execute_with(|| {
+        let max_amount = 500;
+        let amount = 100;
+        let initial_balance = 500;
+        let cherry = 100;
+        let account_id = 1;
+        let member_id = 1;
+        let funding_period = 10;
+
+        increase_account_balance(&account_id, initial_balance);
+
+        // Increment block in order to get Substrate events (no events on block 0).
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        // Create bounty
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_creator_member_id(member_id)
+            .with_cherry(cherry)
+            .with_limited_funding(max_amount, max_amount, funding_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        // Member funding
+        let funding_account_id1 = 2;
+        let funding_member_id1 = 2;
+        increase_account_balance(&funding_account_id1, initial_balance);
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(amount)
+            .with_member_id(funding_member_id1)
+            .with_origin(RawOrigin::Signed(funding_account_id1))
+            .call_and_assert(Ok(()));
+
+        let funding_account_id2 = 3;
+        let funding_member_id2 = 3;
+        increase_account_balance(&funding_account_id2, initial_balance);
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(amount)
+            .with_member_id(funding_member_id2)
+            .with_origin(RawOrigin::Signed(funding_account_id2))
+            .call_and_assert(Ok(()));
+
+        let funding_account_id3 = 4;
+        let funding_member_id3 = 4;
+        increase_account_balance(&funding_account_id3, initial_balance);
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(amount)
+            .with_member_id(funding_member_id3)
+            .with_origin(RawOrigin::Signed(funding_account_id3))
+            .call_and_assert(Ok(()));
+
+        // Bounty failed because of the funding period
+        run_to_block(starting_block + funding_period + 1);
+
+        // Withdraw member funding
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_member_id(funding_member_id1)
+            .with_origin(RawOrigin::Signed(funding_account_id1))
+            .call_and_assert(Ok(()));
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_member_id(funding_member_id2)
+            .with_origin(RawOrigin::Signed(funding_account_id2))
+            .call_and_assert(Ok(()));
+
+        let cherry_remaining_fraction = cherry - (cherry * 2 / 3) + amount;
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            cherry_remaining_fraction
+        );
+
+        WithdrawFundingFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_member_id(funding_member_id3)
+            .with_origin(RawOrigin::Signed(funding_account_id3))
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&account_id),
+            initial_balance - cherry
+        );
+
+        // Bounty removal effects.
+        assert_eq!(
+            balances::Module::<Test>::usable_balance(&Bounty::bounty_account_id(bounty_id)),
+            0
+        );
+
+        assert!(!crate::Bounties::<Test>::contains_key(bounty_id));
+        assert!(!Bounty::contributions_exist(&bounty_id));
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyRemoved(bounty_id));
+    });
+}
+
+#[test]
+fn announce_work_entry_succeeded() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let member_id = 1;
+        let account_id = 1;
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id = 1;
+
+        EventFixture::assert_last_crate_event(RawEvent::WorkEntryAnnounced(
+            entry_id, bounty_id, member_id, account_id,
+        ));
+    });
+}
+
+#[test]
+fn announce_work_entry_failed_with_closed_contract() {
+    build_test_externalities().execute_with(|| {
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+
+        let closed_contract_member_ids = vec![2, 3];
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_closed_contract(closed_contract_member_ids)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let member_id = 1;
+        let account_id = 1;
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(
+                Error::<Test>::CannotSubmitWorkToClosedContractBounty.into()
+            ));
+    });
+}
+
+#[test]
+fn announce_work_entry_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        AnnounceWorkEntryFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn announce_work_entry_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        AnnounceWorkEntryFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn announce_work_entry_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        CreateBountyFixture::default().call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        AnnounceWorkEntryFixture::default()
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(Error::<Test>::InvalidStageUnexpectedFunding.into()));
+    });
+}
+
+#[test]
+fn announce_work_entry_fails_with_invalid_staking_data() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let member_id = 1;
+        let account_id = 1;
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(Error::<Test>::InsufficientBalanceForStake.into()));
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(Error::<Test>::InvalidStakingAccountForMember.into()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(Error::<Test>::ConflictingStakes.into()));
+    });
+}
+
+#[test]
+fn withdraw_work_entry_succeeded() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id = 1;
+
+        WithdrawWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(Balances::usable_balance(&account_id), initial_balance);
+
+        EventFixture::assert_last_crate_event(RawEvent::WorkEntryWithdrawn(
+            bounty_id, entry_id, member_id,
+        ));
+    });
+}
+
+#[test]
+fn withdraw_work_slashes_successfully1() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 100;
+        let work_period = 1000;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(work_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        // Announcing entry with no slashes
+        let member_id1 = 1;
+        let account_id1 = 1;
+
+        increase_account_balance(&account_id1, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_staking_account_id(account_id1)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id1),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id1 = 1;
+
+        // Announcing entry with half slashing.
+
+        let member_id2 = 2;
+        let account_id2 = 2;
+
+        increase_account_balance(&account_id2, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id2))
+            .with_member_id(member_id2)
+            .with_staking_account_id(account_id2)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id2),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id2 = 2;
+
+        // No slashes
+        WithdrawWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_entry_id(entry_id1)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(Balances::usable_balance(&account_id1), initial_balance);
+
+        // Slashes half.
+        let half_period = work_period / 2;
+        run_to_block(starting_block + half_period);
+
+        WithdrawWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id2))
+            .with_member_id(member_id2)
+            .with_entry_id(entry_id2)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id2),
+            initial_balance - entrant_stake / 2
+        );
+    });
+}
+
+#[test]
+fn withdraw_work_slashes_successfully2() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 100;
+        let work_period = 1000;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(work_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        // Announcing entry with 33%
+        let member_id1 = 1;
+        let account_id1 = 1;
+
+        increase_account_balance(&account_id1, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_staking_account_id(account_id1)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id1),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id1 = 1;
+
+        // Announcing entry with full slashing.
+
+        let member_id2 = 2;
+        let account_id2 = 2;
+
+        increase_account_balance(&account_id2, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id2))
+            .with_member_id(member_id2)
+            .with_staking_account_id(account_id2)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id2),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id2 = 2;
+
+        // Slashes half.
+        let one_third_period = work_period / 3;
+        run_to_block(starting_block + one_third_period);
+
+        WithdrawWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_entry_id(entry_id1)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id1),
+            initial_balance - entrant_stake / 3
+        );
+
+        // Slashes all.
+        run_to_block(starting_block + work_period);
+
+        WithdrawWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id2))
+            .with_member_id(member_id2)
+            .with_entry_id(entry_id2)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id2),
+            initial_balance - entrant_stake
+        );
+    });
+}
+
+#[test]
+fn withdraw_work_entry_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        WithdrawWorkEntryFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn withdraw_work_entry_fails_with_invalid_entry_id() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let invalid_entry_id = 11u64;
+
+        WithdrawWorkEntryFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_entry_id(invalid_entry_id)
+            .call_and_assert(Err(Error::<Test>::WorkEntryDoesntExist.into()));
+    });
+}
+
+#[test]
+fn withdraw_work_entry_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1;
+
+        WithdrawWorkEntryFixture::default()
+            .with_entry_id(entry_id)
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn withdraw_work_entry_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+        let work_period = 10;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(work_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id = 1;
+
+        run_to_block(starting_block + work_period + 1);
+
+        WithdrawWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(
+                Error::<Test>::InvalidStageUnexpectedFailedBountyWithdrawal.into(),
+            ));
+    });
+}
+
+#[test]
+fn submit_work_succeeded() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1;
+
+        let work_data = b"Work submitted".to_vec();
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .with_work_data(work_data.clone())
+            .call_and_assert(Ok(()));
+
+        EventFixture::assert_last_crate_event(RawEvent::WorkSubmitted(
+            bounty_id, entry_id, member_id, work_data,
+        ));
+    });
+}
+
+#[test]
+fn submit_work_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        SubmitWorkFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn submit_work_fails_with_invalid_entry_id() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let invalid_entry_id = 11u64;
+
+        SubmitWorkFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_entry_id(invalid_entry_id)
+            .call_and_assert(Err(Error::<Test>::WorkEntryDoesntExist.into()));
+    });
+}
+
+#[test]
+fn submit_work_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1;
+
+        SubmitWorkFixture::default()
+            .with_entry_id(entry_id)
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn submit_work_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+        let work_period = 10;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(work_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id = 1;
+
+        run_to_block(starting_block + work_period + 1);
+
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(
+                Error::<Test>::InvalidStageUnexpectedFailedBountyWithdrawal.into(),
+            ));
+    });
+}
+
+#[test]
+fn submit_judgment_by_council_succeeded_with_complex_judgment() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+        let working_period = 10;
+        let judging_period = 10;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(working_period)
+            .with_judging_period(judging_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        // First work entry
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id1 = 1u64;
+
+        let work_data = b"Work submitted".to_vec();
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id1)
+            .with_work_data(work_data.clone())
+            .call_and_assert(Ok(()));
+
+        // Second work entry
+        let member_id = 2;
+        let account_id = 2;
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id2 = 2u64;
+
+        let work_data = b"Work submitted".to_vec();
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id2)
+            .with_work_data(work_data.clone())
+            .call_and_assert(Ok(()));
+
+        // Third work entry
+        let member_id = 3;
+        let account_id = 3;
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id3 = 3u64;
+
+        let work_data = b"Work submitted".to_vec();
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id3)
+            .with_work_data(work_data.clone())
+            .call_and_assert(Ok(()));
+
+        run_to_block(starting_block + working_period + 1);
+
+        // Judgment
+        let judgment = vec![
+            (
+                entry_id1,
+                OracleWorkEntryJudgment::Winner { reward: max_amount },
+            ),
+            (entry_id3, OracleWorkEntryJudgment::Rejected),
+        ]
+        .iter()
+        .cloned()
+        .collect::<BTreeMap<_, _>>();
+
+        assert!(<Entries<Test>>::contains_key(entry_id3));
+        assert_eq!(Balances::total_balance(&account_id), initial_balance);
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment.clone())
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Bounty::entries(entry_id1).oracle_judgment_result,
+            Some(OracleWorkEntryJudgment::Winner { reward: max_amount })
+        );
+        assert_eq!(Bounty::entries(entry_id2).oracle_judgment_result, None);
+        assert!(!<Entries<Test>>::contains_key(entry_id3));
+        assert_eq!(
+            Balances::total_balance(&account_id),
+            initial_balance - entrant_stake
+        );
+
+        EventFixture::contains_crate_event(RawEvent::WorkEntrySlashed(bounty_id, entry_id3));
+        EventFixture::assert_last_crate_event(RawEvent::OracleJudgmentSubmitted(
+            bounty_id,
+            BountyActor::Council,
+            judgment,
+        ));
+    });
+}
+
+#[test]
+fn submit_judgment_returns_cherry_on_successful_bounty() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let cherry = DEFAULT_BOUNTY_CHERRY;
+        let entrant_stake = 37;
+        let working_period = 10;
+        let judging_period = 10;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(working_period)
+            .with_judging_period(judging_period)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1u64;
+
+        let work_data = b"Work submitted".to_vec();
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .with_work_data(work_data.clone())
+            .call_and_assert(Ok(()));
+
+        run_to_block(starting_block + working_period + 1);
+
+        // Judgment
+        let judgment = vec![(
+            entry_id,
+            OracleWorkEntryJudgment::Winner { reward: max_amount },
+        )]
+        .iter()
+        .cloned()
+        .collect::<BTreeMap<_, _>>();
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment.clone())
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Bounty::entries(entry_id).oracle_judgment_result,
+            Some(OracleWorkEntryJudgment::Winner { reward: max_amount })
+        );
+
+        // Cherry returned.
+        assert_eq!(
+            get_council_budget(),
+            initial_balance - max_amount // initial - funding_amount
+        );
+
+        EventFixture::contains_crate_event(RawEvent::BountyCreatorCherryWithdrawal(
+            bounty_id,
+            BountyActor::Council,
+        ));
+    });
+}
+
+#[test]
+fn submit_judgment_dont_return_cherry_on_unsuccessful_bounty() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let cherry = DEFAULT_BOUNTY_CHERRY;
+        let entrant_stake = 37;
+        let working_period = 10;
+        let judging_period = 10;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(working_period)
+            .with_judging_period(judging_period)
+            .with_cherry(cherry)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1u64;
+
+        let work_data = b"Work submitted".to_vec();
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .with_work_data(work_data.clone())
+            .call_and_assert(Ok(()));
+
+        run_to_block(starting_block + working_period + 1);
+
+        // Judgment
+        let judgment = vec![(entry_id, OracleWorkEntryJudgment::Rejected)]
+            .iter()
+            .cloned()
+            .collect::<BTreeMap<_, _>>();
+
+        assert!(<Entries<Test>>::contains_key(entry_id));
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment.clone())
+            .call_and_assert(Ok(()));
+
+        assert!(!<Entries<Test>>::contains_key(entry_id));
+
+        // Cherry not returned.
+        assert_eq!(
+            get_council_budget(),
+            initial_balance - max_amount - cherry // initial - funding_amount - cherry
+        );
+    });
+}
+
+#[test]
+fn submit_judgment_by_member_succeeded() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+        let working_period = 10;
+        let judging_period = 10;
+        let oracle_member_id = 1;
+        let oracle_account_id = 1;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(working_period)
+            .with_judging_period(judging_period)
+            .with_oracle_member_id(oracle_member_id)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1;
+
+        let work_data = b"Work submitted".to_vec();
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .with_work_data(work_data.clone())
+            .call_and_assert(Ok(()));
+
+        run_to_block(starting_block + working_period + 1);
+
+        let judgment = vec![entry_id]
+            .iter()
+            .map(|entry_id| {
+                (
+                    *entry_id,
+                    OracleWorkEntryJudgment::Winner { reward: max_amount },
+                )
+            })
+            .collect::<BTreeMap<_, _>>();
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Signed(oracle_account_id))
+            .with_oracle_member_id(oracle_member_id)
+            .with_judgment(judgment.clone())
+            .call_and_assert(Ok(()));
+
+        EventFixture::assert_last_crate_event(RawEvent::OracleJudgmentSubmitted(
+            bounty_id,
+            BountyActor::Member(oracle_member_id),
+            judgment,
+        ));
+    });
+}
+
+#[test]
+fn submit_judgment_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn submit_judgment_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        let member_id = 1;
+        let account_id = 1;
+        let initial_balance = 500;
+
+        increase_account_balance(&account_id, initial_balance);
+        set_council_budget(initial_balance);
+
+        // Oracle is set to a council - try to submit judgment with bad origin
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Err(DispatchError::BadOrigin));
+
+        // Oracle is set to a member - try to submit judgment with invalid member_id
+        CreateBountyFixture::default()
+            .with_oracle_member_id(member_id)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 2u64;
+        let invalid_member_id = 2;
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_oracle_member_id(invalid_member_id)
+            .call_and_assert(Err(Error::<Test>::NotBountyActor.into()));
+
+        // Oracle is set to a member - try to submit judgment with bad origin
+        CreateBountyFixture::default()
+            .with_oracle_member_id(member_id)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 3u64;
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::None)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+
+        // Oracle is set to a member - try to submit judgment as a council
+        CreateBountyFixture::default()
+            .with_oracle_member_id(member_id)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 4u64;
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Err(Error::<Test>::NotBountyActor.into()));
+    });
+}
+
+#[test]
+fn submit_judgment_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        set_council_budget(500);
+
+        // Test already cancelled bounty.
+        CreateBountyFixture::default().call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(Error::<Test>::InvalidStageUnexpectedFunding.into()));
+    });
+}
+
+#[test]
+fn submit_judgment_fails_with_zero_work_entries() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+        let working_period = 10;
+        let judging_period = 10;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(working_period)
+            .with_judging_period(judging_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1;
+
+        let work_data = b"Work submitted".to_vec();
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .with_work_data(work_data.clone())
+            .call_and_assert(Ok(()));
+
+        WithdrawWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .call_and_assert(Ok(()));
+
+        run_to_block(starting_block + working_period + 1);
+
+        // Withdrawn entry.
+        let judgment = vec![entry_id]
+            .iter()
+            .map(|entry_id| {
+                (
+                    *entry_id,
+                    OracleWorkEntryJudgment::Winner {
+                        reward: DEFAULT_WINNER_REWARD,
+                    },
+                )
+            })
+            .collect::<BTreeMap<_, _>>();
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment)
+            .call_and_assert(Err(Error::<Test>::NoActiveWorkEntries.into()));
+    });
+}
+
+#[test]
+fn submit_judgment_fails_with_invalid_judgment() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+        let working_period = 10;
+        let judging_period = 10;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(working_period)
+            .with_judging_period(judging_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1;
+
+        let account_id2 = 2;
+        increase_account_balance(&account_id2, initial_balance);
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id2)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id2 = 2;
+
+        let work_data = b"Work submitted".to_vec();
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .with_work_data(work_data.clone())
+            .call_and_assert(Ok(()));
+
+        run_to_block(starting_block + working_period + 1);
+
+        // Invalid entry_id
+        let invalid_entry_id = 1111u64;
+        let judgment = vec![invalid_entry_id]
+            .iter()
+            .map(|entry_id| {
+                (
+                    *entry_id,
+                    OracleWorkEntryJudgment::Winner {
+                        reward: DEFAULT_WINNER_REWARD,
+                    },
+                )
+            })
+            .collect::<BTreeMap<_, _>>();
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment)
+            .call_and_assert(Err(Error::<Test>::WorkEntryDoesntExist.into()));
+
+        // Zero reward for winners.
+        let invalid_reward = 0;
+        let judgment = vec![entry_id]
+            .iter()
+            .map(|entry_id| {
+                (
+                    *entry_id,
+                    OracleWorkEntryJudgment::Winner {
+                        reward: invalid_reward,
+                    },
+                )
+            })
+            .collect::<BTreeMap<_, _>>();
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment)
+            .call_and_assert(Err(Error::<Test>::ZeroWinnerReward.into()));
+
+        // Winner reward is not equal to the total bounty funding.
+        let invalid_reward = max_amount * 2;
+        let judgment = vec![entry_id]
+            .iter()
+            .map(|entry_id| {
+                (
+                    *entry_id,
+                    OracleWorkEntryJudgment::Winner {
+                        reward: invalid_reward,
+                    },
+                )
+            })
+            .collect::<BTreeMap<_, _>>();
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment)
+            .call_and_assert(Err(
+                Error::<Test>::TotalRewardShouldBeEqualToTotalFunding.into()
+            ));
+
+        // No work submission for a winner.
+        let winner_reward = max_amount;
+        let judgment = vec![entry_id2]
+            .iter()
+            .map(|entry_id| {
+                (
+                    *entry_id,
+                    OracleWorkEntryJudgment::Winner {
+                        reward: winner_reward,
+                    },
+                )
+            })
+            .collect::<BTreeMap<_, _>>();
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment)
+            .call_and_assert(Err(Error::<Test>::WinnerShouldHasWorkSubmission.into()));
+    });
+}
+
+#[test]
+fn withdraw_work_entrant_funds_succeeded() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let winner_reward = max_amount;
+        let entrant_stake = 37;
+        let work_period = 1;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_work_period(work_period)
+            .with_entrant_stake(entrant_stake)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let member_id1 = 1;
+        let account_id1 = 1;
+        increase_account_balance(&account_id1, initial_balance);
+
+        // Winner
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_staking_account_id(account_id1)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id1),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id1 = 1;
+
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_entry_id(entry_id1)
+            .call_and_assert(Ok(()));
+
+        // Legitimate participant
+        let member_id2 = 2;
+        let account_id2 = 2;
+        increase_account_balance(&account_id2, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id2))
+            .with_member_id(member_id2)
+            .with_staking_account_id(account_id2)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id2),
+            initial_balance - entrant_stake
+        );
+
+        let entry_id2 = 2;
+
+        SubmitWorkFixture::default()
+            .with_origin(RawOrigin::Signed(account_id2))
+            .with_member_id(member_id2)
+            .with_entry_id(entry_id2)
+            .call_and_assert(Ok(()));
+
+        // Judgment
+        let mut judgment = BTreeMap::new();
+        judgment.insert(
+            entry_id1,
+            OracleWorkEntryJudgment::Winner {
+                reward: winner_reward,
+            },
+        );
+
+        run_to_block(starting_block + work_period + 1);
+
+        SubmitJudgmentFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_judgment(judgment)
+            .call_and_assert(Ok(()));
+
+        // Withdraw work entrant.
+        WithdrawWorkEntrantFundsFixture::default()
+            .with_origin(RawOrigin::Signed(account_id1))
+            .with_member_id(member_id1)
+            .with_entry_id(entry_id1)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(
+            Balances::usable_balance(&account_id1),
+            initial_balance + winner_reward
+        );
+
+        EventFixture::assert_last_crate_event(RawEvent::WorkEntrantFundsWithdrawn(
+            bounty_id, entry_id1, member_id1,
+        ));
+
+        // Bounty exists before the last withdrawal call.
+        assert!(<Bounties<Test>>::contains_key(bounty_id));
+
+        WithdrawWorkEntrantFundsFixture::default()
+            .with_origin(RawOrigin::Signed(account_id2))
+            .with_member_id(member_id2)
+            .with_entry_id(entry_id2)
+            .call_and_assert(Ok(()));
+
+        assert_eq!(Balances::usable_balance(&account_id2), initial_balance);
+
+        EventFixture::contains_crate_event(RawEvent::WorkEntrantFundsWithdrawn(
+            bounty_id, entry_id2, member_id2,
+        ));
+
+        // Bounty was removed with the last withdrawal call.
+        assert!(!<Bounties<Test>>::contains_key(bounty_id));
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyRemoved(bounty_id));
+    });
+}
+
+#[test]
+fn withdraw_work_entrant_funds_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        WithdrawWorkEntrantFundsFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn withdraw_work_entrant_funds_fails_with_invalid_entry_id() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        run_to_block(100);
+
+        let invalid_entry_id = 11u64;
+
+        WithdrawWorkEntrantFundsFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_entry_id(invalid_entry_id)
+            .call_and_assert(Err(Error::<Test>::WorkEntryDoesntExist.into()));
+    });
+}
+
+#[test]
+fn withdraw_work_entrant_funds_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1;
+
+        WithdrawWorkEntrantFundsFixture::default()
+            .with_entry_id(entry_id)
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn withdraw_work_entrant_funds_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let initial_balance = 500;
+        let max_amount = 100;
+        let entrant_stake = 37;
+        let work_period = 10;
+
+        set_council_budget(initial_balance);
+
+        CreateBountyFixture::default()
+            .with_max_funding_amount(max_amount)
+            .with_entrant_stake(entrant_stake)
+            .with_work_period(work_period)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1;
+        let member_id = 1;
+        let account_id = 1;
+
+        FundBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_amount(max_amount)
+            .with_council()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        increase_account_balance(&account_id, initial_balance);
+
+        AnnounceWorkEntryFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_staking_account_id(account_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        let entry_id = 1;
+
+        run_to_block(starting_block + 1);
+
+        WithdrawWorkEntrantFundsFixture::default()
+            .with_origin(RawOrigin::Signed(account_id))
+            .with_member_id(member_id)
+            .with_entry_id(entry_id)
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(
+                Error::<Test>::InvalidStageUnexpectedWorkSubmission.into()
+            ));
+    });
+}

+ 16 - 0
runtime-modules/common/src/council.rs

@@ -0,0 +1,16 @@
+use frame_support::dispatch::DispatchResult;
+
+/// Provides an interface for the council budget.
+pub trait CouncilBudgetManager<Balance> {
+    /// Returns the current council balance.
+    fn get_budget() -> Balance;
+
+    /// Set the current budget value.
+    fn set_budget(budget: Balance);
+}
+
+/// Council validator for the origin(account_id) and member_id.
+pub trait CouncilOriginValidator<Origin, MemberId, AccountId> {
+    /// Check for valid combination of origin and member_id for a councilor.
+    fn ensure_member_consulate(origin: Origin, member_id: MemberId) -> DispatchResult;
+}

+ 4 - 41
runtime-modules/common/src/lib.rs

@@ -2,52 +2,15 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 pub mod constraints;
-pub mod origin;
+pub mod council;
+pub mod membership;
 pub mod working_group;
 
-use codec::{Codec, Decode, Encode};
-use frame_support::Parameter;
+use codec::{Decode, Encode};
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
-use sp_arithmetic::traits::BaseArithmetic;
-use sp_runtime::traits::{MaybeSerialize, Member};
 
-/// Member id type alias
-pub type MemberId<T> = <T as Trait>::MemberId;
-
-/// Actor id type alias
-pub type ActorId<T> = <T as Trait>::ActorId;
-
-/// Generic trait for membership dependent pallets.
-pub trait Trait: frame_system::Trait {
-    /// Describes the common type for the members.
-    type MemberId: Parameter
-        + Member
-        + BaseArithmetic
-        + Codec
-        + Default
-        + Copy
-        + MaybeSerialize
-        + Ord
-        + Eq;
-
-    /// Describes the common type for the working group members (workers).
-    type ActorId: Parameter
-        + Member
-        + BaseArithmetic
-        + Codec
-        + Default
-        + Copy
-        + MaybeSerialize
-        + Ord
-        + PartialEq;
-}
-
-/// Validates staking account ownership for a member.
-pub trait StakingAccountValidator<T: Trait> {
-    /// Verifies that staking account bound to the member.
-    fn is_member_staking_account(member_id: &MemberId<T>, account_id: &T::AccountId) -> bool;
-}
+pub use membership::{ActorId, MemberId, StakingAccountValidator};
 
 /// Defines time in both block number and substrate time abstraction.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]

+ 60 - 0
runtime-modules/common/src/membership.rs

@@ -0,0 +1,60 @@
+use codec::Codec;
+use frame_support::dispatch::DispatchError;
+use frame_support::Parameter;
+use sp_arithmetic::traits::BaseArithmetic;
+use sp_runtime::traits::{MaybeSerialize, Member};
+
+/// Member id type alias
+pub type MemberId<T> = <T as Trait>::MemberId;
+
+/// Actor id type alias
+pub type ActorId<T> = <T as Trait>::ActorId;
+
+/// Generic trait for membership dependent pallets.
+pub trait Trait: frame_system::Trait {
+    /// Describes the common type for the members.
+    type MemberId: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + MaybeSerialize
+        + Ord
+        + PartialEq;
+
+    /// Describes the common type for the working group members (workers).
+    type ActorId: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + MaybeSerialize
+        + Ord
+        + PartialEq;
+}
+
+/// Validates staking account ownership for a member.
+pub trait StakingAccountValidator<T: Trait> {
+    /// Verifies that staking account bound to the member.
+    fn is_member_staking_account(member_id: &MemberId<T>, account_id: &T::AccountId) -> bool;
+}
+
+/// Membership validator for the origin(account_id) and member_id (eg.: thread author id).
+pub trait MemberOriginValidator<Origin, MemberId, AccountId> {
+    /// Check for valid combination of origin and member_id. Returns member controller account ID.
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        member_id: MemberId,
+    ) -> Result<AccountId, DispatchError>;
+
+    /// Verifies that provided account is the controller account of the member
+    fn is_member_controller_account(member_id: &MemberId, account_id: &AccountId) -> bool;
+}
+
+/// Gives access to some membership information.
+pub trait MembershipInfoProvider<T: Trait> {
+    /// Returns current controller account for a member.
+    fn controller_account_id(member_id: MemberId<T>) -> Result<T::AccountId, DispatchError>;
+}

+ 0 - 19
runtime-modules/common/src/origin.rs

@@ -1,19 +0,0 @@
-use frame_support::dispatch::{DispatchError, DispatchResult};
-
-/// Council validator for the origin(account_id) and member_id.
-pub trait CouncilOriginValidator<Origin, MemberId, AccountId> {
-    /// Check for valid combination of origin and member_id for a councilor.
-    fn ensure_member_consulate(origin: Origin, member_id: MemberId) -> DispatchResult;
-}
-
-/// Membership validator for the origin(account_id) and member_id (eg.: thread author id).
-pub trait MemberOriginValidator<Origin, MemberId, AccountId> {
-    /// Check for valid combination of origin and member_id.
-    fn ensure_member_controller_account_origin(
-        origin: Origin,
-        member_id: MemberId,
-    ) -> Result<AccountId, DispatchError>;
-
-    /// Verifies that provided account is the controller account of the member
-    fn is_member_controller_account(member_id: &MemberId, account_id: &AccountId) -> bool;
-}

+ 1 - 1
runtime-modules/common/src/working_group.rs

@@ -20,7 +20,7 @@ pub enum WorkingGroup {
 }
 
 /// Working group interface to work with its members - workers and leaders.
-pub trait WorkingGroupAuthenticator<T: crate::Trait> {
+pub trait WorkingGroupAuthenticator<T: crate::membership::Trait> {
     /// Validate origin for the worker.
     fn ensure_worker_origin(origin: T::Origin, worker_id: &T::ActorId) -> DispatchResult;
 

+ 1 - 1
runtime-modules/constitution/src/lib.rs

@@ -72,7 +72,7 @@ decl_module! {
         /// - Db writes: 1 (constant value)
         /// # </weight>
         #[weight = WeightInfoConstitution::<T>::amend_constitution(constitution_text.len().saturated_into())]
-        fn amend_constitution(origin, constitution_text: Vec<u8>) {
+        pub fn amend_constitution(origin, constitution_text: Vec<u8>) {
             ensure_root(origin)?;
 
             //

+ 9 - 5
runtime-modules/content-directory/src/lib.rs

@@ -158,7 +158,7 @@ use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
 use sp_std::vec;
 use sp_std::vec::Vec;
 
-use common::origin::MemberOriginValidator;
+use common::membership::MemberOriginValidator;
 
 pub use errors::Error;
 
@@ -170,7 +170,7 @@ pub type MaxNumber = u32;
 /// Type simplification
 pub type EntityOf<T> = Entity<
     <T as Trait>::ClassId,
-    <T as common::Trait>::MemberId,
+    <T as common::membership::Trait>::MemberId,
     <T as frame_system::Trait>::Hash,
     <T as Trait>::EntityId,
     <T as Trait>::Nonce,
@@ -191,7 +191,7 @@ pub type StoredPropertyValueOf<T> = StoredPropertyValue<
 pub type CuratorId<T> = common::ActorId<T>;
 
 /// Module configuration trait for this Substrate module.
-pub trait Trait: frame_system::Trait + common::Trait {
+pub trait Trait: frame_system::Trait + common::membership::Trait {
     /// The overarching event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
@@ -2958,10 +2958,14 @@ decl_event!(
         CuratorId = CuratorId<T>,
         ClassId = <T as Trait>::ClassId,
         EntityId = <T as Trait>::EntityId,
-        EntityController = EntityController<<T as common::Trait>::MemberId>,
+        EntityController = EntityController<<T as common::membership::Trait>::MemberId>,
         EntityCreationVoucher = EntityCreationVoucher<T>,
         Status = bool,
-        Actor = Actor<<T as Trait>::CuratorGroupId, CuratorId<T>, <T as common::Trait>::MemberId>,
+        Actor = Actor<
+            <T as Trait>::CuratorGroupId,
+            CuratorId<T>,
+            <T as common::membership::Trait>::MemberId,
+        >,
         Nonce = <T as Trait>::Nonce,
         SideEffects = Option<ReferenceCounterSideEffects<T>>,
         SideEffect = Option<(<T as Trait>::EntityId, EntityReferenceCounterSideEffect)>,

+ 6 - 6
runtime-modules/content-directory/src/mock.rs

@@ -24,7 +24,7 @@ pub type Hashed = <Runtime as frame_system::Trait>::Hash;
 
 pub type TestCuratorId = CuratorId<Runtime>;
 pub type CuratorGroupId = <Runtime as Trait>::CuratorGroupId;
-pub type MemberId = <Runtime as common::Trait>::MemberId;
+pub type MemberId = <Runtime as common::membership::Trait>::MemberId;
 
 /// Origins
 
@@ -275,7 +275,7 @@ impl Trait for Runtime {
 impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
     fn ensure_worker_origin(
         _origin: <Runtime as frame_system::Trait>::Origin,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
+        _worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!()
     }
@@ -284,7 +284,7 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Runtime as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Runtime as common::membership::Trait>::MemberId> {
         unimplemented!()
     }
 
@@ -296,7 +296,7 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
 
     fn is_worker_account_id(
         account_id: &<Runtime as frame_system::Trait>::AccountId,
-        worker_id: &<Runtime as common::Trait>::ActorId,
+        worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> bool {
         let first_curator_account_id = ensure_signed(Origin::signed(FIRST_CURATOR_ORIGIN)).unwrap();
         let second_curator_account_id =
@@ -306,12 +306,12 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
     }
 }
 
-impl common::Trait for Runtime {
+impl common::membership::Trait for Runtime {
     type MemberId = u64;
     type ActorId = u64;
 }
 
-impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_controller_account_origin(
         _origin: Origin,
         _member_id: u64,

+ 24 - 20
runtime-modules/content-directory/src/schema/property.rs

@@ -471,31 +471,35 @@ impl<ClassId: Default + BaseArithmetic + Clone + Copy> Property<ClassId> {
             (
                 InputPropertyValue::Single(single_property_value),
                 PropertyType::Single(ref single_property_type),
-            ) => matches!((single_property_value, single_property_type.deref()),
+            ) => matches!(
+                (single_property_value, single_property_type.deref()),
                 (InputValue::Bool(_), Type::Bool)
-                | (InputValue::Uint16(_), Type::Uint16)
-                | (InputValue::Uint32(_), Type::Uint32)
-                | (InputValue::Uint64(_), Type::Uint64)
-                | (InputValue::Int16(_), Type::Int16)
-                | (InputValue::Int32(_), Type::Int32)
-                | (InputValue::Int64(_), Type::Int64)
-                | (InputValue::Text(_), Type::Text(_))
-                | (InputValue::TextToHash(_), Type::Hash(_))
-                | (InputValue::Reference(_), Type::Reference(_, _))),
+                    | (InputValue::Uint16(_), Type::Uint16)
+                    | (InputValue::Uint32(_), Type::Uint32)
+                    | (InputValue::Uint64(_), Type::Uint64)
+                    | (InputValue::Int16(_), Type::Int16)
+                    | (InputValue::Int32(_), Type::Int32)
+                    | (InputValue::Int64(_), Type::Int64)
+                    | (InputValue::Text(_), Type::Text(_))
+                    | (InputValue::TextToHash(_), Type::Hash(_))
+                    | (InputValue::Reference(_), Type::Reference(_, _))
+            ),
             (
                 InputPropertyValue::Vector(vec_value),
                 PropertyType::Vector(ref vec_property_type),
-            ) => matches!((vec_value, vec_property_type.get_vec_type()),
+            ) => matches!(
+                (vec_value, vec_property_type.get_vec_type()),
                 (VecInputValue::Bool(_), Type::Bool)
-                | (VecInputValue::Uint16(_), Type::Uint16)
-                | (VecInputValue::Uint32(_), Type::Uint32)
-                | (VecInputValue::Uint64(_), Type::Uint64)
-                | (VecInputValue::Int16(_), Type::Int16)
-                | (VecInputValue::Int32(_), Type::Int32)
-                | (VecInputValue::Int64(_), Type::Int64)
-                | (VecInputValue::Text(_), Type::Text(_))
-                | (VecInputValue::TextToHash(_), Type::Hash(_))
-                | (VecInputValue::Reference(_), Type::Reference(_, _))),
+                    | (VecInputValue::Uint16(_), Type::Uint16)
+                    | (VecInputValue::Uint32(_), Type::Uint32)
+                    | (VecInputValue::Uint64(_), Type::Uint64)
+                    | (VecInputValue::Int16(_), Type::Int16)
+                    | (VecInputValue::Int32(_), Type::Int32)
+                    | (VecInputValue::Int64(_), Type::Int64)
+                    | (VecInputValue::Text(_), Type::Text(_))
+                    | (VecInputValue::TextToHash(_), Type::Hash(_))
+                    | (VecInputValue::Reference(_), Type::Reference(_, _))
+            ),
             _ => false,
         }
     }

+ 6 - 2
runtime-modules/council/src/benchmarking.rs

@@ -318,12 +318,13 @@ benchmarks! {
         Council::<T>::set_budget(RawOrigin::Root.into(), Balance::<T>::max_value()).unwrap();
         assert_eq!(Council::<T>::budget(), Balance::<T>::max_value());
         let mut funding_requests = Vec::new();
+        let amount: Balance<T> = 100.into();
 
         for id in 0 .. i {
             let account = T::AccountId::create_account_id(id);
             assert_eq!(Balances::<T>::total_balance(&account), Zero::zero());
             funding_requests.push(common::FundingRequestParameters {
-                amount: One::one(),
+                amount,
                 account
             });
         }
@@ -332,7 +333,10 @@ benchmarks! {
 
     }: _(RawOrigin::Root, funding_requests.clone())
     verify {
-        assert_eq!(Council::<T>::budget(), Balance::<T>::max_value() - Balance::<T>::from(i));
+        assert_eq!(
+            Council::<T>::budget(),
+            Balance::<T>::max_value() - Balance::<T>::from(i) * amount
+        );
 
         for fund_request in funding_requests {
 

+ 26 - 12
runtime-modules/council/src/lib.rs

@@ -55,7 +55,8 @@ use serde::{Deserialize, Serialize};
 use sp_runtime::traits::{Hash, SaturatedConversion, Saturating, Zero};
 use sp_std::vec::Vec;
 
-use common::origin::{CouncilOriginValidator, MemberOriginValidator};
+use common::council::CouncilOriginValidator;
+use common::membership::MemberOriginValidator;
 use common::{FundingRequestParameters, StakingAccountValidator};
 use referendum::{CastVote, OptionResult, ReferendumManager};
 use staking_handler::StakingHandler;
@@ -175,15 +176,18 @@ pub type Balance<T> = <T as balances::Trait>::Balance;
 pub type VotePowerOf<T> = <<T as Trait>::Referendum as ReferendumManager<
     <T as frame_system::Trait>::Origin,
     <T as frame_system::Trait>::AccountId,
-    <T as common::Trait>::MemberId,
+    <T as common::membership::Trait>::MemberId,
     <T as frame_system::Trait>::Hash,
 >>::VotePower;
-pub type CastVoteOf<T> =
-    CastVote<<T as frame_system::Trait>::Hash, Balance<T>, <T as common::Trait>::MemberId>;
+pub type CastVoteOf<T> = CastVote<
+    <T as frame_system::Trait>::Hash,
+    Balance<T>,
+    <T as common::membership::Trait>::MemberId,
+>;
 
 pub type CouncilMemberOf<T> = CouncilMember<
     <T as frame_system::Trait>::AccountId,
-    <T as common::Trait>::MemberId,
+    <T as common::membership::Trait>::MemberId,
     Balance<T>,
     <T as frame_system::Trait>::BlockNumber,
 >;
@@ -218,7 +222,7 @@ pub trait WeightInfo {
 type CouncilWeightInfo<T> = <T as Trait>::WeightInfo;
 
 /// The main council trait.
-pub trait Trait: frame_system::Trait + common::Trait + balances::Trait {
+pub trait Trait: frame_system::Trait + common::membership::Trait + balances::Trait {
     /// The overarching event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
@@ -272,7 +276,7 @@ pub trait ReferendumConnection<T: Trait> {
     /// Process referendum results. This function MUST be called in runtime's implementation of
     /// referendum's `process_results()`.
     fn recieve_referendum_results(
-        winners: &[OptionResult<<T as common::Trait>::MemberId, VotePowerOf<T>>],
+        winners: &[OptionResult<<T as common::membership::Trait>::MemberId, VotePowerOf<T>>],
     );
 
     /// Process referendum results. This function MUST be called in runtime's implementation of
@@ -328,7 +332,7 @@ decl_event! {
     where
         Balance = Balance<T>,
         <T as frame_system::Trait>::BlockNumber,
-        <T as common::Trait>::MemberId,
+        <T as common::membership::Trait>::MemberId,
         <T as frame_system::Trait>::AccountId,
     {
         /// New council was elected
@@ -888,7 +892,7 @@ impl<T: Trait> Module<T> {
 
     // Conclude election period and elect new council if possible.
     fn end_election_period(
-        winners: &[OptionResult<<T as common::Trait>::MemberId, VotePowerOf<T>>],
+        winners: &[OptionResult<<T as common::membership::Trait>::MemberId, VotePowerOf<T>>],
     ) {
         let council_size = T::CouncilSize::get();
         if winners.len() as u64 != council_size {
@@ -1066,7 +1070,7 @@ impl<T: Trait> Module<T> {
 impl<T: Trait> ReferendumConnection<T> for Module<T> {
     // Process candidates' results recieved from the referendum.
     fn recieve_referendum_results(
-        winners: &[OptionResult<<T as common::Trait>::MemberId, VotePowerOf<T>>],
+        winners: &[OptionResult<<T as common::membership::Trait>::MemberId, VotePowerOf<T>>],
     ) {
         //
         // == MUTATION SAFE ==
@@ -1566,8 +1570,8 @@ impl<T: Trait> EnsureChecks<T> {
     }
 }
 
-impl<T: Trait + common::Trait> CouncilOriginValidator<T::Origin, T::MemberId, T::AccountId>
-    for Module<T>
+impl<T: Trait + common::membership::Trait>
+    CouncilOriginValidator<T::Origin, T::MemberId, T::AccountId> for Module<T>
 {
     fn ensure_member_consulate(origin: T::Origin, member_id: T::MemberId) -> DispatchResult {
         EnsureChecks::<T>::ensure_user_membership(origin, &member_id)?;
@@ -1581,3 +1585,13 @@ impl<T: Trait + common::Trait> CouncilOriginValidator<T::Origin, T::MemberId, T:
         Ok(())
     }
 }
+
+impl<T: Trait + balances::Trait> common::council::CouncilBudgetManager<Balance<T>> for Module<T> {
+    fn get_budget() -> Balance<T> {
+        Self::budget()
+    }
+
+    fn set_budget(budget: Balance<T>) {
+        Mutations::<T>::set_budget(budget);
+    }
+}

+ 9 - 7
runtime-modules/council/src/mock.rs

@@ -69,7 +69,7 @@ parameter_types! {
     pub const BudgetRefillPeriod: u64 = 1000;
 }
 
-impl common::Trait for Runtime {
+impl common::membership::Trait for Runtime {
     type MemberId = u64;
     type ActorId = u64;
 }
@@ -107,7 +107,7 @@ impl Trait for Runtime {
     type MemberOriginValidator = ();
 }
 
-impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         member_id: u64,
@@ -264,6 +264,7 @@ parameter_types! {
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
     pub const CandidateStake: u64 = 100;
     pub const MaxWinnerTargetCount: u64 = 10;
+    pub const ReferralCutMaximumPercent: u8 = 50;
 }
 
 mod balances_mod {
@@ -445,6 +446,7 @@ impl membership::Trait for Runtime {
     type WeightInfo = Weights;
     type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -463,12 +465,12 @@ impl common::working_group::WorkingGroupBudgetHandler<Runtime> for () {
 impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
     fn ensure_worker_origin(
         _origin: <Runtime as frame_system::Trait>::Origin,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
+        _worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!();
     }
 
-    fn get_leader_member_id() -> Option<<Runtime as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Runtime as common::membership::Trait>::MemberId> {
         unimplemented!();
     }
 
@@ -482,7 +484,7 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
 
     fn is_worker_account_id(
         _account_id: &<Runtime as frame_system::Trait>::AccountId,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
+        _worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> bool {
         unimplemented!()
     }
@@ -504,7 +506,7 @@ impl Runtime {
 }
 
 parameter_types! {
-    pub const ExistentialDeposit: u64 = 0;
+    pub const ExistentialDeposit: u64 = 10;
     pub const MaxLocks: u32 = 50;
 }
 
@@ -775,7 +777,7 @@ where
 
     pub fn vote_commitment(
         account_id: &<T as frame_system::Trait>::AccountId,
-        vote_option_index: &<T as common::Trait>::MemberId,
+        vote_option_index: &<T as common::membership::Trait>::MemberId,
         cycle_id: &u64,
     ) -> (T::Hash, Vec<u8>) {
         let salt = Self::generate_salt();

+ 27 - 2
runtime-modules/council/src/tests.rs

@@ -5,7 +5,8 @@ use super::{
     CouncilStageAnnouncing, Error, Module, Trait,
 };
 use crate::mock::*;
-use common::origin::CouncilOriginValidator;
+use common::council::CouncilBudgetManager;
+use common::council::CouncilOriginValidator;
 use frame_support::traits::Currency;
 use frame_support::StorageValue;
 use frame_system::RawOrigin;
@@ -1717,7 +1718,7 @@ fn test_funding_request_succeeds() {
             origin,
             vec![
                 common::FundingRequestParameters {
-                    amount: 5,
+                    amount: 15,
                     account: 0,
                 },
                 common::FundingRequestParameters {
@@ -1729,3 +1730,27 @@ fn test_funding_request_succeeds() {
         );
     });
 }
+
+#[test]
+fn test_council_budget_manager_works_correctlyl() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let origin = OriginType::Root;
+        let initial_budget = 100;
+
+        Mocks::set_budget(origin.clone(), initial_budget, Ok(()));
+
+        assert_eq!(
+            <Module<Runtime> as CouncilBudgetManager<u64>>::get_budget(),
+            initial_budget
+        );
+
+        let new_budget = 200;
+        <Module<Runtime> as CouncilBudgetManager<u64>>::set_budget(new_budget);
+        assert_eq!(
+            <Module<Runtime> as CouncilBudgetManager<u64>>::get_budget(),
+            new_budget
+        );
+    });
+}

+ 223 - 268
runtime-modules/forum/src/benchmarking.rs

@@ -2,7 +2,7 @@
 use super::*;
 use balances::Module as Balances;
 use core::convert::TryInto;
-use frame_benchmarking::{account, benchmarks, Zero};
+use frame_benchmarking::{account, benchmarks};
 use frame_support::storage::StorageMap;
 use frame_support::traits::Currency;
 use frame_system::Module as System;
@@ -49,6 +49,8 @@ pub type BalanceOf<T> = <T as balances::Trait>::Balance;
 
 const SEED: u32 = 0;
 const MAX_BYTES: u32 = 16384;
+const MAX_POSTS: u32 = 500;
+const MAX_THREADS: u32 = 500;
 
 fn get_byte(num: u32, byte_number: u8) -> u8 {
     ((num & (0xff << (8 * byte_number))) >> 8 * byte_number) as u8
@@ -119,10 +121,44 @@ fn handle_from_id<T: membership::Trait>(id: u32) -> Vec<u8> {
     handle
 }
 
+fn insert_a_leader<
+    T: Trait + membership::Trait + working_group::Trait<ForumWorkingGroupInstance> + balances::Trait,
+>(
+    id: u64,
+) -> T::AccountId
+where
+    T::AccountId: CreateAccountId,
+{
+    let (caller_id, member_id) = member_funded_account::<T>(id as u32);
+
+    let (opening_id, application_id) = add_and_apply_opening::<T>(
+        &T::Origin::from(RawOrigin::Root),
+        &caller_id,
+        &member_id,
+        &OpeningType::Leader,
+    );
+
+    let mut successful_application_ids = BTreeSet::<ApplicationId>::new();
+    successful_application_ids.insert(application_id);
+    ForumGroup::<T>::fill_opening(
+        RawOrigin::Root.into(),
+        opening_id,
+        successful_application_ids,
+    )
+    .unwrap();
+
+    let actor_id = <T as common::membership::Trait>::ActorId::from(id.try_into().unwrap());
+    assert!(WorkerById::<T, ForumWorkingGroupInstance>::contains_key(
+        actor_id
+    ));
+
+    caller_id
+}
+
 fn insert_a_worker<
     T: Trait + membership::Trait + working_group::Trait<ForumWorkingGroupInstance> + balances::Trait,
 >(
-    job_opening_type: OpeningType,
+    leader_account_id: T::AccountId,
     id: u64,
 ) -> T::AccountId
 where
@@ -130,28 +166,25 @@ where
 {
     let (caller_id, member_id) = member_funded_account::<T>(id as u32);
 
-    let add_worker_origin = match job_opening_type {
-        OpeningType::Leader => RawOrigin::Root,
-        OpeningType::Regular => RawOrigin::Signed(caller_id.clone()),
-    };
+    let leader_origin = RawOrigin::Signed(leader_account_id);
 
     let (opening_id, application_id) = add_and_apply_opening::<T>(
-        &T::Origin::from(add_worker_origin.clone()),
+        &T::Origin::from(leader_origin.clone()),
         &caller_id,
         &member_id,
-        &job_opening_type,
+        &OpeningType::Regular,
     );
 
     let mut successful_application_ids = BTreeSet::<ApplicationId>::new();
     successful_application_ids.insert(application_id);
     ForumGroup::<T>::fill_opening(
-        add_worker_origin.clone().into(),
+        leader_origin.clone().into(),
         opening_id,
         successful_application_ids,
     )
     .unwrap();
 
-    let actor_id = <T as common::Trait>::ActorId::from(id.try_into().unwrap());
+    let actor_id = <T as common::membership::Trait>::ActorId::from(id.try_into().unwrap());
     assert!(WorkerById::<T, ForumWorkingGroupInstance>::contains_key(
         actor_id
     ));
@@ -161,13 +194,14 @@ where
 
 fn add_and_apply_opening<T: Trait + working_group::Trait<ForumWorkingGroupInstance>>(
     add_opening_origin: &T::Origin,
-    applicant_id: &T::AccountId,
-    member_id: &T::MemberId,
+    applicant_account_id: &T::AccountId,
+    applicant_member_id: &T::MemberId,
     job_opening_type: &OpeningType,
 ) -> (OpeningId, ApplicationId) {
     let opening_id = add_opening_helper::<T>(add_opening_origin, job_opening_type);
 
-    let application_id = apply_on_opening_helper::<T>(applicant_id, member_id, &opening_id);
+    let application_id =
+        apply_on_opening_helper::<T>(applicant_account_id, applicant_member_id, &opening_id);
 
     (opening_id, application_id)
 }
@@ -182,7 +216,7 @@ fn add_opening_helper<T: Trait + working_group::Trait<ForumWorkingGroupInstance>
         *job_opening_type,
         StakePolicy {
             stake_amount:
-                <T as working_group::Trait<ForumWorkingGroupInstance>>::MinimumStakeForOpening::get(
+                <T as working_group::Trait<ForumWorkingGroupInstance>>::MinimumApplicationStake::get(
                 ),
             leaving_unstaking_period: <T as
                 working_group::Trait<ForumWorkingGroupInstance>>::MinUnstakingPeriodLimit::get() + One::one(),
@@ -202,21 +236,21 @@ fn add_opening_helper<T: Trait + working_group::Trait<ForumWorkingGroupInstance>
 }
 
 fn apply_on_opening_helper<T: Trait + working_group::Trait<ForumWorkingGroupInstance>>(
-    applicant_id: &T::AccountId,
-    member_id: &T::MemberId,
+    applicant_account_id: &T::AccountId,
+    applicant_member_id: &T::MemberId,
     opening_id: &OpeningId,
 ) -> ApplicationId {
     ForumGroup::<T>::apply_on_opening(
-        RawOrigin::Signed(applicant_id.clone()).into(),
+        RawOrigin::Signed((*applicant_account_id).clone()).into(),
         ApplyOnOpeningParameters::<T> {
-            member_id: *member_id,
+            member_id: *applicant_member_id,
             opening_id: *opening_id,
-            role_account_id: applicant_id.clone(),
-            reward_account_id: applicant_id.clone(),
+            role_account_id: applicant_account_id.clone(),
+            reward_account_id: applicant_account_id.clone(),
             description: vec![],
             stake_parameters: StakeParameters {
-                stake: <T as working_group::Trait<ForumWorkingGroupInstance>>::MinimumStakeForOpening::get(),
-                staking_account_id: applicant_id.clone()
+                stake: <T as working_group::Trait<ForumWorkingGroupInstance>>::MinimumApplicationStake::get(),
+                staking_account_id: applicant_account_id.clone()
             },
         },
     )
@@ -285,6 +319,7 @@ fn add_thread_post<T: Trait>(
         category_id,
         thread_id,
         text,
+        true,
     )
     .unwrap();
     Module::<T>::next_post_id() - T::PostId::one()
@@ -382,7 +417,7 @@ benchmarks! {
         let lead_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, lead_id);
+            insert_a_leader::<T>(lead_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -443,7 +478,7 @@ benchmarks! {
         let moderator_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, moderator_id);
+            insert_a_leader::<T>(moderator_id);
 
         let text = vec![0u8].repeat(MAX_BYTES as usize);
 
@@ -484,7 +519,7 @@ benchmarks! {
         let moderator_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, moderator_id);
+            insert_a_leader::<T>(moderator_id);
 
         let text = vec![0u8].repeat(MAX_BYTES as usize);
 
@@ -529,7 +564,7 @@ benchmarks! {
         let lead_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, lead_id);
+            insert_a_leader::<T>(lead_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -568,7 +603,7 @@ benchmarks! {
         let moderator_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, moderator_id);
+            insert_a_leader::<T>(moderator_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -614,7 +649,7 @@ benchmarks! {
         let lead_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, lead_id);
+            insert_a_leader::<T>(lead_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -658,7 +693,7 @@ benchmarks! {
         let lead_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, lead_id);
+            insert_a_leader::<T>(lead_id);
 
         let i in 3 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -702,7 +737,7 @@ benchmarks! {
     create_thread {
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -749,19 +784,19 @@ benchmarks! {
         let new_post = Post {
             text_hash: T::calculate_hash(&text),
             author_id: forum_user_id.saturated_into(),
+            thread_id: next_thread_id,
+            last_edited: System::<T>::block_number(),
+            cleanup_pay_off: T::PostDeposit::get(),
         };
 
-        let mut posts = BTreeMap::new();
-        posts.insert(next_post_id, new_post.clone());
         // Ensure new thread created successfully
         let new_thread = Thread {
             category_id,
             title_hash: T::calculate_hash(&title),
             author_id: forum_user_id.saturated_into(),
-            archived: false,
             poll: poll.clone(),
-            cleanup_pay_off: T::ThreadDeposit::get() + T::PostDeposit::get(),
-            posts
+            cleanup_pay_off: T::ThreadDeposit::get(),
+            number_of_posts: 1,
         };
 
         assert_eq!(Module::<T>::thread_by_id(category_id, next_thread_id), new_thread);
@@ -769,10 +804,10 @@ benchmarks! {
 
 
         assert_eq!(
-            *Module::<T>::thread_by_id(category_id, next_thread_id)
-                .posts.get(&next_post_id).unwrap(),
+            Module::<T>::post_by_id(next_thread_id, next_post_id),
             new_post
         );
+
         assert_eq!(Module::<T>::next_post_id(), next_post_id + T::PostId::one());
 
         assert_last_event::<T>(
@@ -791,7 +826,7 @@ benchmarks! {
         let forum_user_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -824,87 +859,13 @@ benchmarks! {
         );
     }
 
-    update_thread_archival_status_lead {
-        let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
-
-        let forum_user_id = 0;
-        let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
-        let text = vec![1u8].repeat(MAX_BYTES as usize);
-
-        // Generate categories tree
-        let (category_id, _) = generate_categories_tree::<T>(caller_id.clone(), i, None);
-
-        // Create thread
-        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, text.clone(), text.clone(), None);
-        let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
-        let new_archival_status = true;
-
-    }: update_thread_archival_status(RawOrigin::Signed(caller_id), PrivilegedActor::Lead, category_id, thread_id, new_archival_status)
-    verify {
-        thread.archived = new_archival_status;
-
-        assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
-
-        assert_last_event::<T>(
-            RawEvent::ThreadUpdated(
-                thread_id,
-                new_archival_status,
-                PrivilegedActor::Lead,
-                category_id
-            ).into()
-        );
-    }
-
-    update_thread_archival_status_moderator {
-        let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
-
-        let forum_user_id = 0;
-        let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
-        let text = vec![1u8].repeat(MAX_BYTES as usize);
-
-        // Generate categories tree
-        let (category_id, _) = generate_categories_tree::<T>(caller_id.clone(), i, None);
-
-        // Create thread
-        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, text.clone(), text.clone(), None);
-        let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
-        let new_archival_status = true;
-
-        let moderator_id = ModeratorId::<T>::from(forum_user_id.try_into().unwrap());
-
-        // Set up category membership of moderator.
-        Module::<T>::update_category_membership_of_moderator(
-            RawOrigin::Signed(caller_id.clone()).into(),
-            moderator_id,
-            category_id,
-            true,
-        )
-        .unwrap();
-
-    }: update_thread_archival_status(RawOrigin::Signed(caller_id), PrivilegedActor::Moderator(moderator_id), category_id, thread_id, new_archival_status)
-    verify {
-        thread.archived = new_archival_status;
-
-        assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
-
-        assert_last_event::<T>(
-            RawEvent::ThreadUpdated(
-                thread_id,
-                new_archival_status,
-                PrivilegedActor::Moderator(moderator_id),
-                category_id
-            ).into()
-        );
-    }
-
-    delete_thread_lead {
+    delete_thread {
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
+        let hide = false;
 
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         // Generate categories tree
         let (category_id, _) = generate_categories_tree::<T>(caller_id.clone(), i, None);
@@ -921,93 +882,38 @@ benchmarks! {
             text.clone(), text.clone(), poll
         );
 
-        let mut category = Module::<T>::category_by_id(category_id);
-        let max_posts_in_thread =
-            <<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get();
-
-        for _ in 0..max_posts_in_thread - 1{
-            add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
-        }
-
-        let initial_balance = Balances::<T>::usable_balance(&caller_id);
-    }: delete_thread(RawOrigin::Signed(caller_id.clone()), PrivilegedActor::Lead, category_id, thread_id)
-    verify {
-
-        // Ensure that balance is paid off
-        assert_eq!(
-            Balances::<T>::usable_balance(&caller_id),
-            initial_balance +
-            T::ThreadDeposit::get() +
-            BalanceOf::<T>::from(
-                max_posts_in_thread.try_into().unwrap()
-            ) * T::PostDeposit::get()
-        );
-
-        // Ensure category num_direct_threads updated successfully.
-        category.num_direct_threads-=1;
-        assert_eq!(Module::<T>::category_by_id(category_id), category);
-
-        // Ensure thread was successfully deleted
-        assert!(!<ThreadById<T>>::contains_key(category_id, thread_id));
+        // Add poll voting.
+        for idx in 1..(T::MaxWorkerNumberLimit::get() - 1) {
+            let member_id = idx.into();
+            let member_account_id = insert_a_worker::<T>(caller_id.clone(), member_id);
 
-        assert_last_event::<T>(
-            RawEvent::ThreadDeleted(
+            let alternative_idx = 1;
+            Module::<T>::vote_on_poll(
+                RawOrigin::Signed(member_account_id.clone()).into(),
+                member_id.saturated_into(),
+                category_id,
                 thread_id,
-                PrivilegedActor::Lead,
-                category_id
-            ).into()
-        );
-    }
-
-    delete_thread_moderator {
-        let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
-
-        let forum_user_id = 0;
-        let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
-
-        // Generate categories tree
-        let (category_id, _) = generate_categories_tree::<T>(caller_id.clone(), i, None);
-
-        // Create thread
-        let expiration_diff = 10.into();
-        let poll = Some(
-            generate_poll::<T>(expiration_diff, (<<<T as Trait>::MapLimits as StorageLimits>::MaxPollAlternativesNumber>::get() - 1) as u32)
-        );
-        let text = vec![1u8].repeat(MAX_BYTES as usize);
-
-        let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.saturated_into(), category_id,
-            text.clone(), text.clone(), poll
-        );
-
-        let moderator_id = ModeratorId::<T>::from(forum_user_id.try_into().unwrap());
-
-        // Set up category membership of moderator.
-        Module::<T>::update_category_membership_of_moderator(
-            RawOrigin::Signed(caller_id.clone()).into(), moderator_id, category_id, true
-        ).unwrap();
+                alternative_idx
+            ).unwrap();
+        }
 
         let mut category = Module::<T>::category_by_id(category_id);
 
-        let max_posts_in_thread =
-            <<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get();
-
-        for _ in 0..max_posts_in_thread - 1 {
-            add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
-        }
-
         let initial_balance = Balances::<T>::usable_balance(&caller_id);
-
-    }: delete_thread(RawOrigin::Signed(caller_id.clone()), PrivilegedActor::Moderator(moderator_id), category_id, thread_id)
+    }: _(
+        RawOrigin::Signed(caller_id.clone()),
+        forum_user_id.saturated_into(),
+        category_id,
+        thread_id,
+        hide
+    )
     verify {
+
         // Ensure that balance is paid off
         assert_eq!(
             Balances::<T>::usable_balance(&caller_id),
             initial_balance +
-            T::ThreadDeposit::get() +
-            BalanceOf::<T>::from(max_posts_in_thread.try_into().unwrap()) *
-            T::PostDeposit::get()
+            T::ThreadDeposit::get()
         );
 
         // Ensure category num_direct_threads updated successfully.
@@ -1016,12 +922,14 @@ benchmarks! {
 
         // Ensure thread was successfully deleted
         assert!(!<ThreadById<T>>::contains_key(category_id, thread_id));
+        assert_eq!(<PollVotes<T>>::iter_prefix_values(&thread_id).count(), 0);
 
         assert_last_event::<T>(
             RawEvent::ThreadDeleted(
                 thread_id,
-                PrivilegedActor::Moderator(moderator_id),
-                category_id
+                forum_user_id.saturated_into(),
+                category_id,
+                hide
             ).into()
         );
     }
@@ -1033,7 +941,7 @@ benchmarks! {
         let text = vec![1u8].repeat(MAX_BYTES as usize);
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         // If category depth is less or equal to one, create two separate categories
         let (category_id, new_category_id) = if i <= 2 {
@@ -1095,7 +1003,7 @@ benchmarks! {
         let text = vec![1u8].repeat(MAX_BYTES as usize);
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         // If category depth is less or equal to one, create two separate categories
         let (category_id, new_category_id) = if i <= 2 {
@@ -1166,7 +1074,7 @@ benchmarks! {
 
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -1207,6 +1115,7 @@ benchmarks! {
         }
 
         assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
+        assert!(<PollVotes<T>>::get(thread_id, forum_user_id.saturated_into::<ForumUserId<T>>()));
 
         assert_last_event::<T>(
             RawEvent::VoteOnPoll(
@@ -1222,12 +1131,10 @@ benchmarks! {
         let lead_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, lead_id);
+            insert_a_leader::<T>(lead_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
-        let j in 0 .. <<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get() as u32 - 1;
-
         let k in 0 .. MAX_BYTES;
 
         // Generate categories tree
@@ -1247,9 +1154,6 @@ benchmarks! {
 
         let mut category = Module::<T>::category_by_id(category_id);
 
-        for _ in 0..j {
-            add_thread_post::<T>(caller_id.clone(), (lead_id as u64).saturated_into(), category_id, thread_id, text.clone());
-        }
         let rationale = vec![0u8].repeat(k as usize);
 
     }: moderate_thread(RawOrigin::Signed(caller_id), PrivilegedActor::Lead, category_id, thread_id, rationale.clone())
@@ -1258,7 +1162,7 @@ benchmarks! {
         let thread_account_id = T::ModuleId::get().into_sub_account(thread_id);
         assert_eq!(
            Balances::<T>::free_balance(&thread_account_id),
-           BalanceOf::<T>::zero()
+           T::PostDeposit::get()
         );
 
         // Ensure category num_direct_threads updated successfully.
@@ -1282,11 +1186,10 @@ benchmarks! {
         let lead_id = 0;
 
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, lead_id);
+            insert_a_leader::<T>(lead_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
-        let j in 0 .. <<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get() as u32 - 1;
 
         let k in 0 .. MAX_BYTES;
 
@@ -1314,9 +1217,6 @@ benchmarks! {
 
         let mut category = Module::<T>::category_by_id(category_id);
 
-        for _ in 0..j {
-            add_thread_post::<T>(caller_id.clone(), (lead_id as u64).saturated_into(), category_id, thread_id, text.clone());
-        }
         let rationale = vec![0u8].repeat(k as usize);
 
     }: moderate_thread(RawOrigin::Signed(caller_id), PrivilegedActor::Moderator(moderator_id), category_id, thread_id, rationale.clone())
@@ -1325,7 +1225,7 @@ benchmarks! {
         let thread_account_id = T::ModuleId::get().into_sub_account(thread_id);
         assert_eq!(
            Balances::<T>::free_balance(&thread_account_id),
-           BalanceOf::<T>::zero()
+           T::PostDeposit::get()
         );
 
 
@@ -1350,15 +1250,12 @@ benchmarks! {
 
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
         let j in 0 .. MAX_BYTES;
 
-        let k in 0 ..
-            (<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread::get() - 2) as u32;
-
         let text = vec![0u8].repeat(j as usize);
 
         // Generate categories tree
@@ -1370,21 +1267,11 @@ benchmarks! {
             vec![0u8].repeat(MAX_BYTES as usize), vec![0u8].repeat(MAX_BYTES as usize), None
         );
 
-        for _ in 0 .. k {
-            add_thread_post::<T>(
-                caller_id.clone(),
-                forum_user_id.saturated_into(),
-                category_id,
-                thread_id,
-                vec![0u8],
-            );
-        }
-
-        let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
+        let thread = Module::<T>::thread_by_id(category_id, thread_id);
         let post_id = Module::<T>::next_post_id();
 
         let initial_balance = Balances::<T>::usable_balance(&caller_id);
-    }: _ (RawOrigin::Signed(caller_id.clone()), forum_user_id.saturated_into(), category_id, thread_id, text.clone())
+    }: _ (RawOrigin::Signed(caller_id.clone()), forum_user_id.saturated_into(), category_id, thread_id, text.clone(), true)
     verify {
         assert_eq!(
             Balances::<T>::usable_balance(&caller_id),
@@ -1395,12 +1282,12 @@ benchmarks! {
         let new_post = Post {
             text_hash: T::calculate_hash(&text),
             author_id: forum_user_id.saturated_into(),
+            thread_id: thread_id,
+            last_edited: System::<T>::block_number(),
+            cleanup_pay_off: T::PostDeposit::get(),
         };
 
-        thread.posts.insert(post_id, new_post);
-        thread.cleanup_pay_off += T::PostDeposit::get();
-
-        assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
+        assert_eq!(Module::<T>::post_by_id(thread_id, post_id), new_post);
 
         assert_eq!(Module::<T>::next_post_id(), post_id + T::PostId::one());
 
@@ -1410,7 +1297,8 @@ benchmarks! {
                 forum_user_id.saturated_into(),
                 category_id,
                 thread_id,
-                text
+                text,
+                true,
             ).into()
         );
     }
@@ -1419,7 +1307,7 @@ benchmarks! {
 
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -1458,7 +1346,7 @@ benchmarks! {
     edit_post_text {
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -1481,8 +1369,7 @@ benchmarks! {
 
         let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
 
-        let mut post = Module::<T>::thread_by_id(category_id, thread_id)
-            .posts.get(&post_id).unwrap().clone();
+        let mut post = Module::<T>::post_by_id(thread_id, post_id);
 
         let new_text = vec![0u8].repeat(j as usize);
 
@@ -1491,9 +1378,10 @@ benchmarks! {
 
         // Ensure post text updated successfully.
         post.text_hash = T::calculate_hash(&new_text);
+        post.last_edited = System::<T>::block_number();
 
         assert_eq!(
-            *Module::<T>::thread_by_id(category_id, thread_id).posts.get(&post_id).unwrap(),
+            Module::<T>::post_by_id(thread_id, post_id),
             post
         );
 
@@ -1512,7 +1400,7 @@ benchmarks! {
     moderate_post_lead {
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -1540,9 +1428,11 @@ benchmarks! {
 
     }: moderate_post(RawOrigin::Signed(caller_id), PrivilegedActor::Lead, category_id, thread_id, post_id, rationale.clone())
     verify {
-        thread.posts.remove(&post_id);
+        thread.number_of_posts -= 1;
         assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
 
+        assert!(!<PostById<T>>::contains_key(thread_id, post_id));
+
         assert_last_event::<T>(
             RawEvent::PostModerated(
                 post_id,
@@ -1557,7 +1447,7 @@ benchmarks! {
     moderate_post_moderator {
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
@@ -1592,9 +1482,11 @@ benchmarks! {
 
     }: moderate_post(RawOrigin::Signed(caller_id), PrivilegedActor::Moderator(moderator_id), category_id, thread_id, post_id, rationale.clone())
     verify {
-        thread.posts.remove(&post_id);
+        thread.number_of_posts -= 1;
         assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
 
+        assert!(!<PostById<T>>::contains_key(thread_id, post_id));
+
         assert_last_event::<T>(
             RawEvent::PostModerated(
                 post_id,
@@ -1606,14 +1498,93 @@ benchmarks! {
         );
     }
 
+    delete_posts {
+        let forum_user_id = 0;
+        let caller_id =
+            insert_a_leader::<T>(forum_user_id);
+
+        let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
+
+        let j in 0 .. MAX_BYTES;
+
+        let k in 1 .. MAX_POSTS;
+
+        // Generate categories tree
+        let (category_id, _) = generate_categories_tree::<T>(caller_id.clone(), i, None);
+
+        // Create thread
+        let expiration_diff = 10.into();
+        let poll = Some(
+            generate_poll::<T>(expiration_diff, (<<<T as Trait>::MapLimits as StorageLimits>::MaxPollAlternativesNumber>::get() - 1) as u32)
+        );
+        let text = vec![1u8].repeat(MAX_BYTES as usize);
+
+        let thread_id = create_new_thread::<T>(
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
+            text.clone(), text.clone(), poll
+        );
+        let hide = false;
+        let mut posts = Vec::new();
+        for _ in 0 .. k {
+            posts.push((
+                    category_id,
+                    thread_id,
+                    add_thread_post::<T>(
+                        caller_id.clone(),
+                        forum_user_id.saturated_into(),
+                        category_id,
+                        thread_id,
+                        vec![0u8],
+                    ),
+                    hide
+                )
+            );
+        }
+
+        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
+
+        let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
+
+        let moderator_id = ModeratorId::<T>::from(forum_user_id.try_into().unwrap());
+
+        // Set up category membership of moderator.
+        Module::<T>::update_category_membership_of_moderator(
+            RawOrigin::Signed(caller_id.clone()).into(), moderator_id, category_id, true
+        ).unwrap();
+
+        let rationale = vec![0u8].repeat(j as usize);
+
+    }: _(
+        RawOrigin::Signed(caller_id),
+        forum_user_id.saturated_into(),
+        posts.clone(),
+        rationale.clone()
+    )
+    verify {
+        thread.number_of_posts -= k as u64;
+        assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
+
+        for post in posts.clone() {
+            assert!(!<PostById<T>>::contains_key(post.1, post.2));
+        }
+
+        assert_last_event::<T>(
+            RawEvent::PostDeleted(
+                rationale,
+                forum_user_id.saturated_into(),
+                posts,
+            ).into()
+        );
+    }
+
     set_stickied_threads_lead {
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
-        let j in 0 .. <<<T as Trait>::MapLimits as StorageLimits>::MaxThreadsInCategory>::get() as u32;
+        let j in 0 .. MAX_THREADS;
 
         // Generate categories tree
         let (category_id, parent_category_id) = generate_categories_tree::<T>(caller_id.clone(), i, None);
@@ -1652,11 +1623,11 @@ benchmarks! {
     set_stickied_threads_moderator {
         let forum_user_id = 0;
         let caller_id =
-            insert_a_worker::<T>(OpeningType::Leader, forum_user_id);
+            insert_a_leader::<T>(forum_user_id);
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
-        let j in 0 .. <<<T as Trait>::MapLimits as StorageLimits>::MaxThreadsInCategory>::get() as u32;
+        let j in 0 .. MAX_THREADS;
 
         // Generate categories tree
         let (category_id, parent_category_id) = generate_categories_tree::<T>(caller_id.clone(), i, None);
@@ -1772,32 +1743,9 @@ mod tests {
     }
 
     #[test]
-    fn test_update_thread_archival_status_lead() {
-        with_test_externalities(|| {
-            assert_ok!(test_benchmark_update_thread_archival_status_lead::<Runtime>());
-        });
-    }
-
-    #[test]
-    fn test_update_thread_archival_status_moderator() {
-        with_test_externalities(|| {
-            assert_ok!(test_benchmark_update_thread_archival_status_moderator::<
-                Runtime,
-            >());
-        });
-    }
-
-    #[test]
-    fn test_delete_thread_lead() {
-        with_test_externalities(|| {
-            assert_ok!(test_benchmark_delete_thread_lead::<Runtime>());
-        });
-    }
-
-    #[test]
-    fn test_delete_thread_moderator() {
+    fn test_delete_thread() {
         with_test_externalities(|| {
-            assert_ok!(test_benchmark_delete_thread_moderator::<Runtime>());
+            assert_ok!(test_benchmark_delete_thread::<Runtime>());
         });
     }
 
@@ -1884,4 +1832,11 @@ mod tests {
             assert_ok!(test_benchmark_set_stickied_threads_lead::<Runtime>());
         });
     }
+
+    #[test]
+    fn test_delete_posts() {
+        with_test_externalities(|| {
+            assert_ok!(test_benchmark_delete_posts::<Runtime>());
+        });
+    }
 }

+ 321 - 180
runtime-modules/forum/src/lib.rs

@@ -15,13 +15,13 @@ use frame_support::{
 use frame_system::ensure_signed;
 use sp_arithmetic::traits::{BaseArithmetic, One};
 pub use sp_io::storage::clear_prefix;
-use sp_runtime::traits::{AccountIdConversion, MaybeSerialize, Member, Saturating};
+use sp_runtime::traits::{AccountIdConversion, MaybeSerialize, Member};
 use sp_runtime::{ModuleId, SaturatedConversion};
-use sp_std::collections::btree_map::BTreeMap;
+use sp_std::collections::btree_set::BTreeSet;
 use sp_std::fmt::Debug;
 use sp_std::prelude::*;
 
-use common::origin::MemberOriginValidator;
+use common::membership::MemberOriginValidator;
 use common::working_group::WorkingGroupAuthenticator;
 
 mod mock;
@@ -29,6 +29,9 @@ mod tests;
 
 mod benchmarking;
 
+/// Type for keeping track of number of posts in a thread
+pub type NumberOfPosts = u64;
+
 /// Moderator ID alias for the actor of the system.
 pub type ModeratorId<T> = common::ActorId<T>;
 
@@ -45,8 +48,6 @@ pub type ThreadOf<T> = Thread<
     <T as Trait>::CategoryId,
     <T as pallet_timestamp::Trait>::Moment,
     <T as frame_system::Trait>::Hash,
-    <T as Trait>::PostId,
-    Post<ForumUserId<T>, <T as frame_system::Trait>::Hash>,
     BalanceOf<T>,
 >;
 
@@ -62,28 +63,26 @@ pub trait WeightInfo {
     fn update_category_archival_status_moderator(i: u32) -> Weight;
     fn delete_category_lead(i: u32) -> Weight;
     fn delete_category_moderator(i: u32) -> Weight;
-    fn create_thread(i: u32, j: u32, k: u32, z: u32) -> Weight;
+    fn create_thread(j: u32, k: u32, i: u32) -> Weight;
     fn edit_thread_title(i: u32, j: u32) -> Weight;
-    fn update_thread_archival_status_lead(i: u32) -> Weight;
-    fn update_thread_archival_status_moderator(i: u32) -> Weight;
-    fn delete_thread_lead(i: u32) -> Weight;
-    fn delete_thread_moderator(i: u32) -> Weight;
+    fn delete_thread(i: u32) -> Weight;
     fn move_thread_to_category_lead(i: u32) -> Weight;
     fn move_thread_to_category_moderator(i: u32) -> Weight;
     fn vote_on_poll(i: u32, j: u32) -> Weight;
-    fn moderate_thread_lead(i: u32, j: u32, k: u32) -> Weight;
-    fn moderate_thread_moderator(i: u32, j: u32, k: u32) -> Weight;
+    fn moderate_thread_lead(i: u32, k: u32) -> Weight;
+    fn moderate_thread_moderator(i: u32, k: u32) -> Weight;
     fn add_post(i: u32, j: u32) -> Weight;
     fn react_post(i: u32) -> Weight;
     fn edit_post_text(i: u32, j: u32) -> Weight;
     fn moderate_post_lead(i: u32, j: u32) -> Weight;
     fn moderate_post_moderator(i: u32, j: u32) -> Weight;
+    fn delete_posts(i: u32, j: u32, k: u32) -> Weight;
     fn set_stickied_threads_lead(i: u32, j: u32) -> Weight;
     fn set_stickied_threads_moderator(i: u32, j: u32) -> Weight;
 }
 
 pub trait Trait:
-    frame_system::Trait + pallet_timestamp::Trait + common::Trait + balances::Trait
+    frame_system::Trait + pallet_timestamp::Trait + common::membership::Trait + balances::Trait
 {
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
@@ -141,6 +140,9 @@ pub trait Trait:
     /// Maximum depth for nested categories
     type MaxCategoryDepth: Get<u64>;
 
+    /// Maximum number of blocks before a post can be erased by anyone
+    type PostLifeTime: Get<Self::BlockNumber>;
+
     /// Type defining the limits for different Storage items in the forum pallet
     type MapLimits: StorageLimits;
 
@@ -170,12 +172,6 @@ pub trait StorageLimits {
     /// Maximum direct subcategories in a category
     type MaxSubcategories: Get<u64>;
 
-    /// Maximum direct threads in a category
-    type MaxThreadsInCategory: Get<u64>;
-
-    /// Maximum posts in a thread
-    type MaxPostsInThread: Get<u64>;
-
     /// Maximum moderator count for a single category
     type MaxModeratorsForCategory: Get<u64>;
 
@@ -214,18 +210,27 @@ pub struct Poll<Timestamp, Hash> {
 /// Represents a thread post
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct Post<ForumUserId, Hash> {
+pub struct Post<ForumUserId, ThreadId, Hash, Balance, BlockNumber> {
+    /// Id of thread to which this post corresponds.
+    pub thread_id: ThreadId,
+
     /// Hash of current text
     pub text_hash: Hash,
 
     /// Author of post.
     pub author_id: ForumUserId,
+
+    /// Cleanup pay off
+    pub cleanup_pay_off: Balance,
+
+    /// When it was created or last edited
+    pub last_edited: BlockNumber,
 }
 
 /// Represents a thread
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Debug, Eq)]
-pub struct Thread<ForumUserId, CategoryId, Moment, Hash, PostId: sp_std::cmp::Ord, Post, Balance> {
+pub struct Thread<ForumUserId, CategoryId, Moment, Hash, Balance> {
     /// Title hash
     pub title_hash: Hash,
 
@@ -235,17 +240,14 @@ pub struct Thread<ForumUserId, CategoryId, Moment, Hash, PostId: sp_std::cmp::Or
     /// Author of post.
     pub author_id: ForumUserId,
 
-    /// Whether thread is archived.
-    pub archived: bool,
-
     /// poll description.
     pub poll: Option<Poll<Moment, Hash>>,
 
-    /// Post in thread
-    pub posts: BTreeMap<PostId, Post>,
-
     /// Pay off by deleting
     pub cleanup_pay_off: Balance,
+
+    /// Number of posts in the thread
+    pub number_of_posts: NumberOfPosts,
 }
 
 /// Represents a category
@@ -333,9 +335,6 @@ decl_error! {
         /// Thread not being updated.
         ThreadNotBeingUpdated,
 
-        /// Thread is immutable, i.e. archived.
-        ThreadImmutable,
-
         /// Not enough balance to create thread
         InsufficientBalanceForThreadCreation,
 
@@ -379,6 +378,9 @@ decl_error! {
         /// No permissions to update category.
         ModeratorCantUpdateCategory,
 
+        /// Duplicates for the stickied thread id collection.
+        StickiedThreadIdsDuplicates,
+
         // Errors about poll.
 
         /// Poll items number too short.
@@ -396,6 +398,9 @@ decl_error! {
         /// Poll data committed after poll expired.
         PollCommitExpired,
 
+        /// Forum user has already voted.
+        AlreadyVotedOnPoll,
+
         // Error data migration
 
         /// data migration not done yet.
@@ -438,6 +443,22 @@ decl_storage! {
 
         /// If data migration is done, set as configible for unit test purpose
         pub DataMigrationDone get(fn data_migration_done) config(): bool;
+
+        /// Unique thread poll voters. This private double map prevents double voting.
+        PollVotes get(fn poll_votes_by_thread_id_by_forum_user_id): double_map
+            hasher(blake2_128_concat) T::ThreadId,
+            hasher(blake2_128_concat) ForumUserId<T> => bool;
+
+        /// Map post identifier to corresponding post.
+        pub PostById get(fn post_by_id) config(): double_map hasher(blake2_128_concat) T::ThreadId,
+            hasher(blake2_128_concat) T::PostId =>
+                                                Post<
+                                                    ForumUserId<T>,
+                                                    T::ThreadId,
+                                                    T::Hash,
+                                                    BalanceOf<T>,
+                                                    T::BlockNumber
+                                                >;
     }
 }
 
@@ -477,17 +498,20 @@ decl_event!(
         ThreadTitleUpdated(ThreadId, ForumUserId, CategoryId, Vec<u8>),
 
         /// A thread was deleted.
-        ThreadDeleted(ThreadId, PrivilegedActor, CategoryId),
+        ThreadDeleted(ThreadId, ForumUserId, CategoryId, bool),
 
         /// A thread was moved to new category
         ThreadMoved(ThreadId, CategoryId, PrivilegedActor, CategoryId),
 
         /// Post with given id was created.
-        PostAdded(PostId, ForumUserId, CategoryId, ThreadId, Vec<u8>),
+        PostAdded(PostId, ForumUserId, CategoryId, ThreadId, Vec<u8>, bool),
 
         /// Post with givne id was moderated.
         PostModerated(PostId, Vec<u8>, PrivilegedActor, CategoryId, ThreadId),
 
+        /// Post with givne id was deleted.
+        PostDeleted(Vec<u8>, ForumUserId, Vec<(CategoryId, ThreadId, PostId, bool)>),
+
         /// Post with given id had its text updated.
         /// The second argument reflects the number of total edits when the text update occurs.
         PostTextUpdated(PostId, ForumUserId, CategoryId, ThreadId, Vec<u8>),
@@ -725,12 +749,9 @@ decl_module! {
         ///    - O(W)
         /// # </weight>
         #[weight = WeightInfoForum::<T>::create_thread(
-            T::MaxCategoryDepth::get() as u32,
             title.len().saturated_into(),
             text.len().saturated_into(),
-            poll.as_ref()
-                .map(|poll| poll.poll_alternatives.len().saturated_into())
-                .unwrap_or_default(),
+            T::MaxCategoryDepth::get() as u32,
         )]
         fn create_thread(
             origin,
@@ -776,10 +797,9 @@ decl_module! {
                 category_id,
                 title_hash: T::calculate_hash(&title),
                 author_id: forum_user_id,
-                archived: false,
                 poll: poll.clone(),
-                posts: BTreeMap::new(),
                 cleanup_pay_off: T::ThreadDeposit::get(),
+                number_of_posts: 0,
             };
 
             // Store thread
@@ -792,7 +812,8 @@ decl_module! {
                 new_thread_id,
                 category_id,
                 &text,
-                forum_user_id
+                forum_user_id,
+                true,
             );
 
             // Update next thread id
@@ -860,53 +881,6 @@ decl_module! {
             Ok(())
         }
 
-        /// Update thread archival status
-        ///
-        /// <weight>
-        ///
-        /// ## Weight
-        /// `O (W)` where:
-        /// - `W` is the category depth
-        /// - DB:
-        ///    - O(W)
-        /// # </weight>
-        #[weight = WeightInfoForum::<T>::update_thread_archival_status_lead(
-            T::MaxCategoryDepth::get() as u32,
-        ).max(WeightInfoForum::<T>::update_thread_archival_status_moderator(
-            T::MaxCategoryDepth::get() as u32,
-        ))]
-        fn update_thread_archival_status(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, thread_id: T::ThreadId, new_archival_status: bool) -> DispatchResult {
-            // Ensure data migration is done
-            Self::ensure_data_migration_done()?;
-
-            let account_id = ensure_signed(origin)?;
-
-            // Ensure actor can update category
-            let (_, thread) = Self::ensure_can_update_thread_archival_status(account_id, &actor, &category_id, &thread_id)?;
-
-            // No change, invalid transaction
-            if new_archival_status == thread.archived {
-                return Err(Error::<T>::ThreadNotBeingUpdated.into());
-            }
-
-            //
-            // == MUTATION SAFE ==
-            //
-
-            // Mutate thread, and set possible new change parameters
-            <ThreadById<T>>::mutate(thread.category_id, thread_id, |c| c.archived = new_archival_status);
-
-            // Generate event
-            Self::deposit_event(RawEvent::ThreadUpdated(
-                    thread_id,
-                    new_archival_status,
-                    actor,
-                    category_id
-                ));
-
-            Ok(())
-        }
-
         /// Delete thread
         ///
         /// <weight>
@@ -917,18 +891,25 @@ decl_module! {
         /// - DB:
         ///    - O(W)
         /// # </weight>
-        #[weight = WeightInfoForum::<T>::delete_thread_lead(
-            T::MaxCategoryDepth::get() as u32,
-        ).max(WeightInfoForum::<T>::delete_thread_moderator(
-            T::MaxCategoryDepth::get() as u32,
-        ))]
-        fn delete_thread(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, thread_id: T::ThreadId) -> DispatchResult {
+        #[weight = WeightInfoForum::<T>::delete_thread(T::MaxCategoryDepth::get() as u32)]
+        fn delete_thread(
+            origin,
+            forum_user_id: ForumUserId<T>,
+            category_id: T::CategoryId,
+            thread_id: T::ThreadId,
+            hide: bool,
+        ) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
             let account_id = ensure_signed(origin)?;
 
-            let thread = Self::ensure_can_moderate_thread(&account_id, &actor, &category_id, &thread_id)?;
+            let thread = Self::ensure_can_delete_thread(
+                &account_id,
+                &forum_user_id,
+                &category_id,
+                &thread_id
+            )?;
 
             //
             // == MUTATION SAFE ==
@@ -943,8 +924,9 @@ decl_module! {
             // Store the event
             Self::deposit_event(RawEvent::ThreadDeleted(
                     thread_id,
-                    actor,
-                    category_id
+                    forum_user_id,
+                    category_id,
+                    hide,
                 ));
 
             Ok(())
@@ -1021,7 +1003,7 @@ decl_module! {
             let category_id = thread.category_id;
 
             // Make sure poll exist
-            let poll = Self::ensure_vote_is_valid(thread, index)?;
+            let poll = Self::ensure_vote_is_valid(thread, index, &thread_id, &forum_user_id)?;
 
             //
             // == MUTATION SAFE ==
@@ -1054,6 +1036,9 @@ decl_module! {
                 }
             });
 
+            // Update unique votes collection.
+            <PollVotes<T>>::insert(&thread_id, &forum_user_id, true);
+
             // Store the event
             Self::deposit_event(
                 RawEvent::VoteOnPoll(thread_id, index, forum_user_id, category_id)
@@ -1076,12 +1061,10 @@ decl_module! {
         /// # </weight>
         #[weight = WeightInfoForum::<T>::moderate_thread_lead(
             T::MaxCategoryDepth::get() as u32,
-            <T::MapLimits as StorageLimits>::MaxPostsInThread::get() as u32,
             rationale.len().saturated_into(),
         ).max(
             WeightInfoForum::<T>::moderate_thread_moderator(
                 T::MaxCategoryDepth::get() as u32,
-                <T::MapLimits as StorageLimits>::MaxPostsInThread::get() as u32,
                 rationale.len().saturated_into(),
             )
         )]
@@ -1126,43 +1109,55 @@ decl_module! {
             T::MaxCategoryDepth::get() as u32,
             text.len().saturated_into(),
         )]
-        fn add_post(origin, forum_user_id: ForumUserId<T>, category_id: T::CategoryId, thread_id: T::ThreadId, text: Vec<u8>) -> DispatchResult {
+        fn add_post(
+            origin,
+            forum_user_id: ForumUserId<T>,
+            category_id: T::CategoryId,
+            thread_id: T::ThreadId,
+            text: Vec<u8>,
+            editable: bool,
+        ) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
             let account_id = ensure_signed(origin)?;
 
             // Make sure thread exists and is mutable
-            let (_, thread) = Self::ensure_can_add_post(&account_id, &forum_user_id, &category_id, &thread_id)?;
+            let _ = Self::ensure_can_add_post(&account_id, &forum_user_id, &category_id, &thread_id)?;
 
-            // Ensure map limits are not reached
-            Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxPostsInThread>(
-                thread.posts.len().saturated_into(),
-            )?;
+            if editable {
+                ensure!(
+                    Self::ensure_enough_balance(T::PostDeposit::get(), &account_id),
+                    Error::<T>::InsufficientBalanceForPost
+                );
+            }
 
             //
             // == MUTATION SAFE ==
             //
 
-            // Shouldn't fail since we checked in `ensure_can_add_post` that the account
-            // has enough balance.
-            Self::transfer_to_state_cleanup_treasury_account(
-                T::PostDeposit::get(),
-                thread_id,
-                &account_id
-            )?;
+            if editable {
+                // Shouldn't fail since we checked in `ensure_can_add_post` that the account
+                // has enough balance.
+                Self::transfer_to_state_cleanup_treasury_account(
+                    T::PostDeposit::get(),
+                    thread_id,
+                    &account_id
+                )?;
+            }
 
             // Add new post
-            let (post_id, _) = Self::add_new_post(
+            let post_id = Self::add_new_post(
                     thread_id,
                     category_id,
                     text.as_slice(),
-                    forum_user_id
+                    forum_user_id,
+                    editable,
                 );
 
             // Generate event
             Self::deposit_event(
-                RawEvent::PostAdded(post_id, forum_user_id, category_id, thread_id, text)
+                RawEvent::PostAdded(post_id, forum_user_id, category_id, thread_id, text, editable)
             );
 
             Ok(())
@@ -1190,8 +1185,8 @@ decl_module! {
             // Check that account is forum member
             Self::ensure_is_forum_user(&account_id, &forum_user_id)?;
 
-            // Make sure there exists a mutable post with post id `post_id`
-            Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?;
+            // Make sure the thread exists and is mutable
+            Self::ensure_thread_is_mutable(&category_id, &thread_id)?;
 
             //
             // == MUTATION SAFE ==
@@ -1219,7 +1214,14 @@ decl_module! {
             T::MaxCategoryDepth::get() as u32,
             new_text.len().saturated_into(),
         )]
-        fn edit_post_text(origin, forum_user_id: ForumUserId<T>, category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId, new_text: Vec<u8>) -> DispatchResult {
+        fn edit_post_text(
+            origin,
+            forum_user_id: ForumUserId<T>,
+            category_id: T::CategoryId,
+            thread_id: T::ThreadId,
+            post_id: T::PostId,
+            new_text: Vec<u8>
+        ) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
@@ -1241,10 +1243,9 @@ decl_module! {
             // Update post text
             let text_hash = T::calculate_hash(&new_text);
             post.text_hash = text_hash;
+            post.last_edited = frame_system::Module::<T>::block_number();
 
-            <ThreadById<T>>::mutate(category_id, thread_id,
-                |thread| thread.posts.insert(post_id, post)
-            );
+            <PostById<T>>::insert(thread_id, post_id, post);
 
             // Generate event
             Self::deposit_event(RawEvent::PostTextUpdated(
@@ -1282,13 +1283,21 @@ decl_module! {
 
             let account_id = ensure_signed(origin)?;
 
-            // Ensure actor is allowed to moderate post
-            Self::ensure_can_moderate_post(account_id, &actor, &category_id, &thread_id, &post_id)?;
+            // Ensure actor is allowed to moderate post and post is editable
+            let post = Self::ensure_can_moderate_post(
+                account_id,
+                &actor,
+                &category_id,
+                &thread_id,
+                &post_id
+            )?;
 
             //
             // == MUTATION SAFE ==
             //
 
+            Self::slash_thread_account(thread_id, post.cleanup_pay_off);
+
             Self::delete_post_inner(category_id, thread_id, post_id);
 
             // Generate event
@@ -1299,6 +1308,70 @@ decl_module! {
             Ok(())
         }
 
+        /// Delete post from storage.
+        /// You need to provide a vector of posts to delete in the form
+        /// (T::CategoryId, T::ThreadId, T::PostId, bool)
+        /// where the last bool is whether you want to hide it apart from deleting it
+        ///
+        /// ## Weight
+        /// `O (W + V + P)` where:
+        /// - `W` is the category depth,
+        /// - `V` is the length of the rationale
+        /// - `P` is the number of posts to delete
+        /// - DB:
+        ///    - O(W + P)
+        /// # </weight>
+        #[weight = WeightInfoForum::<T>::delete_posts(
+            T::MaxCategoryDepth::get() as u32,
+            rationale.len().saturated_into(),
+            posts.len().saturated_into(),
+        )]
+        fn delete_posts(
+            origin,
+            forum_user_id: ForumUserId<T>,
+            posts: Vec<(T::CategoryId, T::ThreadId, T::PostId, bool)>,
+            rationale: Vec<u8>,
+        ) -> DispatchResult {
+
+            // Ensure data migration is done
+            Self::ensure_data_migration_done()?;
+
+            let account_id = ensure_signed(origin)?;
+
+            let mut deleting_posts = Vec::new();
+            for (category_id, thread_id, post_id, hide) in &posts {
+                // Ensure actor is allowed to moderate post and post is editable
+                let post = Self::ensure_can_delete_post(
+                    &account_id,
+                    &forum_user_id,
+                    &category_id,
+                    &thread_id,
+                    &post_id,
+                    *hide,
+                )?;
+
+                deleting_posts.push((category_id, thread_id, post_id, post));
+            }
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            for (category_id, thread_id, post_id, post) in deleting_posts {
+                // Pay off to thread deleter
+                Self::pay_off(*thread_id, post.cleanup_pay_off, &account_id)?;
+
+                Self::delete_post_inner(*category_id, *thread_id, *post_id);
+            }
+
+            // Generate event
+            Self::deposit_event(
+                RawEvent::PostDeleted(rationale, forum_user_id, posts)
+            );
+
+            Ok(())
+        }
+
         /// Set stickied threads for category
         ///
         /// <weight>
@@ -1383,42 +1456,55 @@ impl<T: Trait> Module<T> {
         category_id: T::CategoryId,
         text: &[u8],
         author_id: ForumUserId<T>,
-    ) -> (T::PostId, Post<ForumUserId<T>, T::Hash>) {
+        editable: bool,
+    ) -> T::PostId {
         // Make and add initial post
         let new_post_id = <NextPostId<T>>::get();
 
-        // Build a post
-        let new_post = Post {
-            text_hash: T::calculate_hash(text),
-            author_id,
-        };
+        // Update next post id
+        <NextPostId<T>>::mutate(|n| *n += One::one());
 
-        let mut thread = <ThreadById<T>>::get(category_id, thread_id);
-        thread.posts.insert(new_post_id, new_post.clone());
+        if editable {
+            // Build a post
+            let new_post = Post {
+                text_hash: T::calculate_hash(text),
+                thread_id,
+                author_id,
+                cleanup_pay_off: T::PostDeposit::get(),
+                last_edited: frame_system::Module::<T>::block_number(),
+            };
 
-        thread.cleanup_pay_off = thread.cleanup_pay_off.saturating_add(T::PostDeposit::get());
+            <PostById<T>>::insert(thread_id, new_post_id, new_post);
+        }
 
-        <ThreadById<T>>::insert(category_id, thread_id, thread);
+        let mut thread = <ThreadById<T>>::get(category_id, thread_id);
+        thread.number_of_posts = thread.number_of_posts.saturating_add(1);
 
-        // Update next post id
-        <NextPostId<T>>::mutate(|n| *n += One::one());
+        <ThreadById<T>>::mutate(category_id, thread_id, |value| *value = thread);
 
-        (new_post_id, new_post)
+        new_post_id
     }
 
     fn delete_thread_inner(category_id: T::CategoryId, thread_id: T::ThreadId) {
         // Delete thread
         <ThreadById<T>>::remove(category_id, thread_id);
 
+        // Remove all thread poll votes.
+        <PollVotes<T>>::remove_prefix(thread_id);
+
         // decrease category's thread counter
         <CategoryById<T>>::mutate(category_id, |category| category.num_direct_threads -= 1);
     }
 
     fn delete_post_inner(category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId) {
-        // Decrease thread's post counter
-        <ThreadById<T>>::mutate(category_id, thread_id, |thread| {
-            thread.posts.remove(&post_id);
-        });
+        if <ThreadById<T>>::contains_key(category_id, thread_id) {
+            let mut thread = <ThreadById<T>>::get(category_id, thread_id);
+            thread.number_of_posts = thread.number_of_posts.saturating_sub(1);
+
+            <ThreadById<T>>::mutate(category_id, thread_id, |value| *value = thread);
+        }
+
+        <PostById<T>>::remove(thread_id, post_id);
     }
 
     // Ensure poll is valid
@@ -1451,8 +1537,9 @@ impl<T: Trait> Module<T> {
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
         post_id: &T::PostId,
-    ) -> Result<Post<ForumUserId<T>, T::Hash>, Error<T>> {
-        // Make sure post exists
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>, Error<T>>
+    {
+        // If the post is stored then it's mutable
         let post = Self::ensure_post_exists(category_id, thread_id, post_id)?;
 
         // and make sure thread is mutable
@@ -1461,22 +1548,22 @@ impl<T: Trait> Module<T> {
         Ok(post)
     }
 
+    // TODO: change this name, since it's no longer descriptive
     fn ensure_post_exists(
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
         post_id: &T::PostId,
-    ) -> Result<Post<ForumUserId<T>, T::Hash>, Error<T>> {
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>, Error<T>>
+    {
         if !<ThreadById<T>>::contains_key(category_id, thread_id) {
             return Err(Error::<T>::PostDoesNotExist);
         }
 
-        let thread = <ThreadById<T>>::get(category_id, thread_id);
+        if !<PostById<T>>::contains_key(thread_id, post_id) {
+            return Err(Error::<T>::PostDoesNotExist);
+        }
 
-        thread
-            .posts
-            .get(post_id)
-            .cloned()
-            .ok_or_else(|| Error::<T>::PostDoesNotExist)
+        Ok(<PostById<T>>::get(thread_id, post_id))
     }
 
     fn ensure_can_moderate_post(
@@ -1485,46 +1572,71 @@ impl<T: Trait> Module<T> {
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
         post_id: &T::PostId,
-    ) -> Result<Post<ForumUserId<T>, T::Hash>, Error<T>> {
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>, Error<T>>
+    {
         // Ensure the moderator can moderate the category
         Self::ensure_can_moderate_category(&account_id, &actor, &category_id)?;
 
         // Make sure post exists and is mutable
-        let post = Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?;
+        let post = if Self::thread_exists(category_id, thread_id) {
+            Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?
+        } else {
+            <PostById<T>>::get(thread_id, post_id)
+        };
 
         Ok(post)
     }
 
-    fn ensure_thread_is_mutable(
+    fn ensure_can_delete_post(
+        account_id: &T::AccountId,
+        forum_user_id: &ForumUserId<T>,
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
-    ) -> Result<(Category<T::CategoryId, T::ThreadId, T::Hash>, ThreadOf<T>), Error<T>> {
-        // Make sure thread exists
-        let thread = Self::ensure_thread_exists(category_id, thread_id)?;
+        post_id: &T::PostId,
+        hide: bool,
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>, Error<T>>
+    {
+        let post = if Self::thread_exists(category_id, thread_id) {
+            Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?
+        } else {
+            <PostById<T>>::get(thread_id, post_id)
+        };
 
-        if thread.archived {
-            return Err(Error::<T>::ThreadImmutable);
-        }
+        // Check that account is forum member
+        Self::ensure_is_forum_user(&account_id, &forum_user_id)?;
 
-        // and corresponding category is mutable
-        let category = Self::ensure_category_is_mutable(category_id)?;
+        // Signer does not match creator of post with identifier postId
+        ensure!(
+            post.author_id == *forum_user_id
+                || Self::anyone_can_delete_post(&post, &thread_id, &category_id) && !hide,
+            Error::<T>::AccountDoesNotMatchPostAuthor
+        );
 
-        Ok((category, thread))
+        Ok(post)
     }
 
-    fn ensure_can_update_thread_archival_status(
-        account_id: T::AccountId,
-        actor: &PrivilegedActor<T>,
+    fn anyone_can_delete_post(
+        post: &Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>,
+        thread_id: &T::ThreadId,
+        category_id: &T::CategoryId,
+    ) -> bool {
+        frame_system::Module::<T>::block_number() >= T::PostLifeTime::get() + post.last_edited
+            && !Self::thread_exists(&category_id, &thread_id)
+    }
+
+    fn thread_exists(category_id: &T::CategoryId, thread_id: &T::ThreadId) -> bool {
+        <ThreadById<T>>::contains_key(category_id, thread_id)
+    }
+
+    fn ensure_thread_is_mutable(
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
     ) -> Result<(Category<T::CategoryId, T::ThreadId, T::Hash>, ThreadOf<T>), Error<T>> {
-        // Check actor's role
-        Self::ensure_actor_role(&account_id, actor)?;
-
-        let (category, thread) = Self::ensure_thread_is_mutable(category_id, thread_id)?;
+        // Make sure thread exists
+        let thread = Self::ensure_thread_exists(category_id, thread_id)?;
 
-        // Ensure actor can delete category
-        Self::ensure_can_moderate_category_path(actor, category_id)?;
+        // and corresponding category is mutable
+        let category = Self::ensure_category_is_mutable(category_id)?;
 
         Ok((category, thread))
     }
@@ -1632,6 +1744,25 @@ impl<T: Trait> Module<T> {
         Ok(thread)
     }
 
+    // Ensure actor can manipulate thread.
+    fn ensure_can_delete_thread(
+        account_id: &T::AccountId,
+        forum_user_id: &ForumUserId<T>,
+        category_id: &T::CategoryId,
+        thread_id: &T::ThreadId,
+    ) -> Result<ThreadOf<T>, Error<T>> {
+        // Ensure thread exists and is mutable
+        let (_, thread) = Self::ensure_thread_is_mutable(category_id, thread_id)?;
+
+        // Check that account is forum member
+        Self::ensure_is_forum_user(&account_id, &forum_user_id)?;
+
+        // Ensure forum user is author of the thread
+        Self::ensure_is_thread_author(&thread, &forum_user_id)?;
+
+        Ok(thread)
+    }
+
     fn ensure_can_move_thread(
         account_id: T::AccountId,
         actor: &PrivilegedActor<T>,
@@ -1909,10 +2040,6 @@ impl<T: Trait> Module<T> {
 
         let category = Self::ensure_category_is_mutable(category_id)?;
 
-        Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxThreadsInCategory>(
-            category.num_direct_threads as u64,
-        )?;
-
         // The balance for creation of thread is the base cost plus the cost of a single post
         let minimum_balance = T::ThreadDeposit::get() + T::PostDeposit::get();
         ensure!(
@@ -1936,11 +2063,6 @@ impl<T: Trait> Module<T> {
         // Check that account is forum member
         Self::ensure_is_forum_user(account_id, &forum_user_id)?;
 
-        ensure!(
-            Self::ensure_enough_balance(T::PostDeposit::get(), &account_id),
-            Error::<T>::InsufficientBalanceForPost
-        );
-
         let (category, thread) = Self::ensure_thread_is_mutable(category_id, thread_id)?;
 
         Ok((category, thread))
@@ -1956,8 +2078,19 @@ impl<T: Trait> Module<T> {
         let category = Self::ensure_can_moderate_category(&account_id, &actor, &category_id)?;
 
         // Ensure all thread id valid and is under the category
-        for item in stickied_ids {
-            Self::ensure_thread_exists(&category_id, item)?;
+        // Helps to prevent thread ID duplicates.
+        let mut unique_stickied_ids = BTreeSet::<T::ThreadId>::new();
+
+        // Ensure all thread id valid and is under the category
+        for thread_id in stickied_ids {
+            // Check for ID duplicates.
+            if unique_stickied_ids.contains(thread_id) {
+                return Err(Error::<T>::StickiedThreadIdsDuplicates);
+            } else {
+                unique_stickied_ids.insert(*thread_id);
+            }
+
+            Self::ensure_thread_exists(&category_id, thread_id)?;
         }
 
         Ok(category)
@@ -1967,10 +2100,18 @@ impl<T: Trait> Module<T> {
     fn ensure_vote_is_valid(
         thread: ThreadOf<T>,
         index: u32,
+        thread_id: &T::ThreadId,
+        forum_user_id: &ForumUserId<T>,
     ) -> Result<Poll<T::Moment, T::Hash>, Error<T>> {
         // Ensure poll exists
         let poll = thread.poll.ok_or(Error::<T>::PollNotExist)?;
 
+        // No previous votes for a forum user.
+        ensure!(
+            !Self::poll_votes_by_thread_id_by_forum_user_id(thread_id, forum_user_id),
+            Error::<T>::AlreadyVotedOnPoll
+        );
+
         // Poll not expired
         if poll.end_time < <pallet_timestamp::Module<T>>::now() {
             Err(Error::<T>::PollCommitExpired)

+ 134 - 77
runtime-modules/forum/src/mock.rs

@@ -13,7 +13,7 @@ use staking_handler::LockComparator;
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
 use sp_runtime::{
     testing::Header,
-    traits::{BlakeTwo256, Hash, IdentityLookup, Zero},
+    traits::{BlakeTwo256, Hash, IdentityLookup},
     DispatchError, Perbill,
 };
 
@@ -44,7 +44,7 @@ parameter_types! {
     pub const MaximumBlockLength: u32 = 2 * 1024;
     pub const AvailableBlockRatio: Perbill = Perbill::one();
     pub const MinimumPeriod: u64 = 5;
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u64 = 10;
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
 }
@@ -94,7 +94,7 @@ impl balances::Trait for Runtime {
     type MaxLocks = ();
 }
 
-impl common::Trait for Runtime {
+impl common::membership::Trait for Runtime {
     type MemberId = u128;
     type ActorId = u128;
 }
@@ -104,8 +104,9 @@ parameter_types! {
     pub const LockId: [u8; 8] = [9; 8];
     pub const InviteMemberLockId: [u8; 8] = [9; 8];
     pub const StakingCandidateLockId: [u8; 8] = [10; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
     pub const CandidateStake: u64 = 100;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 // The forum working group instance alias.
@@ -120,7 +121,8 @@ impl working_group::Trait<ForumWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = Weights;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl LockComparator<<Runtime as balances::Trait>::Balance> for Runtime {
@@ -306,17 +308,17 @@ impl membership::Trait for Runtime {
     type WorkingGroup = ();
     type WeightInfo = Weights;
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InviteMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
 }
 
 parameter_types! {
+    pub const ReferralCutMaximumPercent: u8 = 50;
     pub const MaxCategoryDepth: u64 = 20;
-
+    pub const PostLifeTime: u64 = 100;
     pub const MaxSubcategories: u64 = 20;
-    pub const MaxThreadsInCategory: u64 = 20;
-    pub const MaxPostsInThread: u64 = 20;
     pub const MaxModeratorsForCategory: u64 = 3;
     pub const MaxCategories: u64 = 40;
     pub const MaxPollAlternativesNumber: u64 = 20;
@@ -329,8 +331,6 @@ pub struct MapLimits;
 
 impl StorageLimits for MapLimits {
     type MaxSubcategories = MaxSubcategories;
-    type MaxThreadsInCategory = MaxThreadsInCategory;
-    type MaxPostsInThread = MaxPostsInThread;
     type MaxModeratorsForCategory = MaxModeratorsForCategory;
     type MaxCategories = MaxCategories;
     type MaxPollAlternativesNumber = MaxPollAlternativesNumber;
@@ -343,6 +343,7 @@ impl Trait for Runtime {
     type PostId = u64;
     type PostReactionId = u64;
     type MaxCategoryDepth = MaxCategoryDepth;
+    type PostLifeTime = PostLifeTime;
 
     type MapLimits = MapLimits;
     type WorkingGroup = ();
@@ -359,7 +360,7 @@ impl Trait for Runtime {
     type WeightInfo = ();
 }
 
-impl common::origin::MemberOriginValidator<Origin, u128, u128> for () {
+impl common::membership::MemberOriginValidator<Origin, u128, u128> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         member_id: u128,
@@ -373,12 +374,19 @@ impl common::origin::MemberOriginValidator<Origin, u128, u128> for () {
     }
 
     fn is_member_controller_account(member_id: &u128, account_id: &u128) -> bool {
-        let allowed_accounts = [
+        let mut allowed_accounts = vec![
             FORUM_LEAD_ORIGIN_ID,
             NOT_FORUM_LEAD_ORIGIN_ID,
             NOT_FORUM_LEAD_2_ORIGIN_ID,
         ];
 
+        // Test accounts for benchmarks.
+        let max_worker_number =
+            <Runtime as working_group::Trait<ForumWorkingGroupInstance>>::MaxWorkerNumberLimit::get(
+            ) as u128;
+        let mut benchmarks_accounts: Vec<u128> = (1..max_worker_number).collect::<Vec<_>>();
+        allowed_accounts.append(&mut benchmarks_accounts);
+
         allowed_accounts.contains(account_id) && account_id == member_id
     }
 }
@@ -386,7 +394,7 @@ impl common::origin::MemberOriginValidator<Origin, u128, u128> for () {
 impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
     fn ensure_worker_origin(
         _origin: <Runtime as frame_system::Trait>::Origin,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
+        _worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!()
     }
@@ -395,7 +403,7 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Runtime as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Runtime as common::membership::Trait>::MemberId> {
         unimplemented!()
     }
 
@@ -405,7 +413,7 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
 
     fn is_worker_account_id(
         account_id: &<Runtime as frame_system::Trait>::AccountId,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
+        _worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> bool {
         *account_id != NOT_FORUM_MODERATOR_ORIGIN_ID
     }
@@ -433,22 +441,13 @@ impl WeightInfo for () {
     fn delete_category_moderator(_: u32) -> Weight {
         0
     }
-    fn create_thread(_: u32, _: u32, _: u32, _: u32) -> Weight {
+    fn create_thread(_: u32, _: u32, _: u32) -> Weight {
         0
     }
     fn edit_thread_title(_: u32, _: u32) -> Weight {
         0
     }
-    fn update_thread_archival_status_lead(_: u32) -> Weight {
-        0
-    }
-    fn update_thread_archival_status_moderator(_: u32) -> Weight {
-        0
-    }
-    fn delete_thread_lead(_: u32) -> Weight {
-        0
-    }
-    fn delete_thread_moderator(_: u32) -> Weight {
+    fn delete_thread(_: u32) -> Weight {
         0
     }
     fn move_thread_to_category_lead(_: u32) -> Weight {
@@ -460,10 +459,10 @@ impl WeightInfo for () {
     fn vote_on_poll(_: u32, _: u32) -> Weight {
         0
     }
-    fn moderate_thread_lead(_: u32, _: u32, _: u32) -> Weight {
+    fn moderate_thread_lead(_: u32, _: u32) -> Weight {
         0
     }
-    fn moderate_thread_moderator(_: u32, _: u32, _: u32) -> Weight {
+    fn moderate_thread_moderator(_: u32, _: u32) -> Weight {
         0
     }
     fn add_post(_: u32, _: u32) -> Weight {
@@ -487,6 +486,9 @@ impl WeightInfo for () {
     fn set_stickied_threads_moderator(_: u32, _: u32) -> Weight {
         0
     }
+    fn delete_posts(_: u32, _: u32, _: u32) -> Weight {
+        0
+    }
 }
 
 #[derive(Clone)]
@@ -729,12 +731,13 @@ pub fn edit_thread_title_mock(
 pub fn delete_thread_mock(
     origin: OriginType,
     account_id: <Runtime as frame_system::Trait>::AccountId,
-    moderator_id: ModeratorId<Runtime>,
+    forum_user_id: ForumUserId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
-    thread_id: <Runtime as Trait>::PostId,
+    thread_id: <Runtime as Trait>::ThreadId,
     result: DispatchResult,
 ) {
     let initial_balance = balances::Module::<Runtime>::free_balance(&account_id);
+    let hide = false;
 
     let num_direct_threads = match <CategoryById<Runtime>>::contains_key(category_id) {
         true => <CategoryById<Runtime>>::get(category_id).num_direct_threads,
@@ -744,9 +747,10 @@ pub fn delete_thread_mock(
     assert_eq!(
         TestForumModule::delete_thread(
             mock_origin(origin.clone()),
-            PrivilegedActor::Moderator(moderator_id),
+            forum_user_id,
             category_id,
             thread_id,
+            hide,
         ),
         result
     );
@@ -760,8 +764,9 @@ pub fn delete_thread_mock(
             System::events().last().unwrap().event,
             TestEvent::forum_mod(RawEvent::ThreadDeleted(
                 thread_id,
-                PrivilegedActor::Moderator(moderator_id),
-                category_id
+                forum_user_id,
+                category_id,
+                hide,
             ))
         );
 
@@ -777,66 +782,88 @@ pub fn delete_thread_mock(
     }
 }
 
-pub fn move_thread_mock(
+pub fn delete_post_mock(
     origin: OriginType,
-    moderator_id: ModeratorId<Runtime>,
+    account_id: <Runtime as frame_system::Trait>::AccountId,
+    forum_user_id: ForumUserId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
-    thread_id: <Runtime as Trait>::PostId,
-    new_category_id: <Runtime as Trait>::CategoryId,
+    thread_id: <Runtime as Trait>::ThreadId,
+    post_id: <Runtime as Trait>::PostId,
     result: DispatchResult,
+    hide: bool,
 ) {
+    let initial_balance = balances::Module::<Runtime>::free_balance(&account_id);
+    let number_of_posts = <ThreadById<Runtime>>::get(category_id, thread_id).number_of_posts;
+    let deleted_posts = vec![(category_id, thread_id, post_id, hide)];
+
     assert_eq!(
-        TestForumModule::move_thread_to_category(
+        TestForumModule::delete_posts(
             mock_origin(origin.clone()),
-            PrivilegedActor::Moderator(moderator_id),
-            category_id,
-            thread_id,
-            new_category_id,
+            forum_user_id,
+            deleted_posts.clone(),
+            vec![0u8]
         ),
         result
     );
+
     if result.is_ok() {
-        assert!(<ThreadById<Runtime>>::contains_key(
-            new_category_id,
-            thread_id
-        ),);
+        assert!(!<PostById<Runtime>>::contains_key(thread_id, post_id));
+        if <ThreadById<Runtime>>::contains_key(category_id, thread_id) {
+            assert_eq!(
+                <ThreadById<Runtime>>::get(category_id, thread_id).number_of_posts,
+                number_of_posts - 1,
+            );
+        }
         assert_eq!(
             System::events().last().unwrap().event,
-            TestEvent::forum_mod(RawEvent::ThreadMoved(
-                thread_id,
-                new_category_id,
-                PrivilegedActor::Moderator(moderator_id),
-                category_id
+            TestEvent::forum_mod(RawEvent::PostDeleted(
+                vec![0u8],
+                forum_user_id,
+                deleted_posts.clone()
             ))
         );
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&account_id),
+            initial_balance + <Runtime as Trait>::PostDeposit::get()
+        );
+    } else {
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&account_id),
+            initial_balance
+        );
     }
 }
 
-pub fn update_thread_archival_status_mock(
+pub fn move_thread_mock(
     origin: OriginType,
-    actor: PrivilegedActor<Runtime>,
+    moderator_id: ModeratorId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
-    thread_id: <Runtime as Trait>::ThreadId,
-    new_archival_status: bool,
+    thread_id: <Runtime as Trait>::PostId,
+    new_category_id: <Runtime as Trait>::CategoryId,
     result: DispatchResult,
 ) {
     assert_eq!(
-        TestForumModule::update_thread_archival_status(
-            mock_origin(origin),
-            actor.clone(),
+        TestForumModule::move_thread_to_category(
+            mock_origin(origin.clone()),
+            PrivilegedActor::Moderator(moderator_id),
             category_id,
             thread_id,
-            new_archival_status
+            new_category_id,
         ),
         result
     );
     if result.is_ok() {
+        assert!(<ThreadById<Runtime>>::contains_key(
+            new_category_id,
+            thread_id
+        ),);
         assert_eq!(
             System::events().last().unwrap().event,
-            TestEvent::forum_mod(RawEvent::ThreadUpdated(
+            TestEvent::forum_mod(RawEvent::ThreadMoved(
                 thread_id,
-                new_archival_status,
-                actor,
+                new_category_id,
+                PrivilegedActor::Moderator(moderator_id),
                 category_id
             ))
         );
@@ -850,6 +877,7 @@ pub fn create_post_mock(
     category_id: <Runtime as Trait>::CategoryId,
     thread_id: <Runtime as Trait>::ThreadId,
     text: Vec<u8>,
+    editable: bool,
     result: DispatchResult,
 ) -> <Runtime as Trait>::PostId {
     let post_id = TestForumModule::next_post_id();
@@ -860,10 +888,12 @@ pub fn create_post_mock(
             forum_user_id,
             category_id,
             thread_id,
-            text.clone()
+            text.clone(),
+            editable
         ),
         result
     );
+
     if result.is_ok() {
         assert_eq!(TestForumModule::next_post_id(), post_id + 1);
         assert_eq!(
@@ -873,14 +903,26 @@ pub fn create_post_mock(
                 forum_user_id,
                 category_id,
                 thread_id,
-                text
+                text,
+                editable
             ))
         );
 
-        assert_eq!(
-            balances::Module::<Runtime>::free_balance(&account_id),
-            initial_balance - <Runtime as Trait>::PostDeposit::get()
-        );
+        if editable {
+            assert_eq!(
+                balances::Module::<Runtime>::free_balance(&account_id),
+                initial_balance - <Runtime as Trait>::PostDeposit::get()
+            );
+
+            assert!(<PostById<Runtime>>::contains_key(thread_id, post_id));
+        } else {
+            assert_eq!(
+                balances::Module::<Runtime>::free_balance(&account_id),
+                initial_balance
+            );
+
+            assert!(!<PostById<Runtime>>::contains_key(thread_id, post_id));
+        }
     } else {
         assert_eq!(
             balances::Module::<Runtime>::free_balance(&account_id),
@@ -910,12 +952,10 @@ pub fn edit_post_text_mock(
         ),
         result
     );
+
     if result.is_ok() {
-        let thread = TestForumModule::thread_by_id(category_id, thread_id);
-        assert_eq!(
-            thread.posts.get(&post_id).unwrap().text_hash,
-            Runtime::calculate_hash(new_text.as_slice()),
-        );
+        let post = TestForumModule::post_by_id(thread_id, post_id);
+        assert_eq!(post.text_hash, Runtime::calculate_hash(new_text.as_slice()),);
         assert_eq!(
             System::events().last().unwrap().event,
             TestEvent::forum_mod(RawEvent::PostTextUpdated(
@@ -1005,6 +1045,10 @@ pub fn vote_on_poll_mock(
                 category_id
             ))
         );
+        assert!(TestForumModule::poll_votes_by_thread_id_by_forum_user_id(
+            &thread_id,
+            &forum_user_id
+        ));
     };
     thread_id
 }
@@ -1087,9 +1131,11 @@ pub fn moderate_thread_mock(
             ))
         );
 
+        // If we moderate a thread with no extra post, only the initial post deposit
+        // should remain
         assert_eq!(
             balances::Module::<Runtime>::free_balance(&thread_account_id),
-            BalanceOf::<Runtime>::zero()
+            <Runtime as Trait>::PostDeposit::get()
         );
     }
     thread_id
@@ -1104,6 +1150,7 @@ pub fn moderate_post_mock(
     rationale: Vec<u8>,
     result: DispatchResult,
 ) -> <Runtime as Trait>::PostId {
+    let initial_balance = balances::Module::<Runtime>::free_balance(moderator_id);
     assert_eq!(
         TestForumModule::moderate_post(
             mock_origin(origin),
@@ -1116,8 +1163,7 @@ pub fn moderate_post_mock(
         result
     );
     if result.is_ok() {
-        let thread = <ThreadById<Runtime>>::get(category_id, thread_id);
-        assert!(!thread.posts.contains_key(&post_id));
+        assert!(!<PostById<Runtime>>::contains_key(thread_id, post_id));
         assert_eq!(
             System::events().last().unwrap().event,
             TestEvent::forum_mod(RawEvent::PostModerated(
@@ -1128,7 +1174,17 @@ pub fn moderate_post_mock(
                 thread_id
             ))
         );
-    }
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&moderator_id),
+            initial_balance
+        );
+    } else {
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&moderator_id),
+            initial_balance
+        );
+    };
 
     post_id
 }
@@ -1214,6 +1270,7 @@ pub fn create_genesis_config(data_migration_done: bool) -> GenesisConfig<Runtime
         next_category_id: 1,
         category_counter: 0,
         thread_by_id: vec![],
+        post_by_id: vec![],
         next_thread_id: 1,
         next_post_id: 1,
 

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


+ 10 - 5
runtime-modules/membership/src/benchmarking.rs

@@ -13,6 +13,7 @@ use frame_support::traits::Currency;
 use frame_system::Module as System;
 use frame_system::{EventRecord, RawOrigin};
 use sp_arithmetic::traits::One;
+use sp_arithmetic::Perbill;
 use sp_runtime::traits::Bounded;
 use sp_std::prelude::*;
 
@@ -84,7 +85,7 @@ fn handle_from_id<T: Trait>(id: u32) -> Vec<u8> {
 
 benchmarks! {
     where_clause { where T: balances::Trait, T: Trait, T: MembershipWorkingGroupHelper<<T as
-        frame_system::Trait>::AccountId, <T as common::Trait>::MemberId, <T as common::Trait>::ActorId> }
+        frame_system::Trait>::AccountId, <T as common::membership::Trait>::MemberId, <T as common::membership::Trait>::ActorId> }
     _{  }
 
     buy_membership_without_referrer{
@@ -171,7 +172,7 @@ benchmarks! {
 
         Module::<T>::buy_membership(RawOrigin::Signed(account_id.clone()).into(), params.clone()).unwrap();
 
-        let referral_cut: BalanceOf<T> = 1.into();
+        let referral_cut = 10u8; //percents
 
         Module::<T>::set_referral_cut(RawOrigin::Root.into(), referral_cut).unwrap();
 
@@ -187,11 +188,15 @@ benchmarks! {
     }: buy_membership(RawOrigin::Signed(account_id.clone()), params.clone())
     verify {
 
-        // Ensure membership for given member_id is successfully bought
+        // Ensure membership for given member_id is successfully bought.
         assert_eq!(Module::<T>::members_created(), member_id + T::MemberId::one() + T::MemberId::one());
 
         // Same account id gets reward for being referral.
-        assert_eq!(Balances::<T>::free_balance(&account_id.clone()), free_balance - fee + referral_cut);
+        let referral_cut_balance = Perbill::from_percent(referral_cut.into()) * fee;
+        assert_eq!(
+            Balances::<T>::free_balance(&account_id.clone()),
+            free_balance - fee + referral_cut_balance
+        );
 
         let second_handle_hash = T::Hashing::hash(&second_handle).as_ref().to_vec();
 
@@ -378,7 +383,7 @@ benchmarks! {
     set_referral_cut {
         let member_id = 0;
 
-        let referral_cut: BalanceOf<T> = 1.into();
+        let referral_cut = 10u8;
 
     }: _(RawOrigin::Root, referral_cut)
 

+ 63 - 16
runtime-modules/membership/src/lib.rs

@@ -34,7 +34,8 @@
 //! - [update_accounts](./struct.Module.html#method.update_accounts) - updates member accounts.
 //! - [update_profile_verification](./struct.Module.html#method.update_profile_verification) -
 //! updates member profile verification status.
-//! - [set_referral_cut](./struct.Module.html#method.set_referral_cut) - updates the referral cut.
+//! - [set_referral_cut](./struct.Module.html#method.set_referral_cut) -
+//! updates the referral cut percent value.
 //! - [transfer_invites](./struct.Module.html#method.transfer_invites) - transfers the invites
 //! from one member to another.
 //!
@@ -55,11 +56,12 @@ pub use frame_support::weights::Weight;
 use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure};
 use frame_system::{ensure_root, ensure_signed};
 use sp_arithmetic::traits::{One, Zero};
+use sp_arithmetic::Perbill;
 use sp_runtime::traits::{Hash, Saturating};
 use sp_runtime::SaturatedConversion;
 use sp_std::vec::Vec;
 
-use common::origin::MemberOriginValidator;
+use common::membership::{MemberOriginValidator, MembershipInfoProvider};
 use common::working_group::{WorkingGroupAuthenticator, WorkingGroupBudgetHandler};
 use staking_handler::StakingHandler;
 
@@ -95,7 +97,7 @@ pub trait WeightInfo {
 }
 
 pub trait Trait:
-    frame_system::Trait + balances::Trait + pallet_timestamp::Trait + common::Trait
+    frame_system::Trait + balances::Trait + pallet_timestamp::Trait + common::membership::Trait
 {
     /// Membership module event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
@@ -103,6 +105,9 @@ pub trait Trait:
     /// Defines the default membership fee.
     type DefaultMembershipPrice: Get<BalanceOf<Self>>;
 
+    /// Defines the maximum percent value of the membership fee for the referral cut.
+    type ReferralCutMaximumPercent: Get<u8>;
+
     /// Working group pallet integration.
     type WorkingGroup: common::working_group::WorkingGroupAuthenticator<Self>
         + common::working_group::WorkingGroupBudgetHandler<Self>;
@@ -258,6 +263,12 @@ decl_error! {
         /// balance.
         WorkingGroupBudgetIsNotSufficientForInviting,
 
+        /// Cannot invite a member. The controller account has an existing conflicting lock.
+        ConflictingLock,
+
+        /// Cannot set a referral cut percent value. The limit was exceeded.
+        CannotExceedReferralCutPercentLimit,
+
         /// Staking account contains conflicting stakes.
         ConflictStakesOnAccount,
 
@@ -280,8 +291,8 @@ decl_storage! {
         pub MemberIdByHandleHash get(fn handles) : map hasher(blake2_128_concat)
             Vec<u8> => T::MemberId;
 
-        /// Referral cut to receive during on buying the membership.
-        pub ReferralCut get(fn referral_cut) : BalanceOf<T>;
+        /// Referral cut percent of the membership fee to receive on buying the membership.
+        pub ReferralCut get(fn referral_cut) : u8;
 
         /// Current membership price.
         pub MembershipPrice get(fn membership_price) : BalanceOf<T> =
@@ -324,17 +335,17 @@ decl_storage! {
 
 decl_event! {
     pub enum Event<T> where
-      <T as common::Trait>::MemberId,
+      <T as common::membership::Trait>::MemberId,
       Balance = BalanceOf<T>,
       <T as frame_system::Trait>::AccountId,
       BuyMembershipParameters = BuyMembershipParameters<
           <T as frame_system::Trait>::AccountId,
-          <T as common::Trait>::MemberId,
+          <T as common::membership::Trait>::MemberId,
         >,
-      <T as common::Trait>::ActorId,
+      <T as common::membership::Trait>::ActorId,
       InviteMembershipParameters = InviteMembershipParameters<
           <T as frame_system::Trait>::AccountId,
-          <T as common::Trait>::MemberId,
+          <T as common::membership::Trait>::MemberId,
         >,
     {
         MemberInvited(MemberId, InviteMembershipParameters),
@@ -346,7 +357,7 @@ decl_event! {
         ),
         MemberAccountsUpdated(MemberId, Option<AccountId>, Option<AccountId>),
         MemberVerificationStatusUpdated(MemberId, bool, ActorId),
-        ReferralCutUpdated(Balance),
+        ReferralCutUpdated(u8),
         InvitesTransferred(MemberId, MemberId, u32),
         MembershipPriceUpdated(Balance),
         InitialInvitationBalanceUpdated(Balance),
@@ -365,6 +376,16 @@ decl_module! {
 
         fn deposit_event() = default;
 
+        /// Exports const - default membership fee.
+        const DefaultMembershipPrice: BalanceOf<T> = T::DefaultMembershipPrice::get();
+
+        /// Exports const - maximum percent value of the membership fee for the referral cut.
+        const ReferralCutMaximumPercent: u8 = T::ReferralCutMaximumPercent::get();
+
+        /// Exports const - default balance for the invited member.
+        const DefaultInitialInvitationBalance: BalanceOf<T> =
+            T::DefaultInitialInvitationBalance::get();
+
         /// Non-members can buy membership.
         ///
         /// <weight>
@@ -572,7 +593,7 @@ decl_module! {
             );
         }
 
-        /// Updates membership referral cut. Requires root origin.
+        /// Updates membership referral cut percent value. Requires root origin.
         ///
         /// <weight>
         ///
@@ -582,16 +603,21 @@ decl_module! {
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
         #[weight = WeightInfoMembership::<T>::set_referral_cut()]
-        pub fn set_referral_cut(origin, value: BalanceOf<T>) {
+        pub fn set_referral_cut(origin, percent_value: u8) {
             ensure_root(origin)?;
 
+            ensure!(
+                percent_value <= T::ReferralCutMaximumPercent::get(),
+                Error::<T>::CannotExceedReferralCutPercentLimit
+            );
+
             //
             // == MUTATION SAFE ==
             //
 
-            <ReferralCut<T>>::put(value);
+            ReferralCut::put(percent_value);
 
-            Self::deposit_event(RawEvent::ReferralCutUpdated(value));
+            Self::deposit_event(RawEvent::ReferralCutUpdated(percent_value));
         }
 
         /// Transfers invites from one member to another.
@@ -682,6 +708,14 @@ decl_module! {
                 Error::<T>::WorkingGroupBudgetIsNotSufficientForInviting
             );
 
+            // Check for existing invitation locks.
+            ensure!(
+                T::InvitedMemberStakingHandler::is_account_free_of_conflicting_stakes(
+                    &params.controller_account
+                ),
+                Error::<T>::ConflictingLock,
+            );
+
             //
             // == MUTATION SAFE ==
             //
@@ -1084,12 +1118,15 @@ impl<T: Trait> Module<T> {
         Ok(membership)
     }
 
-    // Calculate current referral bonus. It minimum between membership fee and referral cut.
+    // Calculate current referral bonus as a percent of the membership fee.
     pub(crate) fn get_referral_bonus() -> BalanceOf<T> {
         let membership_fee = Self::membership_price();
         let referral_cut = Self::referral_cut();
 
-        membership_fee.min(referral_cut)
+        let referral_cut = Perbill::from_percent(referral_cut.into()) * membership_fee;
+
+        // Cannot be greater than 100%
+        referral_cut.min(membership_fee)
     }
 
     // Verifies registration of the staking account for ANY member.
@@ -1151,3 +1188,13 @@ impl<T: Trait> MemberOriginValidator<T::Origin, T::MemberId, T::AccountId> for M
         Self::ensure_is_controller_account_for_member(member_id, account_id).is_ok()
     }
 }
+
+impl<T: Trait> MembershipInfoProvider<T> for Module<T> {
+    fn controller_account_id(
+        member_id: common::MemberId<T>,
+    ) -> Result<T::AccountId, DispatchError> {
+        let membership = Self::ensure_membership(member_id)?;
+
+        Ok(membership.controller_account)
+    }
+}

+ 6 - 2
runtime-modules/membership/src/tests/fixtures.rs

@@ -248,10 +248,10 @@ pub(crate) fn increase_total_balance_issuance_using_account_id(account_id: u64,
 
 pub struct SetReferralCutFixture {
     pub origin: RawOrigin<u64>,
-    pub value: u64,
+    pub value: u8,
 }
 
-pub const DEFAULT_REFERRAL_CUT_VALUE: u64 = 100;
+pub const DEFAULT_REFERRAL_CUT_VALUE: u8 = 50;
 
 impl Default for SetReferralCutFixture {
     fn default() -> Self {
@@ -276,6 +276,10 @@ impl SetReferralCutFixture {
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
         Self { origin, ..self }
     }
+
+    pub fn with_referral_cut(self, value: u8) -> Self {
+        Self { value, ..self }
+    }
 }
 
 pub struct TransferInvitesFixture {

+ 22 - 16
runtime-modules/membership/src/tests/mock.rs

@@ -83,7 +83,7 @@ impl pallet_timestamp::Trait for Test {
 }
 
 parameter_types! {
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u32 = 10;
     pub const DefaultMembershipPrice: u64 = 100;
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
@@ -100,7 +100,7 @@ impl balances::Trait for Test {
     type MaxLocks = ();
 }
 
-impl common::Trait for Test {
+impl common::membership::Trait for Test {
     type MemberId = u64;
     type ActorId = u64;
 }
@@ -109,7 +109,10 @@ parameter_types! {
     pub const MaxWorkerNumberLimit: u32 = 3;
     pub const LockId: LockIdentifier = [9; 8];
     pub const DefaultInitialInvitationBalance: u64 = 100;
+    pub const ReferralCutMaximumPercent: u8 = 50;
     pub const MinimumStakeForOpening: u32 = 50;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
@@ -121,15 +124,17 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = Weights;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl LockComparator<u64> for Test {
-    fn are_locks_conflicting(
-        _new_lock: &LockIdentifier,
-        _existing_locks: &[LockIdentifier],
-    ) -> bool {
-        false
+    fn are_locks_conflicting(new_lock: &LockIdentifier, existing_locks: &[LockIdentifier]) -> bool {
+        if *new_lock == InvitedMemberLockId::get() {
+            existing_locks.contains(new_lock)
+        } else {
+            false
+        }
     }
 }
 
@@ -282,7 +287,7 @@ impl WeightInfo for () {
     }
 }
 
-impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         _: u64,
@@ -300,6 +305,7 @@ impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
 impl Trait for Test {
     type Event = TestEvent;
     type DefaultMembershipPrice = DefaultMembershipPrice;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type WorkingGroup = ();
     type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
@@ -331,7 +337,7 @@ impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
 impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         origin: <Test as frame_system::Trait>::Origin,
-        worker_id: &<Test as common::Trait>::ActorId,
+        worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         let raw_origin: Result<RawOrigin<u64>, <Test as frame_system::Trait>::Origin> =
             origin.into();
@@ -351,7 +357,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Test as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Test as common::membership::Trait>::MemberId> {
         LEAD_SET.with(|lead_set| {
             if *lead_set.borrow() {
                 Some(ALICE_MEMBER_ID)
@@ -367,7 +373,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
 
     fn is_worker_account_id(
         _account_id: &<Test as frame_system::Trait>::AccountId,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> bool {
         unimplemented!()
     }
@@ -377,15 +383,15 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
 impl
     crate::MembershipWorkingGroupHelper<
         <Test as frame_system::Trait>::AccountId,
-        <Test as common::Trait>::MemberId,
-        <Test as common::Trait>::ActorId,
+        <Test as common::membership::Trait>::MemberId,
+        <Test as common::membership::Trait>::ActorId,
     > for Test
 {
     fn insert_a_lead(
         _opening_id: u32,
         _caller_id: &<Test as frame_system::Trait>::AccountId,
-        _member_id: <Test as common::Trait>::MemberId,
-    ) -> <Test as common::Trait>::ActorId {
+        _member_id: <Test as common::membership::Trait>::MemberId,
+    ) -> <Test as common::membership::Trait>::ActorId {
         ALICE_MEMBER_ID
     }
 }

+ 69 - 8
runtime-modules/membership/src/tests/mod.rs

@@ -7,12 +7,13 @@ use crate::{Error, Event};
 pub use fixtures::*;
 pub use mock::*;
 
-use common::origin::MemberOriginValidator;
+use common::membership::{MemberOriginValidator, MembershipInfoProvider};
 use common::working_group::WorkingGroupBudgetHandler;
 use common::StakingAccountValidator;
 use frame_support::traits::{LockIdentifier, LockableCurrency, WithdrawReasons};
 use frame_support::{assert_ok, StorageMap, StorageValue};
 use frame_system::RawOrigin;
+use sp_arithmetic::Perbill;
 use sp_runtime::DispatchError;
 
 #[test]
@@ -64,8 +65,8 @@ fn buy_membership_fails_without_enough_balance_with_locked_balance() {
     build_test_externalities().execute_with(|| {
         let initial_balance = DefaultMembershipPrice::get();
         let lock_id = LockIdentifier::default();
-        Balances::set_lock(lock_id, &ALICE_ACCOUNT_ID, 1, WithdrawReasons::all());
         set_alice_free_balance(initial_balance);
+        Balances::set_lock(lock_id, &ALICE_ACCOUNT_ID, 1, WithdrawReasons::all());
 
         assert_dispatch_error_message(
             buy_default_membership_as_alice(),
@@ -317,14 +318,17 @@ fn referral_bonus_calculated_successfully() {
     build_test_externalities().execute_with(|| {
         // it should take minimum of the referral cut and membership fee
         let membership_fee = DefaultMembershipPrice::get();
-        let diff = 10;
+        let diff = 10u8;
 
-        let referral_cut = membership_fee.saturating_sub(diff);
-        <crate::ReferralCut<Test>>::put(referral_cut);
-        assert_eq!(Membership::get_referral_bonus(), referral_cut);
+        let referral_cut = 100 - diff;
+        <crate::ReferralCut>::put(referral_cut);
+        assert_eq!(
+            Membership::get_referral_bonus(),
+            Perbill::from_percent(referral_cut.into()) * membership_fee
+        );
 
-        let referral_cut = membership_fee.saturating_add(diff);
-        <crate::ReferralCut<Test>>::put(referral_cut);
+        let referral_cut = 100 + diff; // Incorrect value
+        <crate::ReferralCut>::put(referral_cut);
         assert_eq!(Membership::get_referral_bonus(), membership_fee);
     });
 }
@@ -343,6 +347,19 @@ fn set_referral_cut_succeeds() {
     });
 }
 
+#[test]
+fn set_referral_fails_exceeding_the_limit() {
+    build_test_externalities().execute_with(|| {
+        let invalid_referral_cut_value = ReferralCutMaximumPercent::get() + 1;
+
+        SetReferralCutFixture::default()
+            .with_referral_cut(invalid_referral_cut_value)
+            .call_and_assert(Err(
+                Error::<Test>::CannotExceedReferralCutPercentLimit.into()
+            ));
+    });
+}
+
 #[test]
 fn set_referral_fails_with_invalid_origin() {
     build_test_externalities().execute_with(|| {
@@ -498,6 +515,24 @@ fn invite_member_succeeds() {
     });
 }
 
+#[test]
+fn invite_member_fails_with_existing_invitation_lock() {
+    build_test_externalities().execute_with(|| {
+        let initial_balance = DefaultMembershipPrice::get();
+        set_alice_free_balance(initial_balance);
+
+        assert_ok!(buy_default_membership_as_alice());
+
+        InviteMembershipFixture::default().call_and_assert(Ok(()));
+
+        <Test as Trait>::WorkingGroup::set_budget(initial_balance);
+
+        InviteMembershipFixture::default()
+            .with_handle(b"bob2".to_vec())
+            .call_and_assert(Err(Error::<Test>::ConflictingLock.into()));
+    });
+}
+
 #[test]
 fn invite_member_fails_with_insufficient_working_group_balance() {
     build_test_externalities().execute_with(|| {
@@ -978,3 +1013,29 @@ fn membership_origin_validator_fails_with_incompatible_account_id_and_member_id(
         assert_eq!(validation_result, Err(error.into()));
     });
 }
+
+#[test]
+fn membership_info_provider_controller_account_id_fails_with_invalid_member_id() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        let invalid_member_id = BOB_MEMBER_ID;
+        let validation_result = Membership::controller_account_id(invalid_member_id);
+
+        assert_eq!(
+            validation_result,
+            Err(Error::<Test>::MemberProfileNotFound.into())
+        );
+    });
+}
+
+#[test]
+fn membership_info_provider_controller_account_id_succeeds() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        let validation_result = Membership::controller_account_id(ALICE_MEMBER_ID);
+
+        assert_eq!(validation_result, Ok(ALICE_ACCOUNT_ID));
+    });
+}

+ 1 - 1
runtime-modules/proposals/codex/src/benchmarking.rs

@@ -326,7 +326,7 @@ benchmarks! {
                 stake_policy: working_group::StakePolicy {
                     stake_amount:
                         <T as working_group::Trait<working_group::Instance1>>
-                            ::MinimumStakeForOpening::get(),
+                            ::MinimumApplicationStake::get(),
                     leaving_unstaking_period: Zero::zero(),
                 },
                 reward_per_block: None,

+ 2 - 3
runtime-modules/proposals/codex/src/lib.rs

@@ -55,7 +55,7 @@ use sp_runtime::SaturatedConversion;
 use sp_std::clone::Clone;
 use sp_std::collections::btree_set::BTreeSet;
 
-use common::origin::MemberOriginValidator;
+use common::membership::MemberOriginValidator;
 use common::MemberId;
 use proposals_discussion::ThreadMode;
 use proposals_engine::{
@@ -110,7 +110,7 @@ pub trait Trait:
     frame_system::Trait
     + proposals_engine::Trait
     + proposals_discussion::Trait
-    + common::Trait
+    + common::membership::Trait
     + staking::Trait
     + proposals_engine::Trait
 {
@@ -887,6 +887,5 @@ impl<T: Trait> ProposalObserver<T> for Module<T> {
         let thread_id = Self::thread_id_by_proposal_id(proposal_id);
 
         proposals_discussion::ThreadById::<T>::remove(thread_id);
-        proposals_discussion::PostThreadIdByPostId::<T>::remove_prefix(thread_id);
     }
 }

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

@@ -11,7 +11,7 @@ use sp_runtime::curve::PiecewiseLinear;
 use sp_runtime::{
     testing::Header,
     traits::{BlakeTwo256, IdentityLookup},
-    DispatchResult, Perbill,
+    DispatchResult, ModuleId, Perbill,
 };
 use sp_staking::SessionIndex;
 use staking_handler::{LockComparator, StakingManager};
@@ -35,6 +35,7 @@ parameter_types! {
     pub const AvailableBlockRatio: Perbill = Perbill::one();
     pub const MinimumPeriod: u64 = 5;
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
+    pub const ReferralCutMaximumPercent: u8 = 50;
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
     pub const CandidateStake: u64 = 100;
 }
@@ -71,7 +72,7 @@ impl_outer_dispatch! {
     }
 }
 
-impl common::Trait for Test {
+impl common::membership::Trait for Test {
     type MemberId = u64;
     type ActorId = u64;
 }
@@ -142,6 +143,7 @@ impl membership::Trait for Test {
     type WeightInfo = Weights;
     type DefaultInitialInvitationBalance = ();
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -160,7 +162,7 @@ impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
 impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!();
     }
@@ -169,7 +171,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Test as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Test as common::membership::Trait>::MemberId> {
         unimplemented!();
     }
 
@@ -179,7 +181,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
 
     fn is_worker_account_id(
         _account_id: &<Test as frame_system::Trait>::AccountId,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> bool {
         unimplemented!()
     }
@@ -187,7 +189,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
 
 parameter_types! {
     pub const DefaultMembershipPrice: u64 = 100;
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u32 = 10;
     pub const DefaultInitialInvitationBalance: u64 = 100;
 }
 
@@ -282,7 +284,7 @@ impl Default for crate::Call<Test> {
     }
 }
 
-impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         _: u64,
@@ -297,7 +299,7 @@ impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
     }
 }
 
-impl common::origin::CouncilOriginValidator<Origin, u64, u64> for () {
+impl common::council::CouncilOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_consulate(origin: Origin, _: u64) -> DispatchResult {
         frame_system::ensure_signed(origin)?;
 
@@ -309,6 +311,9 @@ parameter_types! {
     pub const ThreadTitleLengthLimit: u32 = 200;
     pub const PostLengthLimit: u32 = 2000;
     pub const MaxWhiteListSize: u32 = 20;
+    pub const PostLifeTime: u64 = 10;
+    pub const PostDeposit: u64 = 100;
+    pub const ProposalsDiscussionModuleId: ModuleId = ModuleId(*b"mo:propo");
 }
 
 pub struct MockProposalsDiscussionWeight;
@@ -321,6 +326,9 @@ impl proposals_discussion::Trait for Test {
     type PostId = u64;
     type MaxWhiteListSize = MaxWhiteListSize;
     type WeightInfo = MockProposalsDiscussionWeight;
+    type PostLifeTime = PostLifeTime;
+    type PostDeposit = PostDeposit;
+    type ModuleId = ProposalsDiscussionModuleId;
 }
 
 impl proposals_discussion::WeightInfo for MockProposalsDiscussionWeight {
@@ -328,7 +336,11 @@ impl proposals_discussion::WeightInfo for MockProposalsDiscussionWeight {
         0
     }
 
-    fn update_post() -> Weight {
+    fn update_post(_: u32) -> Weight {
+        0
+    }
+
+    fn delete_post() -> Weight {
         0
     }
 
@@ -360,7 +372,8 @@ parameter_types! {
     pub const MaxWorkerNumberLimit: u32 = 100;
     pub const LockId1: [u8; 8] = [1; 8];
     pub const LockId2: [u8; 8] = [2; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -373,7 +386,8 @@ impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::WeightInfo for WorkingGroupWeightInfo {
@@ -454,7 +468,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<ForumWorkingGroupInstance> for Test {
@@ -466,7 +481,8 @@ impl working_group::Trait<ForumWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
@@ -478,7 +494,8 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 pallet_staking_reward_curve::build! {

+ 1 - 7
runtime-modules/proposals/codex/src/tests/mod.rs

@@ -390,8 +390,6 @@ fn create_funding_request_proposal_common_checks_succeed() {
 #[test]
 fn create_funding_request_proposal_call_fails_with_incorrect_balance() {
     initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance_using_account_id(500000, 1);
-
         let general_proposal_parameters = GeneralProposalParameters::<Test> {
             member_id: 1,
             title: b"title".to_vec(),
@@ -437,8 +435,6 @@ fn create_funding_request_proposal_call_fails_with_incorrect_balance() {
 #[test]
 fn create_funding_request_proposal_call_fails_with_incorrect_number_of_accounts() {
     initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance_using_account_id(500000, 1);
-
         let general_proposal_parameters = GeneralProposalParameters::<Test> {
             member_id: 1,
             title: b"title".to_vec(),
@@ -486,8 +482,6 @@ fn create_funding_request_proposal_call_fails_with_incorrect_number_of_accounts(
 #[test]
 fn create_funding_request_proposal_call_fails_repeated_account() {
     initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance_using_account_id(500000, 1);
-
         let general_proposal_parameters = GeneralProposalParameters::<Test> {
             member_id: 1,
             title: b"title".to_vec(),
@@ -722,7 +716,7 @@ fn run_create_add_working_group_leader_opening_proposal_common_checks_succeed(gr
         let add_opening_parameters = CreateOpeningParameters {
             description: b"some text".to_vec(),
             stake_policy: StakePolicy {
-                stake_amount: <Test as working_group::Trait<working_group::Instance1>>::MinimumStakeForOpening::get() as
+                stake_amount: <Test as working_group::Trait<working_group::Instance1>>::MinimumApplicationStake::get() as
                     u64,
                 leaving_unstaking_period: 0 as u64,
             },

+ 1 - 1
runtime-modules/proposals/codex/src/types.rs

@@ -95,7 +95,7 @@ pub enum ProposalDetails<Balance, BlockNumber, AccountId, WorkerId, OpeningId, P
     SetMembershipLeadInvitationQuota(u32),
 
     /// `Set Referral Cut` proposal
-    SetReferralCut(Balance),
+    SetReferralCut(u8),
 
     /// `Create Blog Post` proposal
     CreateBlogPost(Vec<u8>, Vec<u8>),

+ 4 - 5
runtime-modules/proposals/discussion/Cargo.toml

@@ -11,21 +11,19 @@ sp-std = { package = 'sp-std', default-features = false, git = 'https://github.c
 frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 frame-system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 common = { package = 'pallet-common', default-features = false, path = '../../common'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 
 # Benchmarking dependencies
 frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
 membership = { package = 'pallet-membership', default-features = false, path = '../../membership', optional = true}
 council = { package = 'pallet-council', default-features = false, path = '../../council', optional = true}
 referendum = { package = 'pallet-referendum', default-features = false, path = '../../referendum', optional = true}
 
-
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 membership = { package = 'pallet-membership', default-features = false, path = '../../membership'}
 staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../../staking-handler'}
 council = { package = 'pallet-council', default-features = false, path = '../../council'}
@@ -35,7 +33,6 @@ referendum = { package = 'pallet-referendum', default-features = false, path = '
 default = ['std']
 runtime-benchmarks = [
     'frame-benchmarking',
-    'balances',
 	'membership',
     'referendum',
     'council',
@@ -47,4 +44,6 @@ std = [
 	'frame-support/std',
 	'frame-system/std',
     'common/std',
+    'balances/std',
+    'sp-runtime/std'
 ]

+ 36 - 6
runtime-modules/proposals/discussion/src/benchmarking.rs

@@ -70,7 +70,7 @@ fn assert_last_event<T: Trait>(generic_event: <T as Trait>::Event) {
     assert_eq!(event, &system_event);
 }
 
-fn member_account<T: common::Trait + balances::Trait + membership::Trait>(
+fn member_account<T: common::membership::Trait + balances::Trait + membership::Trait>(
     name: &'static str,
     id: u32,
 ) -> (T::AccountId, T::MemberId) {
@@ -251,7 +251,8 @@ benchmarks! {
 
         let text = vec![0u8; j.try_into().unwrap()];
 
-    }: _ (RawOrigin::Signed(account_id), caller_member_id, thread_id, text.clone())
+        assert!(Balances::<T>::usable_balance(&account_id) >= T::PostDeposit::get());
+    }: _ (RawOrigin::Signed(account_id), caller_member_id, thread_id, text.clone(), true)
     verify {
         let post_id = T::PostId::from(1);
 
@@ -279,9 +280,6 @@ benchmarks! {
         let (_, _) = member_account::<T>("caller_member", 0);
         let (account_id, caller_member_id) = member_account::<T>("caller_member", 1);
 
-        // Worst case scenario there is a council
-        elect_council::<T>(2);
-
         let thread_id = ProposalsDiscussion::<T>::create_thread(
             caller_member_id,
             ThreadMode::Open
@@ -293,7 +291,8 @@ benchmarks! {
             RawOrigin::Signed(account_id.clone()).into(),
             caller_member_id,
             thread_id,
-            vec![0u8]
+            vec![0u8],
+            true
         ).unwrap();
 
         let post_id = T::PostId::from(1);
@@ -306,6 +305,37 @@ benchmarks! {
         assert_last_event::<T>(RawEvent::PostUpdated(post_id, caller_member_id, thread_id, new_text).into());
     }
 
+    delete_post {
+        // We do this to ignore the id 0 because the `Test` runtime
+        // returns 0 as an invalid id but 1 as a valid one
+        let (_, _) = member_account::<T>("caller_member", 0);
+        let (account_id, caller_member_id) = member_account::<T>("caller_member", 1);
+
+        let thread_id = ProposalsDiscussion::<T>::create_thread(
+            caller_member_id,
+            ThreadMode::Open
+        ).unwrap();
+
+        assert!(ThreadById::<T>::contains_key(thread_id), "Thread not created");
+
+        ProposalsDiscussion::<T>::add_post(
+            RawOrigin::Signed(account_id.clone()).into(),
+            caller_member_id,
+            thread_id,
+            vec![0u8],
+            true
+        ).unwrap();
+
+        let post_id = T::PostId::from(1);
+
+        assert!(PostThreadIdByPostId::<T>::contains_key(thread_id, post_id), "Post not created");
+
+    }: _ (RawOrigin::Signed(account_id), caller_member_id, post_id, thread_id, true)
+    verify {
+        assert!(!PostThreadIdByPostId::<T>::contains_key(thread_id, post_id));
+        assert_last_event::<T>(RawEvent::PostDeleted(caller_member_id, thread_id, post_id, true).into());
+    }
+
     change_thread_mode {
         let i in 1 .. T::MaxWhiteListSize::get();
 

+ 165 - 21
runtime-modules/proposals/discussion/src/lib.rs

@@ -12,6 +12,7 @@
 //! - [add_post](./struct.Module.html#method.add_post) - adds a post to an existing discussion thread
 //! - [update_post](./struct.Module.html#method.update_post) - updates existing post
 //! - [change_thread_mode](./struct.Module.html#method.change_thread_mode) - changes thread
+//! - [delete_post](./struct.Module.html#method.delete_post) - Removes thread from storage
 //! permission mode
 //!
 //! ## Public API methods
@@ -26,7 +27,7 @@
 //! use frame_system::ensure_root;
 //! use pallet_proposals_discussion::{self as discussions, ThreadMode};
 //!
-//! pub trait Trait: discussions::Trait + common::Trait {}
+//! pub trait Trait: discussions::Trait + common::membership::Trait {}
 //!
 //! decl_module! {
 //!     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
@@ -54,25 +55,35 @@ mod tests;
 mod types;
 
 use frame_support::dispatch::{DispatchError, DispatchResult};
+use frame_support::sp_runtime::ModuleId;
 use frame_support::sp_runtime::SaturatedConversion;
 use frame_support::traits::Get;
+use frame_support::traits::{Currency, ExistenceRequirement};
 use frame_support::{
     decl_error, decl_event, decl_module, decl_storage, ensure, weights::Weight, Parameter,
 };
+use sp_runtime::traits::{AccountIdConversion, Saturating};
 use sp_std::clone::Clone;
 use sp_std::vec::Vec;
 
-use common::origin::{CouncilOriginValidator, MemberOriginValidator};
+use common::council::CouncilOriginValidator;
+use common::membership::MemberOriginValidator;
 use common::MemberId;
 use types::{DiscussionPost, DiscussionThread};
 
 pub use types::ThreadMode;
 
+/// Balance alias for `balances` module.
+pub type BalanceOf<T> = <T as balances::Trait>::Balance;
+
+type Balances<T> = balances::Module<T>;
+
 /// Proposals discussion WeightInfo.
 /// Note: This was auto generated through the benchmark CLI using the `--weight-trait` flag
 pub trait WeightInfo {
-    fn add_post(i: u32) -> Weight; // Note: since parameter doesn't affect weight it's discarded
-    fn update_post() -> Weight; // Note: since parameter doesn't affect weight it's discarded
+    fn add_post(j: u32) -> Weight;
+    fn update_post(j: u32) -> Weight;
+    fn delete_post() -> Weight;
     fn change_thread_mode(i: u32) -> Weight;
 }
 
@@ -97,6 +108,9 @@ decl_event!(
 
         /// Emits on thread mode change.
         ThreadModeChanged(ThreadId, ThreadMode<MemberId>, MemberId),
+
+        /// Emits on post deleted
+        PostDeleted(MemberId, ThreadId, PostId, bool),
     }
 );
 
@@ -107,7 +121,7 @@ pub trait CouncilMembership<AccountId, MemberId> {
 }
 
 /// 'Proposal discussion' substrate module Trait
-pub trait Trait: frame_system::Trait + common::Trait {
+pub trait Trait: frame_system::Trait + balances::Trait + common::membership::Trait {
     /// Discussion event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
@@ -132,6 +146,15 @@ pub trait Trait: frame_system::Trait + common::Trait {
 
     /// Weight information for extrinsics in this pallet.
     type WeightInfo: WeightInfo;
+
+    /// Fee for creating a post
+    type PostDeposit: Get<Self::Balance>;
+
+    /// The proposal_discussion module Id, used to derive the account Id to hold the thread bounty
+    type ModuleId: Get<ModuleId>;
+
+    /// Maximum number of blocks before a post can be erased by anyone
+    type PostLifeTime: Get<Self::BlockNumber>;
 }
 
 decl_error! {
@@ -154,6 +177,12 @@ decl_error! {
 
         /// Max allowed authors list limit exceeded.
         MaxWhiteListSizeExceeded,
+
+        /// Account has insufficient balance to create a post
+        InsufficientBalanceForPost,
+
+        /// Account can't delete post at the moment
+        CannotDeletePost,
     }
 }
 
@@ -170,7 +199,7 @@ decl_storage! {
         /// Map thread id and post id to corresponding post.
         pub PostThreadIdByPostId:
             double_map hasher(blake2_128_concat) T::ThreadId, hasher(blake2_128_concat) T::PostId =>
-                DiscussionPost<MemberId<T>>;
+                DiscussionPost<MemberId<T>, BalanceOf<T>, T::BlockNumber>;
 
         /// Count of all posts that have been created.
         pub PostCount get(fn post_count): u64;
@@ -191,21 +220,20 @@ decl_module! {
         /// <weight>
         ///
         /// ## Weight
-        /// `O (W)` where:
-        /// - `W` is the number of whitelisted members for `thread_id`
+        /// `O (L)` where:
+        /// - `L` is the length of `text`
         /// - DB:
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = WeightInfoDiscussion::<T>::add_post(
-            T::MaxWhiteListSize::get(),
-        )]
+        #[weight = WeightInfoDiscussion::<T>::add_post(text.len().saturated_into())]
         pub fn add_post(
             origin,
             post_author_id: MemberId<T>,
-            thread_id : T::ThreadId,
-            text : Vec<u8>
+            thread_id: T::ThreadId,
+            text: Vec<u8>,
+            editable: bool
         ) {
-            T::AuthorOriginValidator::ensure_member_controller_account_origin(
+            let account_id = T::AuthorOriginValidator::ensure_member_controller_account_origin(
                 origin.clone(),
                 post_author_id,
             )?;
@@ -214,31 +242,106 @@ decl_module! {
 
             Self::ensure_thread_mode(origin, post_author_id, thread_id)?;
 
+            // Ensure account has enough funds
+            if editable {
+                ensure!(
+                    Balances::<T>::usable_balance(&account_id) >= T::PostDeposit::get(),
+                    Error::<T>::InsufficientBalanceForPost,
+                );
+            }
+
             // mutation
 
+            if editable {
+                Self::transfer_to_state_cleanup_treasury_account(
+                    T::PostDeposit::get(),
+                    thread_id,
+                    &account_id,
+                )?;
+            }
+
             let next_post_count_value = Self::post_count() + 1;
             let new_post_id = next_post_count_value;
+            let post_id = T::PostId::from(new_post_id);
 
-            let new_post = DiscussionPost {
-                author_id: post_author_id,
-            };
+            if editable {
+                let new_post = DiscussionPost {
+                    author_id: post_author_id,
+                    cleanup_pay_off: T::PostDeposit::get(),
+                    last_edited: frame_system::Module::<T>::block_number(),
+                };
+
+                <PostThreadIdByPostId<T>>::insert(thread_id, post_id, new_post);
+            }
 
-            let post_id = T::PostId::from(new_post_id);
-            <PostThreadIdByPostId<T>>::insert(thread_id, post_id, new_post);
             PostCount::put(next_post_count_value);
             Self::deposit_event(RawEvent::PostCreated(post_id, post_author_id, thread_id, text));
        }
 
+        /// Remove post from storage, with the last parameter indicating whether to also hide it
+        /// in the UI.
+        ///
+        /// <weight>
+        ///
+        /// ## Weight
+        /// `O (1)`
+        /// - DB:
+        ///    - O(1) doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight = WeightInfoDiscussion::<T>::delete_post()]
+        pub fn delete_post(
+            origin,
+            deleter_id: MemberId<T>,
+            post_id : T::PostId,
+            thread_id: T::ThreadId,
+            hide: bool,
+        ) {
+            let account_id = T::AuthorOriginValidator::ensure_member_controller_account_origin(
+                origin.clone(),
+                deleter_id,
+            )?;
+
+            ensure!(
+                <PostThreadIdByPostId<T>>::contains_key(thread_id, post_id),
+                Error::<T>::PostDoesntExist
+            );
+
+            T::AuthorOriginValidator::ensure_member_controller_account_origin(
+                origin,
+                deleter_id,
+            )?;
+
+            let post = <PostThreadIdByPostId<T>>::get(thread_id, post_id);
+            if !Self::anyone_can_delete_post(thread_id, post_id) {
+                ensure!(
+                    post.author_id == deleter_id,
+                    Error::<T>::CannotDeletePost
+                );
+            }
+
+            // mutation
+
+            Self::pay_off(
+                thread_id,
+                T::PostDeposit::get(),
+                &account_id,
+            )?;
+
+            <PostThreadIdByPostId<T>>::remove(thread_id, post_id);
+            Self::deposit_event(RawEvent::PostDeleted(deleter_id, thread_id, post_id, hide));
+        }
+
         /// Updates a post with author origin check. Update attempts number is limited.
         ///
         /// <weight>
         ///
         /// ## Weight
-        /// `O (1)` doesn't depend on the state or parameters
+        /// `O (L)` where:
+        /// - `L` is the length of `text`
         /// - DB:
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = WeightInfoDiscussion::<T>::update_post()]
+        #[weight = WeightInfoDiscussion::<T>::update_post(text.len().saturated_into())]
         pub fn update_post(
             origin,
             thread_id: T::ThreadId,
@@ -260,6 +363,11 @@ decl_module! {
 
             // mutation
 
+            <PostThreadIdByPostId<T>>::mutate(
+                thread_id,
+                post_id,
+                |new_post| new_post.last_edited = frame_system::Module::<T>::block_number()
+            );
             Self::deposit_event(RawEvent::PostUpdated(post_id, post_author_id, thread_id, text));
        }
 
@@ -366,6 +474,42 @@ impl<T: Trait> Module<T> {
         <frame_system::Module<T>>::block_number()
     }
 
+    fn anyone_can_delete_post(thread_id: T::ThreadId, post_id: T::PostId) -> bool {
+        let thread_exists = <ThreadById<T>>::contains_key(thread_id);
+        let post = <PostThreadIdByPostId<T>>::get(thread_id, post_id);
+        !thread_exists
+            && frame_system::Module::<T>::block_number().saturating_sub(post.last_edited)
+                >= T::PostLifeTime::get()
+    }
+
+    fn pay_off(
+        thread_id: T::ThreadId,
+        amount: BalanceOf<T>,
+        account_id: &T::AccountId,
+    ) -> DispatchResult {
+        let state_cleanup_treasury_account = T::ModuleId::get().into_sub_account(thread_id);
+        <Balances<T> as Currency<T::AccountId>>::transfer(
+            &state_cleanup_treasury_account,
+            account_id,
+            amount,
+            ExistenceRequirement::AllowDeath,
+        )
+    }
+
+    fn transfer_to_state_cleanup_treasury_account(
+        amount: BalanceOf<T>,
+        thread_id: T::ThreadId,
+        account_id: &T::AccountId,
+    ) -> DispatchResult {
+        let state_cleanup_treasury_account = T::ModuleId::get().into_sub_account(thread_id);
+        <Balances<T> as Currency<T::AccountId>>::transfer(
+            account_id,
+            &state_cleanup_treasury_account,
+            amount,
+            ExistenceRequirement::AllowDeath,
+        )
+    }
+
     fn ensure_thread_mode(
         origin: T::Origin,
         thread_author_id: MemberId<T>,

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

@@ -9,7 +9,7 @@ use sp_core::H256;
 use sp_runtime::{
     testing::Header,
     traits::{BlakeTwo256, IdentityLookup},
-    DispatchResult, Perbill,
+    DispatchResult, ModuleId, Perbill,
 };
 
 use crate::CouncilOriginValidator;
@@ -68,15 +68,19 @@ impl_outer_event! {
 }
 
 parameter_types! {
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u32 = 10;
     pub const TransferFee: u32 = 0;
     pub const CreationFee: u32 = 0;
     pub const MaxWhiteListSize: u32 = 4;
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
+    pub const ReferralCutMaximumPercent: u8 = 50;
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
     pub const CandidateStake: u64 = 100;
+    pub const PostLifeTime: u64 = 10;
+    pub const PostDeposit: u64 = 100;
+    pub const ProposalsDiscussionModuleId: ModuleId = ModuleId(*b"mo:propo");
 }
 
 // Weights info stub
@@ -148,7 +152,7 @@ impl balances::Trait for Test {
     type MaxLocks = ();
 }
 
-impl common::Trait for Test {
+impl common::membership::Trait for Test {
     type MemberId = u64;
     type ActorId = u64;
 }
@@ -160,6 +164,7 @@ impl membership::Trait for Test {
     type WeightInfo = Weights;
     type DefaultInitialInvitationBalance = ();
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -187,7 +192,7 @@ impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
 impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!();
     }
@@ -196,7 +201,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Test as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Test as common::membership::Trait>::MemberId> {
         unimplemented!();
     }
 
@@ -206,7 +211,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
 
     fn is_worker_account_id(
         _account_id: &<Test as frame_system::Trait>::AccountId,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> bool {
         unimplemented!()
     }
@@ -220,6 +225,9 @@ impl crate::Trait for Test {
     type PostId = u64;
     type MaxWhiteListSize = MaxWhiteListSize;
     type WeightInfo = ();
+    type PostLifeTime = PostLifeTime;
+    type PostDeposit = PostDeposit;
+    type ModuleId = ProposalsDiscussionModuleId;
 }
 
 impl WeightInfo for () {
@@ -227,7 +235,11 @@ impl WeightInfo for () {
         0
     }
 
-    fn update_post() -> Weight {
+    fn update_post(_: u32) -> Weight {
+        0
+    }
+
+    fn delete_post() -> Weight {
         0
     }
 
@@ -236,17 +248,21 @@ impl WeightInfo for () {
     }
 }
 
-impl MemberOriginValidator<Origin, u64, u64> for () {
+impl MemberOriginValidator<Origin, u64, u128> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         actor_id: u64,
-    ) -> Result<u64, DispatchError> {
+    ) -> Result<u128, DispatchError> {
         if frame_system::ensure_none(origin.clone()).is_ok() {
             return Ok(1);
         }
 
         if actor_id < 12 {
-            return Ok(actor_id);
+            if let Ok(account_id) = frame_system::ensure_signed(origin) {
+                return Ok(account_id.into());
+            } else {
+                return Ok(actor_id.into());
+            }
         }
 
         if actor_id == 12 && frame_system::ensure_signed(origin).unwrap_or_default() == 12 {
@@ -256,7 +272,7 @@ impl MemberOriginValidator<Origin, u64, u64> for () {
         Err(DispatchError::Other("Invalid author"))
     }
 
-    fn is_member_controller_account(_member_id: &u64, _account_id: &u64) -> bool {
+    fn is_member_controller_account(_member_id: &u64, _account_id: &u128) -> bool {
         unimplemented!()
     }
 }
@@ -347,7 +363,7 @@ impl council::WeightInfo for CouncilWeightInfo {
 }
 
 pub struct CouncilMock;
-impl CouncilOriginValidator<Origin, u64, u64> for CouncilMock {
+impl CouncilOriginValidator<Origin, u64, u128> for CouncilMock {
     fn ensure_member_consulate(origin: Origin, actor_id: u64) -> DispatchResult {
         if actor_id == 2 && frame_system::ensure_signed(origin).unwrap_or_default() == 2 {
             return Ok(());
@@ -365,7 +381,7 @@ impl frame_system::Trait for Test {
     type BlockNumber = u64;
     type Hash = H256;
     type Hashing = BlakeTwo256;
-    type AccountId = u64;
+    type AccountId = u128;
     type Lookup = IdentityLookup<Self::AccountId>;
     type Header = Header;
     type Event = TestEvent;

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

@@ -20,7 +20,14 @@ impl EventFixture {
             })
             .collect::<Vec<EventRecord<_, _>>>();
 
-        assert_eq!(System::events(), expected_events);
+        let actual_events: Vec<_> = System::events()
+            .into_iter()
+            .filter(|e| match e.event {
+                TestEvent::discussion(..) => true,
+                _ => false,
+            })
+            .collect();
+        assert_eq!(actual_events, expected_events);
     }
 }
 
@@ -49,7 +56,11 @@ fn assert_thread_content(thread_entry: TestThreadEntry, post_entries: Vec<TestPo
     for post_entry in post_entries {
         let actual_post =
             <PostThreadIdByPostId<Test>>::get(thread_entry.thread_id, post_entry.post_id);
-        let expected_post = DiscussionPost { author_id: 1 };
+        let expected_post = DiscussionPost {
+            author_id: 1,
+            cleanup_pay_off: <Test as Trait>::PostDeposit::get(),
+            last_edited: frame_system::Module::<Test>::block_number(),
+        };
 
         assert_eq!(actual_post, expected_post);
     }
@@ -90,10 +101,13 @@ impl DiscussionFixture {
 
 struct PostFixture {
     pub text: Vec<u8>,
-    pub origin: RawOrigin<u64>,
+    pub origin: RawOrigin<u128>,
     pub thread_id: u64,
     pub post_id: Option<u64>,
     pub author_id: u64,
+    pub initial_balance: u64,
+    pub account_id: u128,
+    pub editable: bool,
 }
 
 impl PostFixture {
@@ -104,10 +118,17 @@ impl PostFixture {
             thread_id,
             origin: RawOrigin::Signed(1),
             post_id: None,
+            initial_balance: <Test as Trait>::PostDeposit::get(),
+            account_id: 1,
+            editable: true,
         }
     }
 
-    fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+    fn with_account_id(self, account_id: u128) -> Self {
+        PostFixture { account_id, ..self }
+    }
+
+    fn with_origin(self, origin: RawOrigin<u128>) -> Self {
         PostFixture { origin, ..self }
     }
 
@@ -115,6 +136,10 @@ impl PostFixture {
         PostFixture { author_id, ..self }
     }
 
+    fn with_editable(self, editable: bool) -> Self {
+        PostFixture { editable, ..self }
+    }
+
     fn change_thread_id(self, thread_id: u64) -> Self {
         PostFixture { thread_id, ..self }
     }
@@ -126,18 +151,70 @@ impl PostFixture {
         }
     }
 
+    fn with_initial_balance(self, initial_balance: u64) -> Self {
+        PostFixture {
+            initial_balance,
+            ..self
+        }
+    }
+
     fn add_post_and_assert(&mut self, result: DispatchResult) -> Option<u64> {
+        balances::Module::<Test>::make_free_balance_be(&self.account_id, self.initial_balance);
+        let initial_balance = balances::Module::<Test>::usable_balance(&self.account_id);
         let add_post_result = Discussions::add_post(
             self.origin.clone().into(),
             self.author_id,
             self.thread_id,
             self.text.clone(),
+            self.editable,
+        );
+
+        assert_eq!(add_post_result, result);
+
+        if result.is_ok() {
+            let post_id = <PostCount>::get();
+            self.post_id = Some(post_id);
+            if self.editable {
+                assert!(<PostThreadIdByPostId<Test>>::contains_key(
+                    self.thread_id,
+                    post_id
+                ));
+                assert_eq!(
+                    balances::Module::<Test>::usable_balance(&self.account_id),
+                    initial_balance - <Test as Trait>::PostDeposit::get()
+                );
+            } else {
+                assert!(!<PostThreadIdByPostId<Test>>::contains_key(
+                    self.thread_id,
+                    post_id
+                ));
+            }
+        }
+
+        self.post_id
+    }
+
+    fn delete_post_and_assert(&mut self, result: DispatchResult) -> Option<u64> {
+        let initial_balance = balances::Module::<Test>::usable_balance(&self.account_id);
+        let add_post_result = Discussions::delete_post(
+            self.origin.clone().into(),
+            self.author_id,
+            self.post_id.unwrap(),
+            self.thread_id,
+            true,
         );
 
         assert_eq!(add_post_result, result);
 
         if result.is_ok() {
-            self.post_id = Some(<PostCount>::get());
+            assert_eq!(
+                balances::Module::<Test>::usable_balance(&self.account_id),
+                initial_balance + <Test as Trait>::PostDeposit::get()
+            );
+            assert!(!<PostThreadIdByPostId<Test>>::contains_key(
+                self.thread_id,
+                self.post_id.unwrap()
+            ));
         }
 
         self.post_id
@@ -169,7 +246,7 @@ fn create_discussion_call_succeeds() {
 }
 
 #[test]
-fn create_post_call_succeeds() {
+fn create_post_call_succeeds_editable() {
     initial_test_ext().execute_with(|| {
         let discussion_fixture = DiscussionFixture::default();
 
@@ -177,14 +254,157 @@ fn create_post_call_succeeds() {
             .create_discussion_and_assert(Ok(1))
             .unwrap();
 
-        let mut post_fixture = PostFixture::default_for_thread(thread_id);
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+    });
+}
+
+#[test]
+fn delete_post_call_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+        post_fixture.delete_post_and_assert(Ok(()));
+    });
+}
+
+#[test]
+fn delete_post_call_fails_with_invalid_user() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+        post_fixture
+            .with_origin(RawOrigin::Signed(12))
+            .with_author(13)
+            .with_account_id(13)
+            .delete_post_and_assert(Err(DispatchError::Other("Invalid author")));
+    });
+}
+
+#[test]
+fn delete_post_call_fails_with_incorrect_user() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+        post_fixture
+            .with_origin(RawOrigin::Signed(10))
+            .with_author(10)
+            .with_account_id(10)
+            .delete_post_and_assert(Err(Error::<Test>::CannotDeletePost.into()));
+    });
+}
+
+#[test]
+fn delete_post_call_fails_with_any_user_before_post_lifetime() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+
+        let current_block = frame_system::Module::<Test>::block_number();
+
+        run_to_block(current_block + <Test as Trait>::PostLifeTime::get());
+
+        post_fixture
+            .with_origin(RawOrigin::Signed(10))
+            .with_author(10)
+            .with_account_id(10)
+            .delete_post_and_assert(Err(Error::<Test>::CannotDeletePost.into()));
+    });
+}
+
+#[test]
+fn delete_post_call_succeds_with_any_user_after_post_lifetime() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+
+        // Erasing manually to prevent circular dependency with proposal codex
+        <ThreadById<Test>>::remove(thread_id);
+
+        let current_block = frame_system::Module::<Test>::block_number();
+
+        run_to_block(current_block + <Test as Trait>::PostLifeTime::get());
+
+        post_fixture
+            .with_origin(RawOrigin::Signed(10))
+            .with_author(10)
+            .with_account_id(10)
+            .delete_post_and_assert(Ok(()));
+    });
+}
+
+#[test]
+fn create_post_call_fails_editable_insufficient_funds() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id)
+            .with_editable(true)
+            .with_initial_balance(<Test as Trait>::PostDeposit::get() - 1);
+
+        post_fixture.add_post_and_assert(Err(Error::<Test>::InsufficientBalanceForPost.into()));
+    });
+}
+
+#[test]
+fn create_post_call_succeeds_non_editable() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id)
+            .with_editable(false)
+            .with_initial_balance(0);
 
         post_fixture.add_post_and_assert(Ok(()));
     });
 }
 
 struct ChangeThreadModeFixture {
-    pub origin: RawOrigin<u64>,
+    pub origin: RawOrigin<u128>,
     pub thread_id: u64,
     pub member_id: u64,
     pub mode: ThreadMode<u64>,
@@ -208,7 +428,7 @@ impl ChangeThreadModeFixture {
         Self { member_id, ..self }
     }
 
-    fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+    fn with_origin(self, origin: RawOrigin<u128>) -> Self {
         Self { origin, ..self }
     }
 
@@ -264,6 +484,7 @@ fn update_post_call_fails_because_of_the_wrong_author() {
 
         let mut post_fixture = PostFixture::default_for_thread(thread_id)
             .with_origin(RawOrigin::Signed(12))
+            .with_account_id(12)
             .with_author(12);
 
         post_fixture.add_post_and_assert(Ok(()));
@@ -489,6 +710,7 @@ fn create_post_call_succeeds_with_closed_mode_by_councilor() {
 
         let mut post_fixture = PostFixture::default_for_thread(thread_id)
             .with_origin(RawOrigin::Signed(2))
+            .with_account_id(2)
             .with_author(2);
 
         post_fixture.add_post_and_assert(Ok(()));
@@ -507,6 +729,7 @@ fn create_post_call_succeeds_with_closed_mode_by_white_listed_member() {
 
         let mut post_fixture = PostFixture::default_for_thread(thread_id)
             .with_origin(RawOrigin::Signed(11))
+            .with_account_id(11)
             .with_author(11);
 
         post_fixture.add_post_and_assert(Ok(()));

+ 7 - 1
runtime-modules/proposals/discussion/src/types.rs

@@ -22,9 +22,15 @@ pub struct DiscussionThread<ThreadAuthorId, BlockNumber, MemberId> {
 /// Post for the discussion thread
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
-pub struct DiscussionPost<PostAuthorId> {
+pub struct DiscussionPost<PostAuthorId, Balance, BlockNumber> {
     /// Author of the post.
     pub author_id: PostAuthorId,
+
+    /// Cleanup pay off
+    pub cleanup_pay_off: Balance,
+
+    /// Last time post was created/edited
+    pub last_edited: BlockNumber,
 }
 
 /// Discussion thread permission modes.

+ 4 - 3
runtime-modules/proposals/engine/src/lib.rs

@@ -74,7 +74,7 @@
 //! use codec::Encode;
 //! use pallet_proposals_engine::{self as engine, ProposalParameters, ProposalCreationParameters};
 //!
-//! pub trait Trait: engine::Trait + common::Trait {}
+//! pub trait Trait: engine::Trait + common::membership::Trait {}
 //!
 //! decl_module! {
 //!     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
@@ -152,7 +152,8 @@ use frame_system::{ensure_root, RawOrigin};
 use sp_arithmetic::traits::{SaturatedConversion, Saturating, Zero};
 use sp_std::vec::Vec;
 
-use common::origin::{CouncilOriginValidator, MemberOriginValidator};
+use common::council::CouncilOriginValidator;
+use common::membership::MemberOriginValidator;
 use common::{MemberId, StakingAccountValidator};
 use staking_handler::StakingHandler;
 
@@ -174,7 +175,7 @@ type WeightInfoEngine<T> = <T as Trait>::WeightInfo;
 
 /// Proposals engine trait.
 pub trait Trait:
-    frame_system::Trait + pallet_timestamp::Trait + common::Trait + balances::Trait
+    frame_system::Trait + pallet_timestamp::Trait + common::membership::Trait + balances::Trait
 {
     /// Engine event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;

+ 9 - 7
runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -156,7 +156,7 @@ impl referendum::WeightInfo for ReferendumWeightInfo {
 }
 
 parameter_types! {
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u32 = 10;
 }
 
 impl balances::Trait for Test {
@@ -181,11 +181,12 @@ parameter_types! {
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
+    pub const ReferralCutMaximumPercent: u8 = 50;
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
     pub const CandidateStake: u64 = 100;
 }
 
-impl common::Trait for Test {
+impl common::membership::Trait for Test {
     type MemberId = u64;
     type ActorId = u64;
 }
@@ -256,6 +257,7 @@ impl membership::Trait for Test {
     type WeightInfo = Weights;
     type DefaultInitialInvitationBalance = ();
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -274,7 +276,7 @@ impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
 impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!();
     }
@@ -283,7 +285,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Test as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Test as common::membership::Trait>::MemberId> {
         unimplemented!();
     }
 
@@ -293,7 +295,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
 
     fn is_worker_account_id(
         _account_id: &<Test as frame_system::Trait>::AccountId,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> bool {
         unimplemented!()
     }
@@ -373,7 +375,7 @@ impl Default for proposals::Call<Test> {
     }
 }
 
-impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         _account_id: u64,
@@ -388,7 +390,7 @@ impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
     }
 }
 
-impl common::origin::CouncilOriginValidator<Origin, u64, u64> for () {
+impl common::council::CouncilOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_consulate(origin: Origin, _: u64) -> DispatchResult {
         frame_system::ensure_signed(origin)?;
 

+ 3 - 3
runtime-modules/proposals/engine/src/tests/mod.rs

@@ -747,7 +747,7 @@ fn veto_proposal_succeeds_during_voting_period() {
 
         assert!(matches!(
             <Proposals<Test>>::get(proposal_id).status,
-            ProposalStatus::Active{..}
+            ProposalStatus::Active { .. }
         ));
 
         let veto_proposal = VetoProposalFixture::new(proposal_id);
@@ -791,7 +791,7 @@ fn veto_proposal_succeeds_during_grace_period() {
 
         assert!(matches!(
             <Proposals<Test>>::get(proposal_id).status,
-            ProposalStatus::PendingExecution{..}
+            ProposalStatus::PendingExecution { .. }
         ));
 
         let veto_proposal = VetoProposalFixture::new(proposal_id);
@@ -834,7 +834,7 @@ fn veto_proposal_succeeds_during_pending_constitutionality() {
 
         assert!(matches!(
             <Proposals<Test>>::get(proposal_id).status,
-            ProposalStatus::PendingConstitutionality{..}
+            ProposalStatus::PendingConstitutionality { .. }
         ));
 
         let veto_proposal = VetoProposalFixture::new(proposal_id);

+ 1 - 1
runtime-modules/proposals/engine/src/types/proposal_statuses.rs

@@ -52,7 +52,7 @@ impl<BlockNumber: Clone> ProposalStatus<BlockNumber> {
 
     /// Determines whether a proposal in pending execution status.
     pub fn is_pending_execution_proposal(&self) -> bool {
-        matches!(self.clone(), ProposalStatus::PendingExecution{..})
+        matches!(self.clone(), ProposalStatus::PendingExecution { .. })
     }
 
     /// Determines whether a proposal in pending contitutionality status.

+ 19 - 8
runtime-modules/referendum/src/benchmarking.rs

@@ -52,7 +52,10 @@ fn funded_account<T: Trait<I>, I: Instance>(name: &'static str, id: u32) -> T::A
 fn make_multiple_votes_for_multiple_options<
     T: Trait<I>
         + membership::Trait
-        + OptionCreator<<T as frame_system::Trait>::AccountId, <T as common::Trait>::MemberId>,
+        + OptionCreator<
+            <T as frame_system::Trait>::AccountId,
+            <T as common::membership::Trait>::MemberId,
+        >,
     I: Instance,
 >(
     number_of_options: u32,
@@ -88,7 +91,10 @@ fn make_multiple_votes_for_multiple_options<
 fn vote_for<
     T: Trait<I>
         + membership::Trait
-        + OptionCreator<<T as frame_system::Trait>::AccountId, <T as common::Trait>::MemberId>,
+        + OptionCreator<
+            <T as frame_system::Trait>::AccountId,
+            <T as common::membership::Trait>::MemberId,
+        >,
     I: Instance,
 >(
     name: &'static str,
@@ -136,7 +142,10 @@ fn vote_for<
 fn create_account_and_vote<
     T: Trait<I>
         + membership::Trait
-        + OptionCreator<<T as frame_system::Trait>::AccountId, <T as common::Trait>::MemberId>,
+        + OptionCreator<
+            <T as frame_system::Trait>::AccountId,
+            <T as common::membership::Trait>::MemberId,
+        >,
     I: Instance,
 >(
     name: &'static str,
@@ -247,8 +256,10 @@ fn member_funded_account<T: Trait<I> + membership::Trait, I: Instance>(
 
 fn add_and_reveal_multiple_votes_and_add_extra_unrevealed_vote<
     T: Trait<I>
-        + OptionCreator<<T as frame_system::Trait>::AccountId, <T as common::Trait>::MemberId>
-        + membership::Trait,
+        + OptionCreator<
+            <T as frame_system::Trait>::AccountId,
+            <T as common::membership::Trait>::MemberId,
+        > + membership::Trait,
     I: Instance,
 >(
     target_winners: u32,
@@ -324,7 +335,7 @@ fn add_and_reveal_multiple_votes_and_add_extra_unrevealed_vote<
 benchmarks_instance! {
     where_clause {
         where T: OptionCreator<<T as frame_system::Trait>::AccountId,
-        <T as common::Trait>::MemberId>,
+        <T as common::membership::Trait>::MemberId>,
         T: membership::Trait
     }
     _ { }
@@ -686,12 +697,12 @@ mod tests {
     impl
         OptionCreator<
             <Runtime as frame_system::Trait>::AccountId,
-            <Runtime as common::Trait>::MemberId,
+            <Runtime as common::membership::Trait>::MemberId,
         > for Runtime
     {
         fn create_option(
             _: <Runtime as frame_system::Trait>::AccountId,
-            _: <Runtime as common::Trait>::MemberId,
+            _: <Runtime as common::membership::Trait>::MemberId,
         ) {
         }
     }

+ 17 - 13
runtime-modules/referendum/src/lib.rs

@@ -33,7 +33,7 @@
 // used dependencies
 use codec::{Codec, Decode, Encode};
 use core::marker::PhantomData;
-use frame_support::traits::{EnsureOrigin, Get, WithdrawReason};
+use frame_support::traits::{EnsureOrigin, Get};
 use frame_support::weights::Weight;
 use frame_support::{
     decl_error, decl_event, decl_module, decl_storage, ensure, error::BadOrigin, Parameter,
@@ -134,17 +134,20 @@ pub struct CastVote<Hash, Currency, MemberId> {
 
 // types simplifying access to common structs and enums
 pub type BalanceOf<T> = <T as balances::Trait>::Balance;
-pub type CastVoteOf<T> =
-    CastVote<<T as frame_system::Trait>::Hash, BalanceOf<T>, <T as common::Trait>::MemberId>;
+pub type CastVoteOf<T> = CastVote<
+    <T as frame_system::Trait>::Hash,
+    BalanceOf<T>,
+    <T as common::membership::Trait>::MemberId,
+>;
 pub type ReferendumStageVotingOf<T> =
     ReferendumStageVoting<<T as frame_system::Trait>::BlockNumber>;
 pub type ReferendumStageRevealingOf<T, I> = ReferendumStageRevealing<
     <T as frame_system::Trait>::BlockNumber,
-    <T as common::Trait>::MemberId,
+    <T as common::membership::Trait>::MemberId,
     <T as Trait<I>>::VotePower,
 >;
 pub type OptionResultOf<T, I> =
-    OptionResult<<T as common::Trait>::MemberId, <T as Trait<I>>::VotePower>;
+    OptionResult<<T as common::membership::Trait>::MemberId, <T as Trait<I>>::VotePower>;
 
 // types aliases for check functions return values
 pub type CanRevealResult<T, I> = (
@@ -207,7 +210,7 @@ pub trait ReferendumManager<Origin, AccountId, MemberId, Hash> {
 
 /// The main Referendum module's trait.
 pub trait Trait<I: Instance = DefaultInstance>:
-    frame_system::Trait + common::Trait + balances::Trait
+    frame_system::Trait + common::membership::Trait + balances::Trait
 {
     /// The overarching event type.
     type Event: From<Event<Self, I>> + Into<<Self as frame_system::Trait>::Event>;
@@ -295,7 +298,7 @@ decl_event! {
         <T as frame_system::Trait>::Hash,
         <T as frame_system::Trait>::AccountId,
         <T as Trait<I>>::VotePower,
-        <T as common::Trait>::MemberId,
+        <T as common::membership::Trait>::MemberId,
     {
         /// Referendum started
         ReferendumStarted(u64),
@@ -455,7 +458,7 @@ decl_module! {
         pub fn reveal_vote(
             origin,
             salt: Vec<u8>,
-            vote_option_id: <T as common::Trait>::MemberId
+            vote_option_id: <T as common::membership::Trait>::MemberId
         ) -> Result<(), Error<T, I>> {
             let (stage_data, account_id, cast_vote) =
                 EnsureChecks::<T, I>::can_reveal_vote::<Self>(origin, &salt, &vote_option_id)?;
@@ -623,7 +626,7 @@ impl<T: Trait<I>, I: Instance> ReferendumManager<T::Origin, T::AccountId, T::Mem
         account_id: &<T as frame_system::Trait>::AccountId,
         salt: &[u8],
         cycle_id: &u64,
-        vote_option_id: &<T as common::Trait>::MemberId,
+        vote_option_id: &<T as common::membership::Trait>::MemberId,
     ) -> T::Hash {
         let mut payload = account_id.encode();
         let mut mut_option_id = vote_option_id.encode();
@@ -674,7 +677,8 @@ impl<T: Trait<I>, I: Instance> Mutations<T, I> {
     // Conclude referendum, count votes, and select the winners.
     fn conclude_referendum(
         revealing_stage: ReferendumStageRevealingOf<T, I>,
-    ) -> Vec<OptionResult<<T as common::Trait>::MemberId, <T as Trait<I>>::VotePower>> {
+    ) -> Vec<OptionResult<<T as common::membership::Trait>::MemberId, <T as Trait<I>>::VotePower>>
+    {
         // reset referendum state
         Stage::<T, I>::put(ReferendumStage::Inactive);
 
@@ -690,7 +694,7 @@ impl<T: Trait<I>, I: Instance> Mutations<T, I> {
         current_cycle_id: &u64,
     ) -> Result<(), Error<T, I>> {
         // Should call after `can_vote`
-        T::StakingHandler::lock_with_reasons(account_id, *stake, WithdrawReason::Transfer.into());
+        T::StakingHandler::lock(account_id, *stake);
 
         // store vote
         Votes::<T, I>::insert(
@@ -710,7 +714,7 @@ impl<T: Trait<I>, I: Instance> Mutations<T, I> {
     fn reveal_vote(
         stage_data: ReferendumStageRevealingOf<T, I>,
         account_id: &<T as frame_system::Trait>::AccountId,
-        option_id: &<T as common::Trait>::MemberId,
+        option_id: &<T as common::membership::Trait>::MemberId,
         cast_vote: CastVoteOf<T>,
     ) -> Result<(), Error<T, I>> {
         // prepare new values
@@ -924,7 +928,7 @@ impl<T: Trait<I>, I: Instance> EnsureChecks<T, I> {
     fn can_reveal_vote<R: ReferendumManager<T::Origin, T::AccountId, T::MemberId, T::Hash>>(
         origin: T::Origin,
         salt: &[u8],
-        vote_option_id: &<T as common::Trait>::MemberId,
+        vote_option_id: &<T as common::membership::Trait>::MemberId,
     ) -> Result<CanRevealResult<T, I>, Error<T, I>> {
         // ensure superuser requested action
         let account_id = Self::ensure_regular_user(origin)?;

+ 17 - 12
runtime-modules/referendum/src/mock.rs

@@ -234,6 +234,7 @@ parameter_types! {
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
+    pub const ReferralCutMaximumPercent: u8 = 50;
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
     pub const CandidateStake: u64 = 100;
 }
@@ -245,6 +246,7 @@ impl membership::Trait for Runtime {
     type WeightInfo = Weights;
     type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -270,7 +272,7 @@ impl common::working_group::WorkingGroupBudgetHandler<Runtime> for () {
 impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
     fn ensure_worker_origin(
         _origin: <Runtime as frame_system::Trait>::Origin,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
+        _worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!()
     }
@@ -279,7 +281,7 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Runtime as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Runtime as common::membership::Trait>::MemberId> {
         unimplemented!()
     }
 
@@ -289,19 +291,19 @@ impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
 
     fn is_worker_account_id(
         _account_id: &<Runtime as frame_system::Trait>::AccountId,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
+        _worker_id: &<Runtime as common::membership::Trait>::ActorId,
     ) -> bool {
         true
     }
 }
 
-impl common::Trait for Runtime {
+impl common::membership::Trait for Runtime {
     type MemberId = u64;
     type ActorId = u64;
 }
 
 parameter_types! {
-    pub const ExistentialDeposit: u64 = 0;
+    pub const ExistentialDeposit: u64 = 10;
     pub const MaxLocks: u32 = 50;
 }
 
@@ -499,7 +501,7 @@ where
 
     pub fn calculate_commitment(
         account_id: &<T as frame_system::Trait>::AccountId,
-        vote_option_index: &<T as common::Trait>::MemberId,
+        vote_option_index: &<T as common::membership::Trait>::MemberId,
         cycle_id: &u64,
     ) -> (T::Hash, Vec<u8>) {
         Self::calculate_commitment_for_cycle(account_id, &cycle_id, vote_option_index, None)
@@ -507,7 +509,7 @@ where
 
     pub fn calculate_commitment_custom_salt(
         account_id: &<T as frame_system::Trait>::AccountId,
-        vote_option_index: &<T as common::Trait>::MemberId,
+        vote_option_index: &<T as common::membership::Trait>::MemberId,
         custom_salt: &[u8],
         cycle_id: &u64,
     ) -> (T::Hash, Vec<u8>) {
@@ -528,7 +530,7 @@ where
     pub fn calculate_commitment_for_cycle(
         account_id: &<T as frame_system::Trait>::AccountId,
         cycle_id: &u64,
-        vote_option_index: &<T as common::Trait>::MemberId,
+        vote_option_index: &<T as common::membership::Trait>::MemberId,
         custom_salt: Option<&[u8]>,
     ) -> (T::Hash, Vec<u8>) {
         let salt = match custom_salt {
@@ -540,7 +542,7 @@ where
             <Module<T, I> as ReferendumManager<
                 <T as frame_system::Trait>::Origin,
                 <T as frame_system::Trait>::AccountId,
-                <T as common::Trait>::MemberId,
+                <T as common::membership::Trait>::MemberId,
                 <T as frame_system::Trait>::Hash,
             >>::calculate_commitment(account_id, &salt, cycle_id, vote_option_index),
             salt.to_vec(),
@@ -609,7 +611,7 @@ impl InstanceMocks<Runtime, DefaultInstance> {
             <Module::<Runtime> as ReferendumManager<
                 <Runtime as frame_system::Trait>::Origin,
                 <Runtime as frame_system::Trait>::AccountId,
-                <Runtime as common::Trait>::MemberId,
+                <Runtime as common::membership::Trait>::MemberId,
                 <Runtime as frame_system::Trait>::Hash,
             >>::start_referendum(
                 InstanceMockUtils::<Runtime, DefaultInstance>::mock_origin(OriginType::Root),
@@ -629,7 +631,7 @@ impl InstanceMocks<Runtime, DefaultInstance> {
         <Module<Runtime> as ReferendumManager<
             <Runtime as frame_system::Trait>::Origin,
             <Runtime as frame_system::Trait>::AccountId,
-            <Runtime as common::Trait>::MemberId,
+            <Runtime as common::membership::Trait>::MemberId,
             <Runtime as frame_system::Trait>::Hash,
         >>::force_start(extra_winning_target_count, cycle_id);
     }
@@ -691,7 +693,10 @@ impl InstanceMocks<Runtime, DefaultInstance> {
 
     pub fn check_revealing_finished(
         expected_winners: Vec<
-            OptionResult<<Runtime as common::Trait>::MemberId, <Runtime as Trait>::VotePower>,
+            OptionResult<
+                <Runtime as common::membership::Trait>::MemberId,
+                <Runtime as Trait>::VotePower,
+            >,
         >,
         expected_referendum_result: BTreeMap<u64, <Runtime as Trait>::VotePower>,
     ) {

+ 12 - 8
runtime-modules/service-discovery/src/mock.rs

@@ -51,9 +51,10 @@ parameter_types! {
     pub const MaximumBlockLength: u32 = 2 * 1024;
     pub const AvailableBlockRatio: Perbill = Perbill::one();
     pub const MinimumPeriod: u64 = 5;
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u32 = 10;
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
+    pub const ReferralCutMaximumPercent: u8 = 50;
 }
 
 impl frame_system::Trait for Test {
@@ -88,7 +89,7 @@ impl Trait for Test {
     type Event = MetaEvent;
 }
 
-impl common::Trait for Test {
+impl common::membership::Trait for Test {
     type MemberId = u64;
     type ActorId = u64;
 }
@@ -159,6 +160,7 @@ impl membership::Trait for Test {
     type WeightInfo = Weights;
     type DefaultInitialInvitationBalance = ();
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -177,7 +179,7 @@ impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
 impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!();
     }
@@ -186,7 +188,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Test as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Test as common::membership::Trait>::MemberId> {
         unimplemented!();
     }
 
@@ -196,7 +198,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
 
     fn is_worker_account_id(
         _account_id: &<Test as frame_system::Trait>::AccountId,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> bool {
         unimplemented!()
     }
@@ -217,8 +219,9 @@ parameter_types! {
     pub const LockId1: [u8; 8] = [1; 8];
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
     pub const CandidateStake: u64 = 100;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -231,7 +234,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::WeightInfo for WorkingGroupWeightInfo {
@@ -303,7 +307,7 @@ impl working_group::WeightInfo for WorkingGroupWeightInfo {
     }
 }
 
-impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         _: u64,

+ 3 - 3
runtime-modules/staking-handler/src/lib.rs

@@ -62,7 +62,7 @@ pub trait StakingHandler<AccountId, Balance, MemberId> {
 pub struct StakingManager<
     T: frame_system::Trait
         + pallet_balances::Trait
-        + common::Trait
+        + common::membership::Trait
         + LockComparator<<T as pallet_balances::Trait>::Balance>,
     LockId: Get<LockIdentifier>,
 > {
@@ -73,14 +73,14 @@ pub struct StakingManager<
 impl<
         T: frame_system::Trait
             + pallet_balances::Trait
-            + common::Trait
+            + common::membership::Trait
             + LockComparator<<T as pallet_balances::Trait>::Balance>,
         LockId: Get<LockIdentifier>,
     >
     StakingHandler<
         <T as frame_system::Trait>::AccountId,
         <T as pallet_balances::Trait>::Balance,
-        <T as common::Trait>::MemberId,
+        <T as common::membership::Trait>::MemberId,
     > for StakingManager<T, LockId>
 {
     fn lock(

+ 2 - 2
runtime-modules/staking-handler/src/mock.rs

@@ -19,7 +19,7 @@ parameter_types! {
     pub const MaximumBlockLength: u32 = 2 * 1024;
     pub const AvailableBlockRatio: Perbill = Perbill::one();
     pub const MinimumPeriod: u64 = 5;
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u32 = 10;
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
 }
@@ -66,7 +66,7 @@ impl pallet_balances::Trait for Test {
     type MaxLocks = ();
 }
 
-impl common::Trait for Test {
+impl common::membership::Trait for Test {
     type MemberId = u64;
     type ActorId = u64;
 }

+ 1 - 1
runtime-modules/storage/src/data_directory.rs

@@ -32,7 +32,7 @@ use sp_std::vec::Vec;
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 
-use common::origin::MemberOriginValidator;
+use common::membership::MemberOriginValidator;
 use common::working_group::WorkingGroupAuthenticator;
 pub(crate) use common::BlockAndTime;
 

+ 1 - 1
runtime-modules/storage/src/data_object_type_registry.rs

@@ -33,7 +33,7 @@ const DEFAULT_TYPE_DESCRIPTION: &str = "Default data object type for audio and v
 const DEFAULT_FIRST_DATA_OBJECT_TYPE_ID: u8 = 1;
 
 /// The _Data object type registry_ main _Trait_.
-pub trait Trait: frame_system::Trait + common::Trait {
+pub trait Trait: frame_system::Trait + common::membership::Trait {
     /// _Data object type registry_ event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 

+ 12 - 8
runtime-modules/storage/src/tests/mock.rs

@@ -100,6 +100,7 @@ parameter_types! {
     pub const AvailableBlockRatio: Perbill = Perbill::one();
     pub const MinimumPeriod: u64 = 5;
     pub const MaxObjectsPerInjection: u32 = 5;
+    pub const ReferralCutMaximumPercent: u8 = 50;
 }
 
 impl frame_system::Trait for Test {
@@ -138,7 +139,7 @@ impl pallet_timestamp::Trait for Test {
 }
 
 parameter_types! {
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u32 = 10;
 }
 
 impl balances::Trait for Test {
@@ -158,8 +159,9 @@ parameter_types! {
     pub const DefaultInitialInvitationBalance: u64 = 100;
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
     pub const CandidateStake: u64 = 100;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u64 = 20;
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -172,7 +174,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::WeightInfo for WorkingGroupWeightInfo {
@@ -303,7 +306,7 @@ impl membership::WeightInfo for Weights {
     }
 }
 
-impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         _: u64,
@@ -345,7 +348,7 @@ impl data_object_storage_registry::Trait for Test {
     type ContentIdExists = MockContent;
 }
 
-impl common::Trait for Test {
+impl common::membership::Trait for Test {
     type MemberId = u64;
     type ActorId = u32;
 }
@@ -357,6 +360,7 @@ impl membership::Trait for Test {
     type WeightInfo = Weights;
     type DefaultInitialInvitationBalance = ();
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -375,7 +379,7 @@ impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
 impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!();
     }
@@ -384,7 +388,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Test as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Test as common::membership::Trait>::MemberId> {
         unimplemented!();
     }
 
@@ -394,7 +398,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
 
     fn is_worker_account_id(
         _account_id: &<Test as frame_system::Trait>::AccountId,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> bool {
         unimplemented!()
     }

+ 6 - 5
runtime-modules/utility/src/benchmarking.rs

@@ -169,15 +169,16 @@ benchmarks! {
     burn_account_tokens {
         let account_id = account::<T::AccountId>("caller", 0, 0);
         let initial_issuance = Balances::<T>::total_issuance();
-        let _ = Balances::<T>::make_free_balance_be(&account_id, One::one());
+        let initial_balance: BalanceOf<T> = 15.into();
+        let _ = Balances::<T>::make_free_balance_be(&account_id, initial_balance);
 
-        assert_eq!(Balances::<T>::free_balance(&account_id), One::one());
-        assert_eq!(Balances::<T>::total_issuance(), initial_issuance + One::one());
-    }: _ (RawOrigin::Signed(account_id.clone()), One::one())
+        assert_eq!(Balances::<T>::free_balance(&account_id), initial_balance);
+        assert_eq!(Balances::<T>::total_issuance(), initial_issuance + initial_balance);
+    }: _ (RawOrigin::Signed(account_id.clone()), initial_balance)
     verify {
         assert_eq!(Balances::<T>::free_balance(&account_id), Zero::zero());
         assert_eq!(Balances::<T>::total_issuance(),  initial_issuance);
-        assert_last_event::<T>(RawEvent::TokensBurned(account_id, One::one()).into());
+        assert_last_event::<T>(RawEvent::TokensBurned(account_id, initial_balance).into());
     }
 }
 

+ 19 - 12
runtime-modules/utility/src/tests/mocks.rs

@@ -84,7 +84,7 @@ impl referendum::WeightInfo for ReferendumWeightInfo {
 pub struct Test;
 
 parameter_types! {
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u32 = 10;
 }
 
 impl balances::Trait for Test {
@@ -223,6 +223,7 @@ impl pallet_timestamp::Trait for Test {
 parameter_types! {
     pub const DefaultMembershipPrice: u64 = 100;
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
+    pub const ReferralCutMaximumPercent: u8 = 50;
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
     pub const CandidateStake: u64 = 100;
 }
@@ -234,6 +235,7 @@ impl membership::Trait for Test {
     type WeightInfo = Weights;
     type DefaultInitialInvitationBalance = ();
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -252,7 +254,7 @@ impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
 impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> DispatchResult {
         unimplemented!();
     }
@@ -261,7 +263,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
         unimplemented!()
     }
 
-    fn get_leader_member_id() -> Option<<Test as common::Trait>::MemberId> {
+    fn get_leader_member_id() -> Option<<Test as common::membership::Trait>::MemberId> {
         unimplemented!();
     }
 
@@ -271,7 +273,7 @@ impl common::working_group::WorkingGroupAuthenticator<Test> for () {
 
     fn is_worker_account_id(
         _account_id: &<Test as frame_system::Trait>::AccountId,
-        _worker_id: &<Test as common::Trait>::ActorId,
+        _worker_id: &<Test as common::membership::Trait>::ActorId,
     ) -> bool {
         unimplemented!()
     }
@@ -339,7 +341,8 @@ parameter_types! {
     pub const MaxWorkerNumberLimit: u32 = 100;
     pub const LockId1: [u8; 8] = [1; 8];
     pub const LockId2: [u8; 8] = [2; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -352,7 +355,8 @@ impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::WeightInfo for WorkingGroupWeightInfo {
@@ -433,7 +437,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<ForumWorkingGroupInstance> for Test {
@@ -445,7 +450,8 @@ impl working_group::Trait<ForumWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
@@ -457,7 +463,8 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 parameter_types! {
@@ -692,7 +699,7 @@ impl common::StakingAccountValidator<Test> for () {
     }
 }
 
-impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         _: u64,
@@ -707,7 +714,7 @@ impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
     }
 }
 
-impl common::origin::CouncilOriginValidator<Origin, u64, u64> for () {
+impl common::council::CouncilOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_consulate(origin: Origin, _: u64) -> DispatchResult {
         frame_system::ensure_signed(origin)?;
 
@@ -715,7 +722,7 @@ impl common::origin::CouncilOriginValidator<Origin, u64, u64> for () {
     }
 }
 
-impl common::Trait for Test {
+impl common::membership::Trait for Test {
     type MemberId = u64;
     type ActorId = u64;
 }

+ 3 - 3
runtime-modules/working-group/src/benchmarking.rs

@@ -35,7 +35,7 @@ fn add_opening_helper<T: Trait<I>, I: Instance>(
     job_opening_type: &OpeningType,
 ) -> OpeningId {
     let staking_policy = StakePolicy {
-        stake_amount: T::MinimumStakeForOpening::get(),
+        stake_amount: T::MinimumApplicationStake::get(),
         leaving_unstaking_period: T::MinUnstakingPeriodLimit::get() + One::one(),
     };
 
@@ -66,7 +66,7 @@ fn apply_on_opening_helper<T: Trait<I>, I: Instance>(
 ) -> ApplicationId {
     let stake_parameters = StakeParameters {
         // Due to mock implementation of StakingHandler we can't go over 1000
-        stake: T::MinimumStakeForOpening::get(),
+        stake: T::MinimumApplicationStake::get(),
         staking_account_id: applicant_id.clone(),
     };
 
@@ -524,7 +524,7 @@ benchmarks_instance! {
             description: vec![0u8; i.try_into().unwrap()],
             stake_parameters:
                 StakeParameters {
-                    stake: T::MinimumStakeForOpening::get(),
+                    stake: T::MinimumApplicationStake::get(),
                     staking_account_id: lead_account_id.clone(),
                 }
         };

+ 26 - 1
runtime-modules/working-group/src/checks.rs

@@ -12,6 +12,7 @@ use sp_arithmetic::traits::Zero;
 use sp_std::collections::btree_set::BTreeSet;
 use sp_std::marker::PhantomData;
 use sp_std::vec::Vec;
+use staking_handler::StakingHandler;
 
 use crate::types::{ApplicationInfo, StakeParameters};
 
@@ -32,6 +33,30 @@ pub(crate) fn ensure_origin_for_opening_type<T: Trait<I>, I: Instance>(
     }
 }
 
+pub(crate) fn ensure_stake_for_opening_type<T: Trait<I>, I: Instance>(
+    origin: T::Origin,
+    opening_type: OpeningType,
+) -> DispatchResult {
+    // Lead needs stake to generate opening
+    if opening_type == OpeningType::Regular {
+        // We check here that the origin is active leader
+        // just to make this future proof for any change in
+        // `ensure_origin_for_opening_type`
+        ensure_origin_is_active_leader::<T, I>(origin)?;
+        let lead = crate::Module::<T, I>::worker_by_id(ensure_lead_is_set::<T, I>()?);
+
+        ensure!(
+            T::StakingHandler::is_enough_balance_for_stake(
+                &lead.staking_account_id,
+                T::LeaderOpeningStake::get()
+            ),
+            Error::<T, I>::InsufficientBalanceToCoverStake
+        );
+    }
+
+    Ok(())
+}
+
 // Check opening: returns the opening by id if it is exists.
 pub(crate) fn ensure_opening_exists<T: Trait<I>, I: Instance>(
     opening_id: OpeningId,
@@ -191,7 +216,7 @@ pub(crate) fn ensure_valid_stake_policy<T: Trait<I>, I: Instance>(
     stake_policy: &StakePolicy<T::BlockNumber, BalanceOf<T>>,
 ) -> Result<(), DispatchError> {
     ensure!(
-        stake_policy.stake_amount >= T::MinimumStakeForOpening::get(),
+        stake_policy.stake_amount >= T::MinimumApplicationStake::get(),
         Error::<T, I>::BelowMinimumStakes
     );
 

+ 114 - 20
runtime-modules/working-group/src/lib.rs

@@ -50,13 +50,13 @@ use sp_std::vec::Vec;
 pub use errors::Error;
 pub use types::{
     Application, ApplicationId, ApplyOnOpeningParameters, BalanceOf, Opening, OpeningId,
-    OpeningType, StakeParameters, StakePolicy, Worker, WorkerId,
+    OpeningType, RewardPaymentType, StakeParameters, StakePolicy, Worker, WorkerId,
 };
 use types::{ApplicationInfo, WorkerInfo};
 
 pub use checks::{ensure_worker_exists, ensure_worker_signed};
 
-use common::origin::MemberOriginValidator;
+use common::membership::MemberOriginValidator;
 use common::{MemberId, StakingAccountValidator};
 use frame_support::dispatch::DispatchResult;
 use staking_handler::StakingHandler;
@@ -92,7 +92,7 @@ pub trait WeightInfo {
 
 /// The _Group_ main _Trait_
 pub trait Trait<I: Instance = DefaultInstance>:
-    frame_system::Trait + balances::Trait + common::Trait
+    frame_system::Trait + balances::Trait + common::membership::Trait
 {
     /// _Administration_ event type.
     type Event: From<Event<Self, I>> + Into<<Self as frame_system::Trait>::Event>;
@@ -118,8 +118,11 @@ pub trait Trait<I: Instance = DefaultInstance>:
     /// Weight information for extrinsics in this pallet.
     type WeightInfo: WeightInfo;
 
-    /// Minimum stake required for an opening
-    type MinimumStakeForOpening: Get<Self::Balance>;
+    /// Minimum stake required for applying into an opening
+    type MinimumApplicationStake: Get<Self::Balance>;
+
+    /// Stake needed to create an opening
+    type LeaderOpeningStake: Get<Self::Balance>;
 }
 
 decl_event!(
@@ -178,11 +181,11 @@ decl_event!(
         /// - Rationale.
         WorkerExited(WorkerId),
 
-        /// Emits when worker is leaving immediatly after extrinsic is called
+        /// Emits when worker started leaving their role.
         /// Params:
         /// - Worker id.
         /// - Rationale.
-        WorkerLeft(WorkerId, Option<Vec<u8>>),
+        WorkerStartedLeaving(WorkerId, Option<Vec<u8>>),
 
         /// Emits on terminating the worker.
         /// Params:
@@ -253,10 +256,24 @@ decl_event!(
 
         /// Emits on budget from the working group being spent
         /// Params:
-        /// - Reciever Account Id.
+        /// - Receiver Account Id.
         /// - Balance spent.
         /// - Rationale.
         BudgetSpending(AccountId, Balance, Option<Vec<u8>>),
+
+        /// Emits on paying the reward.
+        /// Params:
+        /// - Id of the worker.
+        /// - Receiver Account Id.
+        /// - Reward
+        /// - Payment type (missed reward or regular one)
+        RewardPaid(WorkerId, AccountId, Balance, RewardPaymentType),
+
+        /// Emits on reaching new missed reward.
+        /// Params:
+        /// - Worker ID.
+        /// - Missed reward (optional). None means 'no missed reward'.
+        NewMissedRewardLevelReached(WorkerId, Option<Balance>),
     }
 );
 
@@ -363,16 +380,31 @@ decl_module! {
             stake_policy: StakePolicy<T::BlockNumber, BalanceOf<T>>,
             reward_per_block: Option<BalanceOf<T>>
         ){
-            checks::ensure_origin_for_opening_type::<T, I>(origin, opening_type)?;
+            checks::ensure_origin_for_opening_type::<T, I>(origin.clone(), opening_type)?;
 
             checks::ensure_valid_stake_policy::<T, I>(&stake_policy)?;
 
             checks::ensure_valid_reward_per_block::<T, I>(&reward_per_block)?;
 
+            checks::ensure_stake_for_opening_type::<T, I>(origin, opening_type)?;
+
             //
             // == MUTATION SAFE ==
             //
 
+            let mut creation_stake = BalanceOf::<T>::zero();
+            if opening_type == OpeningType::Regular {
+                // Lead must be set for ensure_origin_for_openig_type in the
+                // case of regular.
+                let lead = Self::worker_by_id(checks::ensure_lead_is_set::<T, I>()?);
+                let current_stake = T::StakingHandler::current_stake(&lead.staking_account_id);
+                creation_stake = T::LeaderOpeningStake::get();
+                T::StakingHandler::set_stake(
+                    &lead.staking_account_id,
+                    creation_stake.saturating_add(current_stake)
+                )?;
+            }
+
             let hashed_description = T::Hashing::hash(&description);
 
             // Create and add worker opening.
@@ -382,6 +414,7 @@ decl_module! {
                 description_hash: hashed_description.as_ref().to_vec(),
                 stake_policy: stake_policy.clone(),
                 reward_per_block,
+                creation_stake,
             };
 
             let new_opening_id = NextOpeningId::<I>::get();
@@ -542,6 +575,17 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
+            if opening.opening_type == OpeningType::Regular {
+                // Lead must be set for ensure_origin_for_openig_type in the
+                // case of regular.
+                let lead = Self::worker_by_id(checks::ensure_lead_is_set::<T, I>()?);
+                let current_stake = T::StakingHandler::current_stake(&lead.staking_account_id);
+                T::StakingHandler::set_stake(
+                    &lead.staking_account_id,
+                    current_stake.saturating_sub(opening.creation_stake)
+                )?;
+            }
+
             // Process successful applications
             let application_id_to_worker_id = Self::fulfill_successful_applications(
                 &opening,
@@ -623,6 +667,9 @@ decl_module! {
             WorkerById::<T, I>::mutate(worker_id, |worker| {
                 worker.started_leaving_at = Some(Self::current_block())
             });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::WorkerStartedLeaving(worker_id, rationale));
         }
 
         /// Terminate the active worker by the lead.
@@ -861,6 +908,18 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
+            // Remove opening stake
+            if opening.opening_type == OpeningType::Regular {
+                // Lead must be set for ensure_origin_for_openig_type in the
+                // case of regular.
+                let lead = Self::worker_by_id(checks::ensure_lead_is_set::<T, I>()?);
+                let current_stake = T::StakingHandler::current_stake(&lead.staking_account_id);
+                T::StakingHandler::set_stake(
+                    &lead.staking_account_id,
+                    current_stake.saturating_sub(opening.creation_stake)
+                )?;
+            }
+
             // Remove the opening.
             <OpeningById::<T, I>>::remove(opening_id);
 
@@ -1258,7 +1317,12 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
 
             // Check whether the budget is not zero.
             if actual_reward > Zero::zero() {
-                Self::pay_from_budget(&worker.reward_account_id, actual_reward);
+                Self::pay_reward(
+                    worker_id,
+                    &worker.reward_account_id,
+                    actual_reward,
+                    RewardPaymentType::RegularReward,
+                );
             }
 
             // Check whether the budget is insufficient.
@@ -1281,6 +1345,22 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
         let _ = <balances::Module<T>>::deposit_creating(account_id, amount);
     }
 
+    // Helper-function joining the reward payment with the event.
+    fn pay_reward(
+        worker_id: &WorkerId<T>,
+        account_id: &T::AccountId,
+        amount: BalanceOf<T>,
+        reward_payment_type: RewardPaymentType,
+    ) {
+        Self::pay_from_budget(account_id, amount);
+        Self::deposit_event(RawEvent::RewardPaid(
+            *worker_id,
+            account_id.clone(),
+            amount,
+            reward_payment_type,
+        ));
+    }
+
     // Tries to pay missed reward if the reward is enabled for worker and there is enough of group budget.
     fn try_to_pay_missed_reward(worker_id: &WorkerId<T>, worker: &Worker<T>) {
         if let Some(missed_reward) = worker.missed_reward {
@@ -1289,7 +1369,12 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
 
             // Checks if the budget allows any payment.
             if could_be_paid_reward > Zero::zero() {
-                Self::pay_from_budget(&worker.reward_account_id, could_be_paid_reward);
+                Self::pay_reward(
+                    worker_id,
+                    &worker.reward_account_id,
+                    could_be_paid_reward,
+                    RewardPaymentType::MissedReward,
+                );
 
                 let new_missed_reward = if insufficient_amount > Zero::zero() {
                     Some(insufficient_amount)
@@ -1297,14 +1382,26 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
                     None
                 };
 
-                // Update worker missed reward.
-                WorkerById::<T, I>::mutate(worker_id, |worker| {
-                    worker.missed_reward = new_missed_reward;
-                });
+                Self::update_worker_missed_reward(worker_id, new_missed_reward);
             }
         }
     }
 
+    // Update worker missed reward.
+    fn update_worker_missed_reward(
+        worker_id: &WorkerId<T>,
+        new_missed_reward: Option<BalanceOf<T>>,
+    ) {
+        WorkerById::<T, I>::mutate(worker_id, |worker| {
+            worker.missed_reward = new_missed_reward;
+        });
+
+        Self::deposit_event(RawEvent::NewMissedRewardLevelReached(
+            *worker_id,
+            new_missed_reward,
+        ));
+    }
+
     // Saves missed reward for a worker.
     fn save_missed_reward(worker_id: &WorkerId<T>, worker: &Worker<T>, reward: BalanceOf<T>) {
         // Save unpaid reward.
@@ -1312,10 +1409,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
 
         let new_missed_reward = missed_reward_so_far + reward;
 
-        // Update worker missed reward.
-        WorkerById::<T, I>::mutate(worker_id, |worker| {
-            worker.missed_reward = Some(new_missed_reward);
-        });
+        Self::update_worker_missed_reward(worker_id, Some(new_missed_reward));
     }
 
     // Returns allowed payment by the group budget and possible missed payment
@@ -1334,7 +1428,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
         WorkerById::<T, I>::iter()
             .filter_map(|(worker_id, worker)| {
                 if let Some(started_leaving_at) = worker.started_leaving_at {
-                    if started_leaving_at + worker.job_unstaking_period >= Self::current_block() {
+                    if started_leaving_at + worker.job_unstaking_period <= Self::current_block() {
                         return Some((worker_id, worker).into());
                     }
                 }

+ 44 - 8
runtime-modules/working-group/src/tests/fixtures.rs

@@ -44,6 +44,35 @@ impl EventFixture {
 
         assert_eq!(System::events().pop().unwrap(), expected_event);
     }
+
+    pub fn contains_crate_event(
+        expected_raw_event: RawEvent<
+            u64,
+            u64,
+            BTreeMap<u64, u64>,
+            u64,
+            u64,
+            u64,
+            OpeningType,
+            StakePolicy<u64, u64>,
+            ApplyOnOpeningParameters<Test>,
+            DefaultInstance,
+        >,
+    ) {
+        let converted_event = TestEvent::crate_DefaultInstance(expected_raw_event);
+
+        Self::contains_global_event(converted_event)
+    }
+
+    fn contains_global_event(expected_event: TestEvent) {
+        let expected_event = EventRecord {
+            phase: Phase::Initialization,
+            event: expected_event,
+            topics: vec![],
+        };
+
+        assert!(System::events().iter().any(|ev| *ev == expected_event));
+    }
 }
 
 pub struct AddOpeningFixture {
@@ -63,7 +92,7 @@ impl Default for AddOpeningFixture {
             opening_type: OpeningType::Regular,
             starting_block: 0,
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
@@ -94,6 +123,11 @@ impl AddOpeningFixture {
                 opening_type: self.opening_type,
                 stake_policy: self.stake_policy.clone(),
                 reward_per_block: self.reward_per_block.clone(),
+                creation_stake: if self.opening_type == OpeningType::Regular {
+                    <Test as Trait>::LeaderOpeningStake::get()
+                } else {
+                    0
+                },
             };
 
             assert_eq!(actual_opening, expected_opening);
@@ -204,10 +238,10 @@ impl ApplyOnOpeningFixture {
             reward_account_id: 2,
             description: b"human_text".to_vec(),
             stake_parameters: StakeParameters {
-                stake: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake: <Test as Trait>::MinimumApplicationStake::get(),
                 staking_account_id: 2,
             },
-            initial_balance: <Test as Trait>::MinimumStakeForOpening::get(),
+            initial_balance: <Test as Trait>::MinimumApplicationStake::get(),
         }
     }
 
@@ -293,7 +327,7 @@ impl FillOpeningFixture {
             reward_account_id: 2,
             staking_account_id: 2,
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
@@ -403,12 +437,14 @@ impl Default for HireLeadFixture {
         Self {
             setup_environment: true,
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
             lead_id: 1,
-            initial_balance: <Test as Trait>::MinimumStakeForOpening::get() + 1,
+            initial_balance: <Test as Trait>::MinimumApplicationStake::get()
+                + <Test as Trait>::LeaderOpeningStake::get()
+                + 1,
         }
     }
 }
@@ -477,11 +513,11 @@ impl Default for HireRegularWorkerFixture {
         Self {
             setup_environment: true,
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
-            initial_balance: <Test as Trait>::MinimumStakeForOpening::get(),
+            initial_balance: <Test as Trait>::MinimumApplicationStake::get(),
         }
     }
 }

+ 6 - 4
runtime-modules/working-group/src/tests/hiring_workflow.rs

@@ -33,13 +33,13 @@ impl Default for HiringWorkflow {
             opening_type: OpeningType::Regular,
             expected_result: Ok(()),
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
             applications: Vec::new(),
             setup_environment: true,
-            initial_balance: <Test as Trait>::MinimumStakeForOpening::get() + 1,
+            initial_balance: <Test as Trait>::MinimumApplicationStake::get() + 1,
         }
     }
 }
@@ -129,7 +129,9 @@ impl HiringWorkflow {
         } else {
             balances::Module::<Test>::make_free_balance_be(
                 &1,
-                <Test as Trait>::MinimumStakeForOpening::get() + 1,
+                <Test as Trait>::MinimumApplicationStake::get()
+                    + <Test as Trait>::LeaderOpeningStake::get()
+                    + 1,
             );
             //         setup_members(6);
         }
@@ -137,7 +139,7 @@ impl HiringWorkflow {
 
     pub fn execute(&self) -> Option<u64> {
         if self.setup_environment {
-            self.setup_environment()
+            self.setup_environment();
         }
 
         let result = self.fill_worker_position();

+ 9 - 5
runtime-modules/working-group/src/tests/mock.rs

@@ -37,10 +37,11 @@ parameter_types! {
     pub const MaximumBlockLength: u32 = 2 * 1024;
     pub const AvailableBlockRatio: Perbill = Perbill::one();
     pub const MinimumPeriod: u64 = 5;
-    pub const ExistentialDeposit: u32 = 0;
+    pub const ExistentialDeposit: u32 = 10;
     pub const DefaultMembershipPrice: u64 = 0;
     pub const DefaultInitialInvitationBalance: u64 = 100;
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
+    pub const ReferralCutMaximumPercent: u8 = 50;
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
     pub const CandidateStake: u64 = 100;
 }
@@ -94,7 +95,7 @@ impl balances::Trait for Test {
     type MaxLocks = ();
 }
 
-impl common::Trait for Test {
+impl common::membership::Trait for Test {
     type MemberId = u64;
     type ActorId = u64;
 }
@@ -165,6 +166,7 @@ impl membership::Trait for Test {
     type WeightInfo = Weights;
     type DefaultInitialInvitationBalance = ();
     type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type StakingCandidateStakingHandler =
         staking_handler::StakingManager<Self, StakingCandidateLockId>;
     type CandidateStake = CandidateStake;
@@ -183,8 +185,9 @@ parameter_types! {
     pub const RewardPeriod: u32 = 2;
     pub const MaxWorkerNumberLimit: u32 = 3;
     pub const MinUnstakingPeriodLimit: u64 = 3;
-    pub const MinimumStakeForOpening: u64 = 50;
+    pub const MinimumApplicationStake: u64 = 50;
     pub const LockId: [u8; 8] = [1; 8];
+    pub const LeaderOpeningStake: u64 = 20;
 }
 
 impl Trait for Test {
@@ -196,7 +199,8 @@ impl Trait for Test {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = RewardPeriod;
     type WeightInfo = ();
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl common::StakingAccountValidator<Test> for () {
@@ -276,7 +280,7 @@ impl crate::WeightInfo for () {
 
 pub const ACTOR_ORIGIN_ERROR: &'static str = "Invalid membership";
 
-impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
     fn ensure_member_controller_account_origin(
         origin: Origin,
         member_id: u64,

+ 119 - 14
runtime-modules/working-group/src/tests/mod.rs

@@ -16,7 +16,9 @@ use crate::tests::mock::{
     STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES, STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER,
 };
 use crate::types::StakeParameters;
-use crate::{DefaultInstance, Error, OpeningType, RawEvent, StakePolicy, Trait, Worker};
+use crate::{
+    DefaultInstance, Error, OpeningType, RawEvent, RewardPaymentType, StakePolicy, Trait, Worker,
+};
 use common::working_group::WorkingGroupAuthenticator;
 use fixtures::{
     increase_total_balance_issuance_using_account_id, AddOpeningFixture, ApplyOnOpeningFixture,
@@ -35,12 +37,13 @@ fn add_opening_succeeded() {
         HireLeadFixture::default().hire_lead();
 
         let starting_block = 1;
+
         run_to_block(starting_block);
 
         let add_opening_fixture = AddOpeningFixture::default()
             .with_starting_block(starting_block)
             .with_stake_policy(StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: 100,
             })
             .with_reward_per_block(Some(10));
@@ -83,7 +86,7 @@ fn add_opening_fails_with_less_than_minimum_stake() {
         ));
 
         let add_opening_fixture = AddOpeningFixture::default().with_stake_policy(StakePolicy {
-            stake_amount: <Test as Trait>::MinimumStakeForOpening::get() - 1,
+            stake_amount: <Test as Trait>::MinimumApplicationStake::get() - 1,
             leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get(),
         });
 
@@ -106,6 +109,21 @@ fn add_opening_fails_with_zero_reward() {
     });
 }
 
+#[test]
+fn add_opening_fails_with_insufficient_balance() {
+    build_test_externalities().execute_with(|| {
+        HireLeadFixture::default()
+            .with_initial_balance(<Test as Trait>::MinimumApplicationStake::get() + 1)
+            .hire_lead();
+
+        let add_opening_fixture = AddOpeningFixture::default();
+
+        add_opening_fixture.call_and_assert(Err(
+            Error::<Test, DefaultInstance>::InsufficientBalanceToCoverStake.into(),
+        ));
+    });
+}
+
 #[test]
 fn add_opening_fails_with_incorrect_unstaking_period() {
     build_test_externalities().execute_with(|| {
@@ -152,7 +170,7 @@ fn apply_on_opening_succeeded() {
         let opening_id = add_opening_fixture.call().unwrap();
 
         let apply_on_opening_fixture = ApplyOnOpeningFixture::default_for_opening_id(opening_id)
-            .with_initial_balance(<Test as Trait>::MinimumStakeForOpening::get());
+            .with_initial_balance(<Test as Trait>::MinimumApplicationStake::get());
 
         let application_id = apply_on_opening_fixture.call_and_assert(Ok(()));
 
@@ -239,8 +257,15 @@ fn fill_opening_succeeded() {
                 .with_reward_per_block(Some(reward_per_block))
                 .with_created_at(starting_block);
 
+        let initial_balance = Balances::usable_balance(&1);
+
         let worker_id = fill_opening_fixture.call_and_assert(Ok(()));
 
+        assert_eq!(
+            Balances::usable_balance(&1),
+            initial_balance + <Test as Trait>::LeaderOpeningStake::get()
+        );
+
         let mut result_map = BTreeMap::new();
         result_map.insert(application_id, worker_id);
 
@@ -328,7 +353,13 @@ fn fill_opening_fails_with_bad_origin() {
 #[test]
 fn fill_opening_fails_with_application_for_other_opening() {
     build_test_externalities().execute_with(|| {
-        HireLeadFixture::default().hire_lead();
+        HireLeadFixture::default()
+            .with_initial_balance(
+                <Test as Trait>::MinimumApplicationStake::get()
+                    + 3 * <Test as Trait>::LeaderOpeningStake::get()
+                    + 1,
+            )
+            .hire_lead();
 
         let add_opening_fixture = AddOpeningFixture::default();
 
@@ -563,6 +594,8 @@ fn leave_worker_role_succeeds() {
 
         leave_worker_role_fixture.call_and_assert(Ok(()));
 
+        EventFixture::assert_last_crate_event(RawEvent::WorkerStartedLeaving(worker_id, None));
+
         let worker = TestWorkingGroup::worker_by_id(worker_id);
         run_to_block(1 + worker.job_unstaking_period);
 
@@ -579,9 +612,11 @@ fn leave_worker_role_succeeds_with_paying_missed_reward() {
         let worker_id = HireRegularWorkerFixture::default()
             .with_reward_per_block(Some(reward_per_block))
             .hire();
-        let block_number = 4;
 
-        run_to_block(block_number);
+        let reward_period: u64 = <Test as Trait>::RewardPeriod::get().into();
+        let missed_reward_block_number = reward_period * 2;
+
+        run_to_block(missed_reward_block_number);
 
         assert_eq!(Balances::usable_balance(&account_id), 0);
 
@@ -591,15 +626,70 @@ fn leave_worker_role_succeeds_with_paying_missed_reward() {
         leave_worker_role_fixture.call_and_assert(Ok(()));
 
         let worker = TestWorkingGroup::worker_by_id(worker_id);
-        run_to_block(block_number + worker.job_unstaking_period);
+        let leaving_block = missed_reward_block_number + worker.job_unstaking_period;
+        run_to_block(leaving_block);
+
+        let missed_reward = missed_reward_block_number * reward_per_block;
+        EventFixture::contains_crate_event(RawEvent::NewMissedRewardLevelReached(
+            worker_id,
+            Some(missed_reward),
+        ));
+        EventFixture::contains_crate_event(RawEvent::RewardPaid(
+            worker_id,
+            account_id,
+            missed_reward,
+            RewardPaymentType::MissedReward,
+        ));
 
+        // Didn't get the last reward period: leaving earlier than rewarding.
+        let reward_block_count = leaving_block - reward_period;
         assert_eq!(
             Balances::usable_balance(&account_id),
-            block_number * reward_per_block + <Test as Trait>::MinimumStakeForOpening::get()
+            reward_block_count * reward_per_block + <Test as Trait>::MinimumApplicationStake::get()
         );
     });
 }
 
+#[test]
+fn leave_worker_role_succeeds_with_correct_unstaking_period() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 10;
+        run_to_block(starting_block);
+
+        let worker_id = HireRegularWorkerFixture::default().hire();
+
+        // Assert initial worker existence
+        assert!(<crate::WorkerById<Test, DefaultInstance>>::contains_key(
+            worker_id
+        ));
+
+        let default_unstaking_period =
+            TestWorkingGroup::worker_by_id(worker_id).job_unstaking_period;
+
+        let leave_worker_role_fixture = LeaveWorkerRoleFixture::default_for_worker_id(worker_id);
+        leave_worker_role_fixture.call_and_assert(Ok(()));
+
+        // Assert worker existence after leave_role
+        assert!(<crate::WorkerById<Test, DefaultInstance>>::contains_key(
+            worker_id
+        ));
+
+        run_to_block(starting_block + default_unstaking_period - 1);
+
+        // Assert worker existence one block before the end of the unstaking period.
+        assert!(<crate::WorkerById<Test, DefaultInstance>>::contains_key(
+            worker_id
+        ));
+
+        run_to_block(starting_block + default_unstaking_period);
+
+        // Assert worker removal after the unstaking period.
+        assert!(!<crate::WorkerById<Test, DefaultInstance>>::contains_key(
+            worker_id
+        ));
+    });
+}
+
 #[test]
 fn leave_worker_role_succeeds_with_partial_payment_of_missed_reward() {
     build_test_externalities().execute_with(|| {
@@ -626,7 +716,7 @@ fn leave_worker_role_succeeds_with_partial_payment_of_missed_reward() {
 
         assert_eq!(
             Balances::usable_balance(&account_id),
-            budget + <Test as Trait>::MinimumStakeForOpening::get()
+            budget + <Test as Trait>::MinimumApplicationStake::get()
         );
     });
 }
@@ -768,7 +858,7 @@ fn terminate_worker_role_succeeds_with_paying_missed_reward() {
 
         assert_eq!(
             Balances::usable_balance(&account_id),
-            block_number * reward_per_block + <Test as Trait>::MinimumStakeForOpening::get()
+            block_number * reward_per_block + <Test as Trait>::MinimumApplicationStake::get()
         );
     });
 }
@@ -921,8 +1011,8 @@ fn apply_on_opening_locks_the_stake() {
         HireLeadFixture::default().hire_lead();
 
         let account_id = 2;
-        let total_balance = <Test as Trait>::MinimumStakeForOpening::get() + 100;
-        let stake = <Test as Trait>::MinimumStakeForOpening::get();
+        let total_balance = <Test as Trait>::MinimumApplicationStake::get() + 100;
+        let stake = <Test as Trait>::MinimumApplicationStake::get();
 
         let stake_parameters = StakeParameters {
             stake,
@@ -1684,7 +1774,7 @@ fn withdraw_worker_application_fails_with_invalid_application_author() {
         let opening_id = add_opening_fixture.call_and_assert(Ok(()));
 
         let apply_on_opening_fixture = ApplyOnOpeningFixture::default_for_opening_id(opening_id)
-            .with_initial_balance(<Test as Trait>::MinimumStakeForOpening::get() + 1);
+            .with_initial_balance(<Test as Trait>::MinimumApplicationStake::get() + 1);
         let application_id = apply_on_opening_fixture.call_and_assert(Ok(()));
 
         let invalid_author_account_id = 55;
@@ -1713,9 +1803,16 @@ fn cancel_opening_succeeds() {
         let add_opening_fixture = AddOpeningFixture::default().with_starting_block(starting_block);
         let opening_id = add_opening_fixture.call_and_assert(Ok(()));
 
+        let initial_balance = Balances::usable_balance(&1);
+
         let cancel_opening_fixture = CancelOpeningFixture::default_for_opening_id(opening_id);
         cancel_opening_fixture.call_and_assert(Ok(()));
 
+        assert_eq!(
+            Balances::usable_balance(&1),
+            initial_balance + <Test as Trait>::LeaderOpeningStake::get()
+        );
+
         EventFixture::assert_last_crate_event(RawEvent::OpeningCanceled(opening_id));
     });
 }
@@ -1830,6 +1927,14 @@ fn rewards_payments_are_successful() {
             Balances::usable_balance(&account_id),
             block_number * reward_per_block
         );
+
+        let reward_period: u64 = <Test as Trait>::RewardPeriod::get().into();
+        EventFixture::assert_last_crate_event(RawEvent::RewardPaid(
+            worker_id,
+            account_id,
+            reward_per_block * reward_period,
+            RewardPaymentType::RegularReward,
+        ));
     });
 }
 

+ 17 - 3
runtime-modules/working-group/src/types.rs

@@ -29,13 +29,13 @@ pub(crate) struct ApplicationInfo<T: crate::Trait<I>, I: crate::Instance> {
 }
 
 // WorkerId - Worker - helper struct.
-pub(crate) struct WorkerInfo<T: common::Trait + frame_system::Trait + balances::Trait> {
+pub(crate) struct WorkerInfo<T: common::membership::Trait + frame_system::Trait + balances::Trait> {
     pub worker_id: WorkerId<T>,
     pub worker: Worker<T>,
 }
 
-impl<T: common::Trait + frame_system::Trait + balances::Trait> From<(WorkerId<T>, Worker<T>)>
-    for WorkerInfo<T>
+impl<T: common::membership::Trait + frame_system::Trait + balances::Trait>
+    From<(WorkerId<T>, Worker<T>)> for WorkerInfo<T>
 {
     fn from((worker_id, worker): (WorkerId<T>, Worker<T>)) -> Self {
         WorkerInfo { worker_id, worker }
@@ -72,6 +72,9 @@ pub struct Opening<BlockNumber: Ord, Balance> {
 
     /// Reward per block for the job opening.
     pub reward_per_block: Option<Balance>,
+
+    /// Stake used to create the opening.
+    pub creation_stake: Balance,
 }
 
 /// Defines type of the opening: regular working group fellow or group leader.
@@ -255,3 +258,14 @@ pub type ApplyOnOpeningParameters<T> = ApplyOnOpeningParams<
     <T as frame_system::Trait>::AccountId,
     BalanceOf<T>,
 >;
+
+/// Reward payment type enum.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq, Copy)]
+pub enum RewardPaymentType {
+    /// The reward was missed.
+    MissedReward,
+
+    /// The reward was paid in time.
+    RegularReward,
+}

+ 7 - 4
runtime/Cargo.toml

@@ -4,7 +4,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 = '8.0.1'
+version = '8.1.0'
 
 [dependencies]
 # Third-party dependencies
@@ -76,8 +76,9 @@ proposals-engine = { package = 'pallet-proposals-engine', default-features = fal
 proposals-discussion = { package = 'pallet-proposals-discussion', default-features = false, path = '../runtime-modules/proposals/discussion'}
 proposals-codex = { package = 'pallet-proposals-codex', default-features = false, path = '../runtime-modules/proposals/codex'}
 content-directory = { package = 'pallet-content-directory', default-features = false, path = '../runtime-modules/content-directory' }
-pallet_constitution = { package = 'pallet-constitution', default-features = false, path = '../runtime-modules/constitution' }
+pallet-constitution = { package = 'pallet-constitution', default-features = false, path = '../runtime-modules/constitution' }
 staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../runtime-modules/staking-handler'}
+bounty = { package = 'pallet-bounty', default-features = false, path = '../runtime-modules/bounty'}
 blog = { package = 'pallet-blog', default-features = false, path = '../runtime-modules/blog'}
 joystream-utility = { package = 'pallet-utility', default-features = false, path = '../runtime-modules/utility'}
 
@@ -151,8 +152,9 @@ std = [
     'proposals-discussion/std',
     'proposals-codex/std',
     'content-directory/std',
-    'pallet_constitution/std',
+    'pallet-constitution/std',
     'staking-handler/std',
+    'bounty/std',
     'blog/std',
     'joystream-utility/std'
 ]
@@ -172,12 +174,13 @@ runtime-benchmarks = [
     "proposals-engine/runtime-benchmarks",
     "proposals-codex/runtime-benchmarks",
     "joystream-utility/runtime-benchmarks",
-    "pallet_constitution/runtime-benchmarks",
+    "pallet-constitution/runtime-benchmarks",
     "working-group/runtime-benchmarks",
     "forum/runtime-benchmarks",
     "membership/runtime-benchmarks",
     "council/runtime-benchmarks",
     "referendum/runtime-benchmarks",
+    "bounty/runtime-benchmarks",
     "blog/runtime-benchmarks",
     "hex-literal",
 ]

+ 11 - 0
runtime/src/constants.rs

@@ -112,6 +112,7 @@ parameter_types! {
     pub const MembershipWorkingGroupLockId: LockIdentifier = [9; 8];
     pub const InvitedMemberLockId: LockIdentifier = [10; 8];
     pub const StakingCandidateLockId: LockIdentifier = [11; 8];
+    pub const BountyLockId: LockIdentifier = [12; 8];
 }
 
 // Staking lock ID used by nomination and validation in the staking pallet.
@@ -197,6 +198,11 @@ lazy_static! {
             VotingLockId::get(),
             StakingCandidateLockId::get(),
         ].to_vec()),
+        // Bounty
+        (BountyLockId::get(), [
+            VotingLockId::get(),
+            StakingCandidateLockId::get(),
+        ].to_vec()),
     ]
     .iter()
     .fold(BTreeSet::new(), |mut acc, item| {
@@ -208,6 +214,11 @@ lazy_static! {
     });
 }
 
+// Change it when changing the currency constants!
+parameter_types! {
+    pub const ExistentialDeposit: u128 = 10;
+}
+
 pub mod currency {
     use super::Balance;
 

+ 65 - 19
runtime/src/lib.rs

@@ -96,8 +96,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
     spec_name: create_runtime_str!("joystream-node"),
     impl_name: create_runtime_str!("joystream-node"),
     authoring_version: 8,
-    spec_version: 0,
-    impl_version: 1,
+    spec_version: 1,
+    impl_version: 0,
     apis: crate::runtime_api::EXPORTED_RUNTIME_API_VERSIONS,
     transaction_version: 1,
 };
@@ -249,9 +249,6 @@ impl pallet_timestamp::Trait for Runtime {
 }
 
 parameter_types! {
-    pub const ExistentialDeposit: u128 = 0;
-    pub const TransferFee: u128 = 0;
-    pub const CreationFee: u128 = 0;
     pub const MaxLocks: u32 = 50;
 }
 
@@ -603,9 +600,6 @@ impl memo::Trait for Runtime {
 
 parameter_types! {
     pub const MaxObjectsPerInjection: u32 = 100;
-    pub const DefaultMembershipPrice: Balance = 100;
-    // The candidate stake should be more than the transaction fee which currently is 53
-    pub const CandidateStake: Balance = 200;
 }
 
 impl storage::data_object_type_registry::Trait for Runtime {
@@ -629,11 +623,19 @@ impl storage::data_object_storage_registry::Trait for Runtime {
     type ContentIdExists = DataDirectory;
 }
 
-impl common::Trait for Runtime {
+impl common::membership::Trait for Runtime {
     type MemberId = MemberId;
     type ActorId = ActorId;
 }
 
+parameter_types! {
+    pub const DefaultMembershipPrice: Balance = 100;
+    pub const ReferralCutMaximumPercent: u8 = 50;
+    pub const DefaultInitialInvitationBalance: Balance = 100;
+    // The candidate stake should be more than the transaction fee which currently is 53
+    pub const CandidateStake: Balance = 200;
+}
+
 impl membership::Trait for Runtime {
     type Event = Event;
     type DefaultMembershipPrice = DefaultMembershipPrice;
@@ -642,11 +644,11 @@ impl membership::Trait for Runtime {
     type StakingCandidateStakingHandler = StakingCandidateStakingHandler;
     type WorkingGroup = MembershipWorkingGroup;
     type WeightInfo = weights::membership::WeightInfo;
+    type ReferralCutMaximumPercent = ReferralCutMaximumPercent;
     type CandidateStake = CandidateStake;
 }
 
 parameter_types! {
-    pub const DefaultInitialInvitationBalance: Balance = 100;
     pub const MaxCategoryDepth: u64 = 6;
     pub const MaxSubcategories: u64 = 20;
     pub const MaxThreadsInCategory: u64 = 20;
@@ -657,14 +659,13 @@ parameter_types! {
     pub const ThreadDeposit: u64 = 30;
     pub const PostDeposit: u64 = 10;
     pub const ForumModuleId: ModuleId = ModuleId(*b"mo:forum"); // module : forum
+    pub const PostLifeTime: BlockNumber = 3600;
 }
 
 pub struct MapLimits;
 
 impl forum::StorageLimits for MapLimits {
     type MaxSubcategories = MaxSubcategories;
-    type MaxThreadsInCategory = MaxThreadsInCategory;
-    type MaxPostsInThread = MaxPostsInThread;
     type MaxModeratorsForCategory = MaxModeratorsForCategory;
     type MaxCategories = MaxCategories;
     type MaxPollAlternativesNumber = MaxPollAlternativesNumber;
@@ -691,6 +692,7 @@ impl forum::Trait for Runtime {
 
     type WorkingGroup = ForumWorkingGroup;
     type MemberOriginValidator = Members;
+    type PostLifeTime = PostLifeTime;
 }
 
 impl LockComparator<<Runtime as pallet_balances::Trait>::Balance> for Runtime {
@@ -708,10 +710,14 @@ parameter_types! {
     pub const StorageWorkingGroupRewardPeriod: u32 = 14400 + 20;
     pub const ContentWorkingGroupRewardPeriod: u32 = 14400 + 30;
     pub const MembershipRewardPeriod: u32 = 14400 + 40;
+    // This should be more costly than `apply_on_opening` fee with the current configuration
+    // the base cost of `apply_on_opening` in tokens is 193. And has a very slight slope
+    // with the lenght with the length of rationale, with 2000 stake we are probably safe.
+    pub const MinimumApplicationStake: Balance = 2000;
     // This should be more costly than `add_opening` fee with the current configuration
-    // the base cost of `add_opening` in tokens is 193. And has a very slight slope
+    // the base cost of `add_opening` in tokens is 81. And has a very slight slope
     // with the lenght with the length of rationale, with 2000 stake we are probably safe.
-    pub const MinimumStakeForOpening: Balance = 2000;
+    pub const LeaderOpeningStake: Balance = 2000;
 }
 
 // Staking managers type aliases.
@@ -749,7 +755,8 @@ impl working_group::Trait<ForumWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = ForumWorkingGroupRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<StorageWorkingGroupInstance> for Runtime {
@@ -761,7 +768,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = StorageWorkingGroupRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Runtime {
@@ -773,7 +781,8 @@ impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = ContentWorkingGroupRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<MembershipWorkingGroupInstance> for Runtime {
@@ -785,7 +794,8 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = MembershipRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl service_discovery::Trait for Runtime {
@@ -826,6 +836,10 @@ impl Default for Call {
 
 parameter_types! {
     pub const MaxWhiteListSize: u32 = 20;
+    pub const ProposalsPostDeposit: Balance = 2000;
+    // module : proposals_discussion
+    pub const ProposalsDiscussionModuleId: ModuleId = ModuleId(*b"mo:prdis");
+    pub const ForumPostLifeTime: BlockNumber = 3600;
 }
 
 macro_rules! call_wg {
@@ -847,6 +861,9 @@ impl proposals_discussion::Trait for Runtime {
     type PostId = PostId;
     type MaxWhiteListSize = MaxWhiteListSize;
     type WeightInfo = weights::proposals_discussion::WeightInfo;
+    type PostDeposit = ProposalsPostDeposit;
+    type ModuleId = ProposalsDiscussionModuleId;
+    type PostLifeTime = ForumPostLifeTime;
 }
 
 impl joystream_utility::Trait for Runtime {
@@ -909,9 +926,35 @@ impl pallet_constitution::Trait for Runtime {
     type WeightInfo = weights::pallet_constitution::WeightInfo;
 }
 
+parameter_types! {
+    pub const BountyModuleId: ModuleId = ModuleId(*b"m:bounty"); // module : bounty
+    pub const ClosedContractSizeLimit: u32 = 50;
+    pub const MinCherryLimit: Balance = 10;
+    pub const MinFundingLimit: Balance = 10;
+    pub const MinWorkEntrantStake: Balance = 100;
+}
+
+impl bounty::Trait for Runtime {
+    type Event = Event;
+    type ModuleId = BountyModuleId;
+    type BountyId = u64;
+    type Membership = Members;
+    type WeightInfo = weights::bounty::WeightInfo;
+    type CouncilBudgetManager = Council;
+    type StakingHandler = staking_handler::StakingManager<Self, BountyLockId>;
+    type EntryId = u64;
+    type ClosedContractSizeLimit = ClosedContractSizeLimit;
+    type MinCherryLimit = MinCherryLimit;
+    type MinFundingLimit = MinFundingLimit;
+    type MinWorkEntrantStake = MinWorkEntrantStake;
+}
+
 parameter_types! {
     pub const PostsMaxNumber: u64 = 20;
     pub const RepliesMaxNumber: u64 = 100;
+    pub const ReplyDeposit: Balance = 2000;
+    pub const BlogModuleId: ModuleId = ModuleId(*b"mod:blog"); // module : forum
+    pub const ReplyLifetime: BlockNumber = 43_200;
 }
 
 pub type BlogInstance = blog::Instance1;
@@ -919,11 +962,13 @@ impl blog::Trait<BlogInstance> for Runtime {
     type Event = Event;
 
     type PostsMaxNumber = PostsMaxNumber;
-    type RepliesMaxNumber = RepliesMaxNumber;
     type ParticipantEnsureOrigin = Members;
     type WeightInfo = weights::blog::WeightInfo;
 
     type ReplyId = u64;
+    type ReplyDeposit = ReplyDeposit;
+    type ModuleId = BlogModuleId;
+    type ReplyLifetime = ReplyLifetime;
 }
 
 /// Forum identifier for category
@@ -978,6 +1023,7 @@ construct_runtime!(
         Forum: forum::{Module, Call, Storage, Event<T>, Config<T>},
         ContentDirectory: content_directory::{Module, Call, Storage, Event<T>, Config<T>},
         Constitution: pallet_constitution::{Module, Call, Storage, Event},
+        Bounty: bounty::{Module, Call, Storage, Event<T>},
         Blog: blog::<Instance1>::{Module, Call, Storage, Event<T>},
         JoystreamUtility: joystream_utility::{Module, Call, Event<T>},
         // --- Storage

+ 11 - 6
runtime/src/runtime_api.rs

@@ -294,8 +294,10 @@ impl_runtime_apis! {
             use crate::ImOnline;
             use crate::Council;
             use crate::Referendum;
+            use crate::Bounty;
             use crate::Blog;
             use crate::JoystreamUtility;
+            use crate::Staking;
 
 
             // Trying to add benchmarks directly to the Session Pallet caused cyclic dependency issues.
@@ -303,8 +305,8 @@ impl_runtime_apis! {
             // we need these two lines below.
             impl pallet_session_benchmarking::Trait for Runtime {}
             impl frame_system_benchmarking::Trait for Runtime {}
-            impl referendum::OptionCreator<<Runtime as frame_system::Trait>::AccountId, <Runtime as common::Trait>::MemberId> for Runtime {
-                fn create_option(account_id: <Runtime as frame_system::Trait>::AccountId, member_id: <Runtime as common::Trait>::MemberId) {
+            impl referendum::OptionCreator<<Runtime as frame_system::Trait>::AccountId, <Runtime as common::membership::Trait>::MemberId> for Runtime {
+                fn create_option(account_id: <Runtime as frame_system::Trait>::AccountId, member_id: <Runtime as common::membership::Trait>::MemberId) {
                     crate::council::Module::<Runtime>::announce_candidacy(
                         RawOrigin::Signed(account_id.clone()).into(),
                         member_id,
@@ -321,15 +323,15 @@ impl_runtime_apis! {
 
             impl membership::MembershipWorkingGroupHelper<
                 <Runtime as frame_system::Trait>::AccountId,
-                <Runtime as common::Trait>::MemberId,
-                <Runtime as common::Trait>::ActorId,
+                <Runtime as common::membership::Trait>::MemberId,
+                <Runtime as common::membership::Trait>::ActorId,
                     > for Runtime
             {
                 fn insert_a_lead(
                     opening_id: u32,
                     caller_id: &<Runtime as frame_system::Trait>::AccountId,
-                    member_id: <Runtime as common::Trait>::MemberId,
-                ) -> <Runtime as common::Trait>::ActorId {
+                    member_id: <Runtime as common::membership::Trait>::MemberId,
+                ) -> <Runtime as common::membership::Trait>::ActorId {
                     working_group::benchmarking::complete_opening::<Runtime, crate::MembershipWorkingGroupInstance>(
                         working_group::OpeningType::Leader,
                         opening_id,
@@ -373,6 +375,8 @@ impl_runtime_apis! {
             add_benchmark!(params, batches, pallet_timestamp, Timestamp);
             add_benchmark!(params, batches, pallet_session, SessionBench::<Runtime>);
             add_benchmark!(params, batches, pallet_im_online, ImOnline);
+            add_benchmark!(params, batches, pallet_balances, Balances);
+            add_benchmark!(params, batches, pallet_staking, Staking);
 
             // Joystream Benchmarks
             add_benchmark!(params, batches, proposals_discussion, ProposalsDiscussion);
@@ -384,6 +388,7 @@ impl_runtime_apis! {
             add_benchmark!(params, batches, working_group, ContentDirectoryWorkingGroup);
             add_benchmark!(params, batches, referendum, Referendum);
             add_benchmark!(params, batches, council, Council);
+            add_benchmark!(params, batches, bounty, Bounty);
             add_benchmark!(params, batches, blog, Blog);
             add_benchmark!(params, batches, joystream_utility, JoystreamUtility);
 

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