Browse Source

Merge pull request #194 from shamil-gadelshin/proposals_v2_iteration6

Proposals system. Version 2. Iteration6
Bedeho Mender 5 years ago
parent
commit
09c8b3a335
47 changed files with 1860 additions and 2567 deletions
  1. 1 1
      .travis.yml
  2. 3 0
      Cargo.toml
  3. 0 6
      modules/proposals/Cargo.toml
  4. 0 53
      modules/proposals/codex/src/proposal_types/mod.rs
  5. 0 27
      modules/proposals/codex/src/proposal_types/parameters.rs
  6. 0 39
      modules/proposals/codex/src/proposal_types/runtime_upgrade.rs
  7. 0 38
      modules/proposals/codex/src/proposal_types/text_proposal.rs
  8. 0 195
      modules/proposals/discussion/src/lib.rs
  9. 0 83
      modules/proposals/engine/src/tests/mock/proposals.rs
  10. 1 0
      runtime-modules/common/src/lib.rs
  11. 5 0
      runtime-modules/common/src/origin_validator.rs
  12. 1 1
      runtime-modules/content-working-group/src/lib.rs
  13. 4 52
      runtime-modules/content-working-group/src/tests.rs
  14. 1 1
      runtime-modules/governance/src/council.rs
  15. 0 1
      runtime-modules/governance/src/lib.rs
  16. 1 1
      runtime-modules/governance/src/mock.rs
  17. 0 1572
      runtime-modules/governance/src/proposals.rs
  18. 1 1
      runtime-modules/membership/src/lib.rs
  19. 24 10
      runtime-modules/proposals/codex/Cargo.toml
  20. 129 30
      runtime-modules/proposals/codex/src/lib.rs
  21. 31 0
      runtime-modules/proposals/codex/src/proposal_types/mod.rs
  22. 39 25
      runtime-modules/proposals/codex/src/tests/mock.rs
  23. 19 4
      runtime-modules/proposals/codex/src/tests/mod.rs
  24. 27 10
      runtime-modules/proposals/discussion/Cargo.toml
  25. 321 0
      runtime-modules/proposals/discussion/src/lib.rs
  26. 72 8
      runtime-modules/proposals/discussion/src/tests/mock.rs
  27. 75 30
      runtime-modules/proposals/discussion/src/tests/mod.rs
  28. 31 2
      runtime-modules/proposals/discussion/src/types.rs
  29. 28 18
      runtime-modules/proposals/engine/Cargo.toml
  30. 0 4
      runtime-modules/proposals/engine/src/errors.rs
  31. 305 180
      runtime-modules/proposals/engine/src/lib.rs
  32. 1 1
      runtime-modules/proposals/engine/src/tests/mock/balance_manager.rs
  33. 44 33
      runtime-modules/proposals/engine/src/tests/mock/mod.rs
  34. 18 0
      runtime-modules/proposals/engine/src/tests/mock/proposals.rs
  35. 0 0
      runtime-modules/proposals/engine/src/tests/mock/stakes.rs
  36. 90 90
      runtime-modules/proposals/engine/src/tests/mod.rs
  37. 52 16
      runtime-modules/proposals/engine/src/types/mod.rs
  38. 27 19
      runtime-modules/proposals/engine/src/types/proposal_statuses.rs
  39. 2 1
      runtime-modules/proposals/engine/src/types/stakes.rs
  40. 18 0
      runtime/Cargo.toml
  41. 13 8
      runtime/build.rs
  42. 1 0
      runtime/src/integration/mod.rs
  43. 206 0
      runtime/src/integration/proposals/council_origin_validator.rs
  44. 141 0
      runtime/src/integration/proposals/membership_origin_validator.rs
  45. 7 0
      runtime/src/integration/proposals/mod.rs
  46. 47 0
      runtime/src/integration/proposals/staking_events_handler.rs
  47. 74 7
      runtime/src/lib.rs

+ 1 - 1
.travis.yml

@@ -1,7 +1,7 @@
 language: rust
 
 rust:
-  - 1.41.1
+  - 1.42.0
 # Caching saves a lot of time but often causes stalled builds...
 # disabled for now
 # look into solution here: https://levans.fr/rust_travis_cache.html

+ 3 - 0
Cargo.toml

@@ -1,6 +1,9 @@
 [workspace]
 members = [
 	"runtime",
+	"runtime-modules/proposals/engine",
+	"runtime-modules/proposals/codex",
+	"runtime-modules/proposals/discussion",
 	"runtime-modules/common",
 	"runtime-modules/content-working-group",
 	"runtime-modules/forum",

+ 0 - 6
modules/proposals/Cargo.toml

@@ -1,6 +0,0 @@
-[workspace]
-members = [
-	"engine",
-	"codex",
-	"discussion",
-]

+ 0 - 53
modules/proposals/codex/src/proposal_types/mod.rs

@@ -1,53 +0,0 @@
-use codec::Decode;
-use num_enum::{IntoPrimitive, TryFromPrimitive};
-use rstd::convert::TryFrom;
-use rstd::prelude::*;
-
-use crate::{ProposalCodeDecoder, ProposalExecutable};
-
-pub mod parameters;
-mod runtime_upgrade;
-mod text_proposal;
-
-pub use runtime_upgrade::RuntimeUpgradeProposalExecutable;
-pub use text_proposal::TextProposalExecutable;
-
-/// Defines allowed proposals types. Integer value serves as proposal_type_id.
-#[derive(Debug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)]
-#[repr(u32)]
-pub enum ProposalType {
-    /// Text(signal) proposal type
-    Text = 1,
-
-    /// Runtime upgrade proposal type
-    RuntimeUpgrade = 2,
-}
-
-impl ProposalType {
-    fn compose_executable<T: system::Trait>(
-        &self,
-        proposal_data: Vec<u8>,
-    ) -> Result<Box<dyn ProposalExecutable>, &'static str> {
-        match self {
-            ProposalType::Text => TextProposalExecutable::decode(&mut &proposal_data[..])
-                .map_err(|err| err.what())
-                .map(|obj| Box::new(obj) as Box<dyn ProposalExecutable>),
-            ProposalType::RuntimeUpgrade => {
-                <RuntimeUpgradeProposalExecutable<T>>::decode(&mut &proposal_data[..])
-                    .map_err(|err| err.what())
-                    .map(|obj| Box::new(obj) as Box<dyn ProposalExecutable>)
-            }
-        }
-    }
-}
-
-impl<T: system::Trait> ProposalCodeDecoder<T> for ProposalType {
-    fn decode_proposal(
-        proposal_type: u32,
-        proposal_code: Vec<u8>,
-    ) -> Result<Box<dyn ProposalExecutable>, &'static str> {
-        Self::try_from(proposal_type)
-            .map_err(|_| "Unsupported proposal type")?
-            .compose_executable::<T>(proposal_code)
-    }
-}

+ 0 - 27
modules/proposals/codex/src/proposal_types/parameters.rs

@@ -1,27 +0,0 @@
-use crate::{BalanceOf, ProposalParameters};
-
-// Proposal parameters for the upgrade runtime proposal
-pub(crate) fn upgrade_runtime<T: crate::Trait>() -> ProposalParameters<T::BlockNumber, BalanceOf<T>>
-{
-    ProposalParameters {
-        voting_period: T::BlockNumber::from(50000u32),
-        grace_period: T::BlockNumber::from(10000u32),
-        approval_quorum_percentage: 80,
-        approval_threshold_percentage: 80,
-        slashing_quorum_percentage: 80,
-        slashing_threshold_percentage: 80,
-        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
-    }
-}
-// Proposal parameters for the text proposal
-pub(crate) fn text_proposal<T: crate::Trait>() -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
-    ProposalParameters {
-        voting_period: T::BlockNumber::from(50000u32),
-        grace_period: T::BlockNumber::from(10000u32),
-        approval_quorum_percentage: 40,
-        approval_threshold_percentage: 51,
-        slashing_quorum_percentage: 80,
-        slashing_threshold_percentage: 80,
-        required_stake: Some(<BalanceOf<T>>::from(500u32)),
-    }
-}

+ 0 - 39
modules/proposals/codex/src/proposal_types/runtime_upgrade.rs

@@ -1,39 +0,0 @@
-use codec::{Decode, Encode};
-use rstd::marker::PhantomData;
-use rstd::prelude::*;
-
-use runtime_primitives::traits::ModuleDispatchError;
-use srml_support::dispatch;
-
-use crate::{ProposalExecutable, ProposalType};
-
-/// Text (signal) proposal executable code wrapper. Prints its content on execution.
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default)]
-pub struct RuntimeUpgradeProposalExecutable<T> {
-    /// Proposal title
-    pub title: Vec<u8>,
-
-    /// Proposal description
-    pub description: Vec<u8>,
-
-    /// Text proposal main text
-    pub wasm: Vec<u8>,
-
-    /// Marker for the system::Trait. Required to execute runtime upgrade proposal on exact runtime.
-    pub marker: PhantomData<T>,
-}
-
-impl<T> RuntimeUpgradeProposalExecutable<T> {
-    /// Converts runtime proposal type to proposal_type_id
-    pub fn proposal_type(&self) -> u32 {
-        ProposalType::RuntimeUpgrade.into()
-    }
-}
-
-impl<T: system::Trait> ProposalExecutable for RuntimeUpgradeProposalExecutable<T> {
-    fn execute(&self) -> dispatch::Result {
-        // Update wasm code of node's runtime:
-        <system::Module<T>>::set_code(system::RawOrigin::Root.into(), self.wasm.clone())
-            .map_err(|err| err.as_str())
-    }
-}

+ 0 - 38
modules/proposals/codex/src/proposal_types/text_proposal.rs

@@ -1,38 +0,0 @@
-use codec::{Decode, Encode};
-use rstd::prelude::*;
-
-use rstd::str::from_utf8;
-use srml_support::{dispatch, print};
-
-use crate::{ProposalExecutable, ProposalType};
-
-/// Text (signal) proposal executable code wrapper. Prints its content on execution.
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default)]
-pub struct TextProposalExecutable {
-    /// Text proposal title
-    pub title: Vec<u8>,
-
-    /// Text proposal description
-    pub description: Vec<u8>,
-
-    /// Text proposal main text
-    pub text: Vec<u8>,
-}
-
-impl TextProposalExecutable {
-    /// Converts text proposal type to proposal_type_id
-    pub fn proposal_type(&self) -> u32 {
-        ProposalType::Text.into()
-    }
-}
-
-impl ProposalExecutable for TextProposalExecutable {
-    fn execute(&self) -> dispatch::Result {
-        print("Proposal: ");
-        print(from_utf8(self.title.as_slice()).unwrap());
-        print("Description:");
-        print(from_utf8(self.description.as_slice()).unwrap());
-
-        Ok(())
-    }
-}

+ 0 - 195
modules/proposals/discussion/src/lib.rs

@@ -1,195 +0,0 @@
-//! Proposals discussion module for the Joystream platform. Version 2.
-//! Contains discussion subsystem for the proposals engine.
-//!
-//! Supported extrinsics:
-//! - add_post - adds a post to existing discussion thread
-//!
-//! Public API:
-//! - create_discussion - creates a discussion
-//!
-
-// Ensure we're `no_std` when compiling for Wasm.
-#![cfg_attr(not(feature = "std"), no_std)]
-
-// Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
-//#![warn(missing_docs)]
-
-#[cfg(test)]
-mod tests;
-mod types;
-
-use rstd::clone::Clone;
-use rstd::prelude::*;
-use rstd::vec::Vec;
-use runtime_primitives::traits::EnsureOrigin;
-use srml_support::{decl_module, decl_storage, ensure, Parameter};
-
-use srml_support::traits::Get;
-use types::{Post, Thread};
-
-// TODO: create events
-// TODO: move errors to decl_error macro
-
-pub(crate) const MSG_NOT_AUTHOR: &str = "Author should match the post creator";
-pub(crate) const MSG_POST_EDITION_NUMBER_EXCEEDED: &str = "Post edition limit reached.";
-pub(crate) const MSG_EMPTY_TITLE_PROVIDED: &str = "Discussion cannot have an empty title";
-pub(crate) const MSG_TOO_LONG_TITLE: &str = "Title is too long";
-pub(crate) const MSG_THREAD_DOESNT_EXIST: &str = "Thread doesn't exist";
-pub(crate) const MSG_POST_DOESNT_EXIST: &str = "Post doesn't exist";
-pub(crate) const MSG_EMPTY_POST_PROVIDED: &str = "Post cannot be empty";
-pub(crate) const MSG_TOO_LONG_POST: &str = "Post is too long";
-
-/// 'Proposal discussion' substrate module Trait
-pub trait Trait: system::Trait {
-    /// Origin from which thread author must come.
-    type ThreadAuthorOrigin: EnsureOrigin<Self::Origin, Success = Self::AccountId>;
-
-    /// Origin from which commenter must come.
-    type PostAuthorOrigin: EnsureOrigin<Self::Origin, Success = Self::AccountId>;
-
-    /// Discussion thread Id type
-    type ThreadId: From<u32> + Into<u32> + Parameter + Default + Copy;
-
-    /// Post Id type
-    type PostId: From<u32> + Parameter + Default + Copy;
-
-    /// Type for the thread author id. Should be authenticated by account id.
-    type ThreadAuthorId: From<Self::AccountId> + Parameter + Default;
-
-    /// Type for the post author id. Should be authenticated by account id.
-    type PostAuthorId: From<Self::AccountId> + Parameter + Default;
-
-    /// Defines post edition number limit.
-    type MaxPostEditionNumber: Get<u32>;
-
-    /// Defines thread title length limit.
-    type ThreadTitleLengthLimit: Get<u32>;
-
-    /// Defines post length limit.
-    type PostLengthLimit: Get<u32>;
-}
-
-// Storage for the proposals discussion module
-decl_storage! {
-    pub trait Store for Module<T: Trait> as ProposalDiscussion {
-        /// Map thread identifier to corresponding thread.
-        pub ThreadById get(thread_by_id): map T::ThreadId =>
-            Thread<T::ThreadAuthorId, T::BlockNumber>;
-
-        /// Count of all threads that have been created.
-        pub ThreadCount get(fn thread_count): u32;
-
-        /// Map thread id and post id to corresponding post.
-        pub PostThreadIdByPostId: double_map T::ThreadId, twox_128(T::PostId) =>
-             Post<T::PostAuthorId, T::BlockNumber, T::ThreadId>;
-
-        /// Count of all posts that have been created.
-        pub PostCount get(fn post_count): u32;
-    }
-}
-
-decl_module! {
-    /// 'Proposal discussion' substrate module
-    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
-
-       /// Adds a post with author origin check.
-       pub fn add_post(origin, thread_id : T::ThreadId, text : Vec<u8>) {
-            let account_id = T::ThreadAuthorOrigin::ensure_origin(origin)?;
-            let post_author_id = T::PostAuthorId::from(account_id);
-
-            ensure!(<ThreadById<T>>::exists(thread_id), MSG_THREAD_DOESNT_EXIST);
-
-            ensure!(!text.is_empty(), MSG_EMPTY_POST_PROVIDED);
-            ensure!(
-                text.len() as u32 <= T::PostLengthLimit::get(),
-                MSG_TOO_LONG_POST
-            );
-
-            // mutation
-
-            let next_post_count_value = Self::post_count() + 1;
-            let new_post_id = next_post_count_value;
-
-            let new_post = Post {
-                text,
-                created_at: Self::current_block(),
-                updated_at: Self::current_block(),
-                author_id: post_author_id,
-                edition_number : 0,
-                thread_id,
-            };
-
-            let post_id = T::PostId::from(new_post_id);
-            <PostThreadIdByPostId<T>>::insert(thread_id, post_id, new_post);
-            PostCount::put(next_post_count_value);
-       }
-
-       /// Updates a post with author origin check. Update attempts number is limited.
-      pub fn update_post(origin, thread_id: T::ThreadId,  post_id : T::PostId, text : Vec<u8>) {
-            let account_id = T::ThreadAuthorOrigin::ensure_origin(origin)?;
-            let post_author_id = T::PostAuthorId::from(account_id);
-
-            ensure!(<ThreadById<T>>::exists(thread_id), MSG_THREAD_DOESNT_EXIST);
-            ensure!(<PostThreadIdByPostId<T>>::exists(thread_id, post_id), MSG_POST_DOESNT_EXIST);
-
-            ensure!(!text.is_empty(), MSG_EMPTY_POST_PROVIDED);
-            ensure!(
-                text.len() as u32 <= T::PostLengthLimit::get(),
-                MSG_TOO_LONG_POST
-            );
-
-            let post = <PostThreadIdByPostId<T>>::get(&thread_id, &post_id);
-
-            ensure!(post.author_id == post_author_id, MSG_NOT_AUTHOR);
-            ensure!(post.edition_number < T::MaxPostEditionNumber::get(),
-                MSG_POST_EDITION_NUMBER_EXCEEDED);
-
-            let new_post = Post {
-                text,
-                updated_at: Self::current_block(),
-                edition_number: post.edition_number + 1,
-                ..post
-            };
-
-            // mutation
-
-            <PostThreadIdByPostId<T>>::insert(thread_id, post_id, new_post);
-       }
-    }
-}
-
-impl<T: Trait> Module<T> {
-    // Wrapper-function over system::block_number()
-    fn current_block() -> T::BlockNumber {
-        <system::Module<T>>::block_number()
-    }
-
-    /// Create the discussion thread
-    pub fn create_thread(origin: T::Origin, title: Vec<u8>) -> Result<T::ThreadId, &'static str> {
-        let account_id = T::ThreadAuthorOrigin::ensure_origin(origin)?;
-        let thread_author_id = T::ThreadAuthorId::from(account_id);
-
-        ensure!(!title.is_empty(), MSG_EMPTY_TITLE_PROVIDED);
-        ensure!(
-            title.len() as u32 <= T::ThreadTitleLengthLimit::get(),
-            MSG_TOO_LONG_TITLE
-        );
-
-        let next_thread_count_value = Self::thread_count() + 1;
-        let new_thread_id = next_thread_count_value;
-
-        let new_thread = Thread {
-            title,
-            created_at: Self::current_block(),
-            author_id: thread_author_id,
-        };
-
-        // mutation
-
-        let thread_id = T::ThreadId::from(new_thread_id);
-        <ThreadById<T>>::insert(thread_id, new_thread);
-        ThreadCount::put(next_thread_count_value);
-
-        Ok(thread_id)
-    }
-}

+ 0 - 83
modules/proposals/engine/src/tests/mock/proposals.rs

@@ -1,83 +0,0 @@
-use codec::{Decode, Encode};
-use num_enum::{IntoPrimitive, TryFromPrimitive};
-use rstd::convert::TryFrom;
-use rstd::prelude::*;
-
-use srml_support::dispatch;
-
-use crate::{ProposalCodeDecoder, ProposalExecutable};
-
-use super::*;
-
-/// Defines allowed proposals types. Integer value serves as proposal_type_id.
-#[derive(Debug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)]
-#[repr(u32)]
-pub enum ProposalType {
-    /// Dummy(Text) proposal type
-    Dummy = 1,
-
-    /// Testing proposal type for faults
-    Faulty = 10001,
-}
-
-impl ProposalType {
-    fn compose_executable(
-        &self,
-        proposal_data: Vec<u8>,
-    ) -> Result<Box<dyn ProposalExecutable>, &'static str> {
-        match self {
-            ProposalType::Dummy => DummyExecutable::decode(&mut &proposal_data[..])
-                .map_err(|err| err.what())
-                .map(|obj| Box::new(obj) as Box<dyn ProposalExecutable>),
-            ProposalType::Faulty => FaultyExecutable::decode(&mut &proposal_data[..])
-                .map_err(|err| err.what())
-                .map(|obj| Box::new(obj) as Box<dyn ProposalExecutable>),
-        }
-    }
-}
-
-impl ProposalCodeDecoder<Test> for ProposalType {
-    fn decode_proposal(
-        proposal_type: u32,
-        proposal_code: Vec<u8>,
-    ) -> Result<Box<dyn ProposalExecutable>, &'static str> {
-        Self::try_from(proposal_type)
-            .map_err(|_| "Unsupported proposal type")?
-            .compose_executable(proposal_code)
-    }
-}
-
-/// Testing proposal type
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default)]
-pub struct DummyExecutable {
-    pub title: Vec<u8>,
-    pub description: Vec<u8>,
-}
-
-impl DummyExecutable {
-    pub fn proposal_type(&self) -> u32 {
-        ProposalType::Dummy.into()
-    }
-}
-
-impl ProposalExecutable for DummyExecutable {
-    fn execute(&self) -> dispatch::Result {
-        Ok(())
-    }
-}
-
-/// Faulty proposal executable code wrapper. Used for failed proposal execution tests.
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default)]
-pub struct FaultyExecutable;
-impl ProposalExecutable for FaultyExecutable {
-    fn execute(&self) -> dispatch::Result {
-        Err("ExecutionFailed")
-    }
-}
-
-impl FaultyExecutable {
-    /// Converts faulty proposal type to proposal_type_id
-    pub fn proposal_type(&self) -> u32 {
-        ProposalType::Faulty.into()
-    }
-}

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

@@ -2,3 +2,4 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 pub mod currency;
+pub mod origin_validator;

+ 5 - 0
runtime-modules/common/src/origin_validator.rs

@@ -0,0 +1,5 @@
+/// Abstract validator for the origin(account_id) and actor_id (eg.: thread author id).
+pub trait ActorOriginValidator<Origin, ActorId, AccountId> {
+    /// Check for valid combination of origin and actor_id
+    fn ensure_actor_origin(origin: Origin, actor_id: ActorId) -> Result<AccountId, &'static str>;
+}

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

@@ -1191,7 +1191,7 @@ decl_module! {
             // Increment NextChannelId
             NextChannelId::<T>::mutate(|id| *id += <ChannelId<T> as One>::one());
 
-            /// CREDENTIAL STUFF ///
+            // CREDENTIAL STUFF //
 
             // Dial out to membership module and inform about new role as channe owner.
             let registered_role = <members::Module<T>>::register_role_on_member(owner, &member_in_role).is_ok();

+ 4 - 52
runtime-modules/content-working-group/src/tests.rs

@@ -511,17 +511,11 @@ fn begin_curator_applicant_review_success() {
             let opening =
                 <hiring::OpeningById<Test>>::get(&normal_opening_constructed.curator_opening_id);
             match opening.stage {
-                hiring::OpeningStage::Active {
-                    stage,
-                    applications_added,
-                    active_application_count,
-                    unstaking_application_count,
-                    deactivated_application_count,
-                } => {
+                hiring::OpeningStage::Active { stage, .. } => {
                     match stage {
                         hiring::ActiveOpeningStage::ReviewPeriod {
-                            started_accepting_applicants_at_block,
                             started_review_period_at_block,
+                            ..
                         } => {
                             /* OK */
                             // assert_eq!(started_accepting_applicants_at_block, 0);
@@ -921,12 +915,6 @@ impl UpdateCuratorRoleAccountFixture {
 
         assert_eq!(self.new_role_account, event_new_role_account);
     }
-
-    pub fn call_and_assert_failed_result(&self, error_message: &'static str) {
-        let call_result = self.call();
-
-        assert_eq!(call_result, Err(error_message));
-    }
 }
 
 #[test]
@@ -958,6 +946,7 @@ struct UpdateCuratorRewardAccountFixture {
 }
 
 impl UpdateCuratorRewardAccountFixture {
+    #[allow(dead_code)] // delete if the method is unnecessary
     fn call(&self) -> Result<(), &'static str> {
         ContentWorkingGroup::update_curator_reward_account(
             self.origin.clone(),
@@ -966,6 +955,7 @@ impl UpdateCuratorRewardAccountFixture {
         )
     }
 
+    #[allow(dead_code)] // delete if the method is unnecessary
     pub fn call_and_assert_success(&self) {
         let _original_curator = CuratorById::<Test>::get(self.curator_id);
 
@@ -996,12 +986,6 @@ impl UpdateCuratorRewardAccountFixture {
 
         assert_eq!(self.new_reward_account, event_reward_account);
     }
-
-    pub fn call_and_assert_failed_result(&self, error_message: &'static str) {
-        let call_result = self.call();
-
-        assert_eq!(call_result, Err(error_message));
-    }
 }
 
 #[test]
@@ -1076,12 +1060,6 @@ impl LeaveCuratorRoleFixture {
          * recurringrewards, stake
          */
     }
-
-    pub fn call_and_assert_failed_result(&self, error_message: &'static str) {
-        let call_result = self.call();
-
-        assert_eq!(call_result, Err(error_message));
-    }
 }
 
 #[test]
@@ -1155,12 +1133,6 @@ impl TerminateCuratorRoleFixture {
          * recurringrewards, stake
          */
     }
-
-    pub fn call_and_assert_failed_result(&self, error_message: &'static str) {
-        let call_result = self.call();
-
-        assert_eq!(call_result, Err(error_message));
-    }
 }
 
 #[test]
@@ -1224,16 +1196,6 @@ impl SetLeadFixture {
             crate::RawEvent::LeadSet(new_lead_id)
         );
     }
-
-    pub fn call_and_assert_failed_result(&self, error_message: &'static str) {
-        let number_of_events_before_call = System::events().len();
-
-        let call_result = self.call();
-
-        assert_eq!(call_result, Err(error_message));
-
-        assert_eq!(System::events().len(), number_of_events_before_call);
-    }
 }
 
 #[test]
@@ -1288,16 +1250,6 @@ impl UnsetLeadFixture {
             crate::RawEvent::LeadUnset(original_lead_id)
         );
     }
-
-    pub fn call_and_assert_failed_result(&self, error_message: &'static str) {
-        let number_of_events_before_call = System::events().len();
-
-        let call_result = self.call();
-
-        assert_eq!(call_result, Err(error_message));
-
-        assert_eq!(System::events().len(), number_of_events_before_call);
-    }
 }
 
 #[test]

+ 1 - 1
runtime-modules/governance/src/council.rs

@@ -76,7 +76,7 @@ decl_module! {
         // Privileged methods
 
         /// Force set a zero staked council. Stakes in existing council will vanish into thin air!
-        fn set_council(origin, accounts: Vec<T::AccountId>) {
+        pub fn set_council(origin, accounts: Vec<T::AccountId>) {
             ensure_root(origin)?;
             let new_council: Seats<T::AccountId, BalanceOf<T>> = accounts.into_iter().map(|account| {
                 Seat {

+ 0 - 1
runtime-modules/governance/src/lib.rs

@@ -3,7 +3,6 @@
 
 pub mod council;
 pub mod election;
-pub mod proposals;
 
 mod sealed_vote;
 mod stake;

+ 1 - 1
runtime-modules/governance/src/mock.rs

@@ -1,6 +1,6 @@
 #![cfg(test)]
 
-pub use super::{council, election, proposals};
+pub use super::{council, election};
 pub use common::currency::GovernanceCurrency;
 pub use system;
 

+ 0 - 1572
runtime-modules/governance/src/proposals.rs

@@ -1,1572 +0,0 @@
-use codec::{Decode, Encode};
-use rstd::prelude::*;
-use sr_primitives::{
-    print,
-    traits::{Hash, SaturatedConversion, Zero},
-};
-use srml_support::traits::{Currency, Get, ReservableCurrency};
-use srml_support::{decl_event, decl_module, decl_storage, dispatch, ensure};
-use system::{self, ensure_root, ensure_signed};
-
-#[cfg(feature = "std")]
-use serde::{Deserialize, Serialize};
-
-#[cfg(test)]
-use primitives::storage::well_known_keys;
-
-use super::council;
-pub use common::currency::{BalanceOf, GovernanceCurrency};
-
-const DEFAULT_APPROVAL_QUORUM: u32 = 60;
-const DEFAULT_MIN_STAKE: u32 = 100;
-const DEFAULT_CANCELLATION_FEE: u32 = 5;
-const DEFAULT_REJECTION_FEE: u32 = 10;
-
-const DEFAULT_VOTING_PERIOD_IN_DAYS: u32 = 10;
-const DEFAULT_VOTING_PERIOD_IN_SECS: u32 = DEFAULT_VOTING_PERIOD_IN_DAYS * 24 * 60 * 60;
-
-const DEFAULT_NAME_MAX_LEN: u32 = 100;
-const DEFAULT_DESCRIPTION_MAX_LEN: u32 = 10_000;
-const DEFAULT_WASM_CODE_MAX_LEN: u32 = 2_000_000;
-
-const MSG_STAKE_IS_TOO_LOW: &str = "Stake is too low";
-const MSG_STAKE_IS_GREATER_THAN_BALANCE: &str = "Balance is too low to be staked";
-const MSG_ONLY_MEMBERS_CAN_PROPOSE: &str = "Only members can make a proposal";
-const MSG_ONLY_COUNCILORS_CAN_VOTE: &str = "Only councilors can vote on proposals";
-const MSG_PROPOSAL_NOT_FOUND: &str = "This proposal does not exist";
-const MSG_PROPOSAL_EXPIRED: &str = "Voting period is expired for this proposal";
-const MSG_PROPOSAL_FINALIZED: &str = "Proposal is finalized already";
-const MSG_YOU_ALREADY_VOTED: &str = "You have already voted on this proposal";
-const MSG_YOU_DONT_OWN_THIS_PROPOSAL: &str = "You do not own this proposal";
-const MSG_PROPOSAL_STATUS_ALREADY_UPDATED: &str = "Proposal status has been updated already";
-const MSG_EMPTY_NAME_PROVIDED: &str = "Proposal cannot have an empty name";
-const MSG_EMPTY_DESCRIPTION_PROVIDED: &str = "Proposal cannot have an empty description";
-const MSG_EMPTY_WASM_CODE_PROVIDED: &str = "Proposal cannot have an empty WASM code";
-const MSG_TOO_LONG_NAME: &str = "Name is too long";
-const MSG_TOO_LONG_DESCRIPTION: &str = "Description is too long";
-const MSG_TOO_LONG_WASM_CODE: &str = "WASM code is too big";
-
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq)]
-pub enum ProposalStatus {
-    /// A new proposal that is available for voting.
-    Active,
-    /// If cancelled by a proposer.
-    Cancelled,
-    /// Not enough votes and voting period expired.
-    Expired,
-    /// To clear the quorum requirement, the percentage of council members with revealed votes
-    /// must be no less than the quorum value for the given proposal type.
-    Approved,
-    Rejected,
-    /// If all revealed votes are slashes, then the proposal is rejected,
-    /// and the proposal stake is slashed.
-    Slashed,
-}
-
-impl Default for ProposalStatus {
-    fn default() -> Self {
-        ProposalStatus::Active
-    }
-}
-
-use self::ProposalStatus::*;
-
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub enum VoteKind {
-    /// Signals presence, but unwillingness to cast judgment on substance of vote.
-    Abstain,
-    /// Pass, an alternative or a ranking, for binary, multiple choice
-    /// and ranked choice propositions, respectively.
-    Approve,
-    /// Against proposal.
-    Reject,
-    /// Against the proposal, and slash proposal stake.
-    Slash,
-}
-
-impl Default for VoteKind {
-    fn default() -> Self {
-        VoteKind::Abstain
-    }
-}
-
-use self::VoteKind::*;
-
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
-/// Proposal for node runtime update.
-pub struct RuntimeUpgradeProposal<AccountId, Balance, BlockNumber, Hash> {
-    id: u32,
-    proposer: AccountId,
-    stake: Balance,
-    name: Vec<u8>,
-    description: Vec<u8>,
-    wasm_hash: Hash,
-    proposed_at: BlockNumber,
-    status: ProposalStatus,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
-pub struct TallyResult<BlockNumber> {
-    proposal_id: u32,
-    abstentions: u32,
-    approvals: u32,
-    rejections: u32,
-    slashes: u32,
-    status: ProposalStatus,
-    finalized_at: BlockNumber,
-}
-
-pub trait Trait:
-    timestamp::Trait + council::Trait + GovernanceCurrency + membership::members::Trait
-{
-    /// The overarching event type.
-    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
-}
-
-decl_event!(
-    pub enum Event<T>
-    where
-        <T as system::Trait>::Hash,
-        <T as system::Trait>::BlockNumber,
-        <T as system::Trait>::AccountId
-    {
-        // New events
-
-        /// Params:
-        /// * Account id of a member who proposed.
-        /// * Id of a newly created proposal after it was saved in storage.
-        ProposalCreated(AccountId, u32),
-        ProposalCanceled(AccountId, u32),
-        ProposalStatusUpdated(u32, ProposalStatus),
-
-        /// Params:
-        /// * Voter - an account id of a councilor.
-        /// * Id of a proposal.
-        /// * Kind of vote.
-        Voted(AccountId, u32, VoteKind),
-
-        TallyFinalized(TallyResult<BlockNumber>),
-
-        /// * Hash - hash of wasm code of runtime update.
-        RuntimeUpdated(u32, Hash),
-
-        /// Root cancelled proposal
-        ProposalVetoed(u32),
-    }
-);
-
-decl_storage! {
-    trait Store for Module<T: Trait> as Proposals {
-
-        // Parameters (defaut values could be exported to config):
-
-        // TODO rename 'approval_quorum' -> 'quorum_percent' ?!
-        /// A percent (up to 100) of the council participants
-        /// that must vote affirmatively in order to pass.
-        ApprovalQuorum get(approval_quorum) config(): u32 = DEFAULT_APPROVAL_QUORUM;
-
-        /// Minimum amount of a balance to be staked in order to make a proposal.
-        MinStake get(min_stake) config(): BalanceOf<T> =
-            BalanceOf::<T>::from(DEFAULT_MIN_STAKE);
-
-        /// A fee to be slashed (burn) in case a proposer decides to cancel a proposal.
-        CancellationFee get(cancellation_fee) config(): BalanceOf<T> =
-            BalanceOf::<T>::from(DEFAULT_CANCELLATION_FEE);
-
-        /// A fee to be slashed (burn) in case a proposal was rejected.
-        RejectionFee get(rejection_fee) config(): BalanceOf<T> =
-            BalanceOf::<T>::from(DEFAULT_REJECTION_FEE);
-
-        /// Max duration of proposal in blocks until it will be expired if not enough votes.
-        VotingPeriod get(voting_period) config(): T::BlockNumber =
-            T::BlockNumber::from(DEFAULT_VOTING_PERIOD_IN_SECS /
-            (<T as timestamp::Trait>::MinimumPeriod::get().saturated_into::<u32>() * 2));
-
-        NameMaxLen get(name_max_len) config(): u32 = DEFAULT_NAME_MAX_LEN;
-        DescriptionMaxLen get(description_max_len) config(): u32 = DEFAULT_DESCRIPTION_MAX_LEN;
-        WasmCodeMaxLen get(wasm_code_max_len) config(): u32 = DEFAULT_WASM_CODE_MAX_LEN;
-
-        // Persistent state (always relevant, changes constantly):
-
-        /// Count of all proposals that have been created.
-        ProposalCount get(proposal_count): u32;
-
-        /// Get proposal details by its id.
-        Proposals get(proposals): map u32 => RuntimeUpgradeProposal<T::AccountId, BalanceOf<T>, T::BlockNumber, T::Hash>;
-
-        /// Ids of proposals that are open for voting (have not been finalized yet).
-        ActiveProposalIds get(active_proposal_ids): Vec<u32> = vec![];
-
-        /// Get WASM code of runtime upgrade by hash of its content.
-        WasmCodeByHash get(wasm_code_by_hash): map T::Hash => Vec<u8>;
-
-        VotesByProposal get(votes_by_proposal): map u32 => Vec<(T::AccountId, VoteKind)>;
-
-        // TODO Rethink: this can be replaced with: votes_by_proposal.find(|vote| vote.0 == proposer)
-        VoteByAccountAndProposal get(vote_by_account_and_proposal): map (T::AccountId, u32) => VoteKind;
-
-        TallyResults get(tally_results): map u32 => TallyResult<T::BlockNumber>;
-    }
-}
-
-decl_module! {
-    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
-
-        fn deposit_event() = default;
-
-        /// Use next code to create a proposal from Substrate UI's web console:
-        /// ```js
-        /// post({ sender: runtime.indices.ss58Decode('F7Gh'), call: calls.proposals.createProposal(2500, "0x123", "0x456", "0x789") }).tie(console.log)
-        /// ```
-        fn create_proposal(
-            origin,
-            stake: BalanceOf<T>,
-            name: Vec<u8>,
-            description: Vec<u8>,
-            wasm_code: Vec<u8>
-        ) {
-
-            let proposer = ensure_signed(origin)?;
-            ensure!(Self::can_participate(&proposer), MSG_ONLY_MEMBERS_CAN_PROPOSE);
-            ensure!(stake >= Self::min_stake(), MSG_STAKE_IS_TOO_LOW);
-
-            ensure!(!name.is_empty(), MSG_EMPTY_NAME_PROVIDED);
-            ensure!(name.len() as u32 <= Self::name_max_len(), MSG_TOO_LONG_NAME);
-
-            ensure!(!description.is_empty(), MSG_EMPTY_DESCRIPTION_PROVIDED);
-            ensure!(description.len() as u32 <= Self::description_max_len(), MSG_TOO_LONG_DESCRIPTION);
-
-            ensure!(!wasm_code.is_empty(), MSG_EMPTY_WASM_CODE_PROVIDED);
-            ensure!(wasm_code.len() as u32 <= Self::wasm_code_max_len(), MSG_TOO_LONG_WASM_CODE);
-
-            // Lock proposer's stake:
-            T::Currency::reserve(&proposer, stake)
-                .map_err(|_| MSG_STAKE_IS_GREATER_THAN_BALANCE)?;
-
-            let proposal_id = Self::proposal_count() + 1;
-            ProposalCount::put(proposal_id);
-
-            // See in substrate repo @ srml/contract/src/wasm/code_cache.rs:73
-            let wasm_hash = T::Hashing::hash(&wasm_code);
-
-            let new_proposal = RuntimeUpgradeProposal {
-                id: proposal_id,
-                proposer: proposer.clone(),
-                stake,
-                name,
-                description,
-                wasm_hash,
-                proposed_at: Self::current_block(),
-                status: Active
-            };
-
-            if !<WasmCodeByHash<T>>::exists(wasm_hash) {
-              <WasmCodeByHash<T>>::insert(wasm_hash, wasm_code);
-            }
-            <Proposals<T>>::insert(proposal_id, new_proposal);
-            ActiveProposalIds::mutate(|ids| ids.push(proposal_id));
-            Self::deposit_event(RawEvent::ProposalCreated(proposer.clone(), proposal_id));
-
-            // Auto-vote with Approve if proposer is a councilor:
-            if Self::is_councilor(&proposer) {
-                Self::_process_vote(proposer, proposal_id, Approve)?;
-            }
-        }
-
-        /// Use next code to create a proposal from Substrate UI's web console:
-        /// ```js
-        /// post({ sender: runtime.indices.ss58Decode('F7Gh'), call: calls.proposals.voteOnProposal(1, { option: "Approve", _type: "VoteKind" }) }).tie(console.log)
-        /// ```
-        fn vote_on_proposal(origin, proposal_id: u32, vote: VoteKind) {
-            let voter = ensure_signed(origin)?;
-            ensure!(Self::is_councilor(&voter), MSG_ONLY_COUNCILORS_CAN_VOTE);
-
-            ensure!(<Proposals<T>>::exists(proposal_id), MSG_PROPOSAL_NOT_FOUND);
-            let proposal = Self::proposals(proposal_id);
-
-            ensure!(proposal.status == Active, MSG_PROPOSAL_FINALIZED);
-
-            let not_expired = !Self::is_voting_period_expired(proposal.proposed_at);
-            ensure!(not_expired, MSG_PROPOSAL_EXPIRED);
-
-            let did_not_vote_before = !<VoteByAccountAndProposal<T>>::exists((voter.clone(), proposal_id));
-            ensure!(did_not_vote_before, MSG_YOU_ALREADY_VOTED);
-
-            Self::_process_vote(voter, proposal_id, vote)?;
-        }
-
-        // TODO add 'reason' why a proposer wants to cancel (UX + feedback)?
-        /// Cancel a proposal by its original proposer. Some fee will be withdrawn from his balance.
-        fn cancel_proposal(origin, proposal_id: u32) {
-            let proposer = ensure_signed(origin)?;
-
-            ensure!(<Proposals<T>>::exists(proposal_id), MSG_PROPOSAL_NOT_FOUND);
-            let proposal = Self::proposals(proposal_id);
-
-            ensure!(proposer == proposal.proposer, MSG_YOU_DONT_OWN_THIS_PROPOSAL);
-            ensure!(proposal.status == Active, MSG_PROPOSAL_FINALIZED);
-
-            // Spend some minimum fee on proposer's balance for canceling a proposal
-            let fee = Self::cancellation_fee();
-            let _ = T::Currency::slash_reserved(&proposer, fee);
-
-            // Return unspent part of remaining staked deposit (after taking some fee)
-            let left_stake = proposal.stake - fee;
-            let _ = T::Currency::unreserve(&proposer, left_stake);
-
-            Self::_update_proposal_status(proposal_id, Cancelled)?;
-            Self::deposit_event(RawEvent::ProposalCanceled(proposer, proposal_id));
-        }
-
-        // Called on every block
-        fn on_finalize(n: T::BlockNumber) {
-            if let Err(e) = Self::end_block(n) {
-                print(e);
-            }
-        }
-
-        /// Cancel a proposal and return stake without slashing
-        fn veto_proposal(origin, proposal_id: u32) {
-            ensure_root(origin)?;
-            ensure!(<Proposals<T>>::exists(proposal_id), MSG_PROPOSAL_NOT_FOUND);
-            let proposal = Self::proposals(proposal_id);
-            ensure!(proposal.status == Active, MSG_PROPOSAL_FINALIZED);
-
-            let _ = T::Currency::unreserve(&proposal.proposer, proposal.stake);
-
-            Self::_update_proposal_status(proposal_id, Cancelled)?;
-
-            Self::deposit_event(RawEvent::ProposalVetoed(proposal_id));
-        }
-
-        fn set_approval_quorum(origin, new_value: u32) {
-            ensure_root(origin)?;
-            ensure!(new_value > 0, "approval quorom must be greater than zero");
-            ApprovalQuorum::put(new_value);
-        }
-    }
-}
-
-impl<T: Trait> Module<T> {
-    fn current_block() -> T::BlockNumber {
-        <system::Module<T>>::block_number()
-    }
-
-    fn can_participate(sender: &T::AccountId) -> bool {
-        !T::Currency::free_balance(sender).is_zero()
-            && <membership::members::Module<T>>::is_member_account(sender)
-    }
-
-    fn is_councilor(sender: &T::AccountId) -> bool {
-        <council::Module<T>>::is_councilor(sender)
-    }
-
-    fn councilors_count() -> u32 {
-        <council::Module<T>>::active_council().len() as u32
-    }
-
-    fn approval_quorum_seats() -> u32 {
-        (Self::approval_quorum() * Self::councilors_count()) / 100
-    }
-
-    fn is_voting_period_expired(proposed_at: T::BlockNumber) -> bool {
-        Self::current_block() >= proposed_at + Self::voting_period()
-    }
-
-    fn _process_vote(voter: T::AccountId, proposal_id: u32, vote: VoteKind) -> dispatch::Result {
-        let new_vote = (voter.clone(), vote.clone());
-        if <VotesByProposal<T>>::exists(proposal_id) {
-            // Append a new vote to other votes on this proposal:
-            <VotesByProposal<T>>::mutate(proposal_id, |votes| votes.push(new_vote));
-        } else {
-            // This is the first vote on this proposal:
-            <VotesByProposal<T>>::insert(proposal_id, vec![new_vote]);
-        }
-        <VoteByAccountAndProposal<T>>::insert((voter.clone(), proposal_id), &vote);
-        Self::deposit_event(RawEvent::Voted(voter, proposal_id, vote));
-        Ok(())
-    }
-
-    fn end_block(_now: T::BlockNumber) -> dispatch::Result {
-        // TODO refactor this method
-
-        // TODO iterate over not expired proposals and tally
-
-        Self::tally()?;
-        // TODO approve or reject a proposal
-
-        Ok(())
-    }
-
-    /// Get the voters for the current proposal.
-    pub fn tally() -> dispatch::Result {
-        let councilors: u32 = Self::councilors_count();
-        let quorum: u32 = Self::approval_quorum_seats();
-
-        for &proposal_id in Self::active_proposal_ids().iter() {
-            let votes = Self::votes_by_proposal(proposal_id);
-            let mut abstentions: u32 = 0;
-            let mut approvals: u32 = 0;
-            let mut rejections: u32 = 0;
-            let mut slashes: u32 = 0;
-
-            for (_, vote) in votes.iter() {
-                match vote {
-                    Abstain => abstentions += 1,
-                    Approve => approvals += 1,
-                    Reject => rejections += 1,
-                    Slash => slashes += 1,
-                }
-            }
-
-            let proposal = Self::proposals(proposal_id);
-            let is_expired = Self::is_voting_period_expired(proposal.proposed_at);
-
-            // We need to check that the council is not empty because otherwise,
-            // if there is no votes on a proposal it will be counted as if
-            // all 100% (zero) councilors voted on the proposal and should be approved.
-
-            let non_empty_council = councilors > 0;
-            let all_councilors_voted = non_empty_council && votes.len() as u32 == councilors;
-            let all_councilors_slashed = non_empty_council && slashes == councilors;
-            let quorum_reached = quorum > 0 && approvals >= quorum;
-
-            // Don't approve a proposal right after quorum reached
-            // if not all councilors casted their votes.
-            // Instead let other councilors cast their vote
-            // up until the proposal's expired.
-
-            let new_status: Option<ProposalStatus> = if all_councilors_slashed {
-                Some(Slashed)
-            } else if all_councilors_voted {
-                if quorum_reached {
-                    Some(Approved)
-                } else {
-                    Some(Rejected)
-                }
-            } else if is_expired {
-                if quorum_reached {
-                    Some(Approved)
-                } else {
-                    // Proposal has been expired and quorum not reached.
-                    Some(Expired)
-                }
-            } else {
-                // Councilors still have time to vote on this proposal.
-                None
-            };
-
-            // TODO move next block outside of tally to 'end_block'
-            if let Some(status) = new_status {
-                Self::_update_proposal_status(proposal_id, status.clone())?;
-                let tally_result = TallyResult {
-                    proposal_id,
-                    abstentions,
-                    approvals,
-                    rejections,
-                    slashes,
-                    status,
-                    finalized_at: Self::current_block(),
-                };
-                <TallyResults<T>>::insert(proposal_id, &tally_result);
-                Self::deposit_event(RawEvent::TallyFinalized(tally_result));
-            }
-        }
-
-        Ok(())
-    }
-
-    /// Updates proposal status and removes proposal from active ids.
-    fn _update_proposal_status(proposal_id: u32, new_status: ProposalStatus) -> dispatch::Result {
-        let all_active_ids = Self::active_proposal_ids();
-        let all_len = all_active_ids.len();
-        let other_active_ids: Vec<u32> = all_active_ids
-            .into_iter()
-            .filter(|&id| id != proposal_id)
-            .collect();
-
-        let not_found_in_active = other_active_ids.len() == all_len;
-        if not_found_in_active {
-            // Seems like this proposal's status has been updated and removed from active.
-            Err(MSG_PROPOSAL_STATUS_ALREADY_UPDATED)
-        } else {
-            let pid = proposal_id.clone();
-            match new_status {
-                Slashed => Self::_slash_proposal(pid)?,
-                Rejected | Expired => Self::_reject_proposal(pid)?,
-                Approved => Self::_approve_proposal(pid)?,
-                Active | Cancelled => { /* nothing */ }
-            }
-            ActiveProposalIds::put(other_active_ids);
-            <Proposals<T>>::mutate(proposal_id, |p| p.status = new_status.clone());
-            Self::deposit_event(RawEvent::ProposalStatusUpdated(proposal_id, new_status));
-            Ok(())
-        }
-    }
-
-    /// Slash a proposal. The staked deposit will be slashed.
-    fn _slash_proposal(proposal_id: u32) -> dispatch::Result {
-        let proposal = Self::proposals(proposal_id);
-
-        // Slash proposer's stake:
-        let _ = T::Currency::slash_reserved(&proposal.proposer, proposal.stake);
-
-        Ok(())
-    }
-
-    /// Reject a proposal. The staked deposit will be returned to a proposer.
-    fn _reject_proposal(proposal_id: u32) -> dispatch::Result {
-        let proposal = Self::proposals(proposal_id);
-        let proposer = proposal.proposer;
-
-        // Spend some minimum fee on proposer's balance to prevent spamming attacks:
-        let fee = Self::rejection_fee();
-        let _ = T::Currency::slash_reserved(&proposer, fee);
-
-        // Return unspent part of remaining staked deposit (after taking some fee):
-        let left_stake = proposal.stake - fee;
-        let _ = T::Currency::unreserve(&proposer, left_stake);
-
-        Ok(())
-    }
-
-    /// Approve a proposal. The staked deposit will be returned.
-    fn _approve_proposal(proposal_id: u32) -> dispatch::Result {
-        let proposal = Self::proposals(proposal_id);
-        let wasm_code = Self::wasm_code_by_hash(proposal.wasm_hash);
-
-        // Return staked deposit to proposer:
-        let _ = T::Currency::unreserve(&proposal.proposer, proposal.stake);
-
-        // Update wasm code of node's runtime:
-        <system::Module<T>>::set_code(system::RawOrigin::Root.into(), wasm_code)?;
-
-        Self::deposit_event(RawEvent::RuntimeUpdated(proposal_id, proposal.wasm_hash));
-
-        Ok(())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-
-    use super::*;
-    use primitives::H256;
-    // The testing primitives are very useful for avoiding having to work with signatures
-    // or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried.
-    use sr_primitives::{
-        testing::Header,
-        traits::{BlakeTwo256, IdentityLookup},
-        Perbill,
-    };
-    use srml_support::*;
-
-    impl_outer_origin! {
-        pub enum Origin for Test {}
-    }
-
-    // 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 MinimumPeriod: u64 = 5;
-    }
-
-    impl system::Trait for Test {
-        type Origin = Origin;
-        type Index = u64;
-        type BlockNumber = u64;
-        type Call = ();
-        type Hash = H256;
-        type Hashing = BlakeTwo256;
-        type AccountId = u64;
-        type Lookup = IdentityLookup<Self::AccountId>;
-        type Header = Header;
-        type Event = ();
-        type BlockHashCount = BlockHashCount;
-        type MaximumBlockWeight = MaximumBlockWeight;
-        type MaximumBlockLength = MaximumBlockLength;
-        type AvailableBlockRatio = AvailableBlockRatio;
-        type Version = ();
-    }
-
-    impl timestamp::Trait for Test {
-        type Moment = u64;
-        type OnTimestampSet = ();
-        type MinimumPeriod = MinimumPeriod;
-    }
-
-    parameter_types! {
-        pub const ExistentialDeposit: u32 = 0;
-        pub const TransferFee: u32 = 0;
-        pub const CreationFee: u32 = 0;
-        pub const TransactionBaseFee: u32 = 1;
-        pub const TransactionByteFee: u32 = 0;
-        pub const InitialMembersBalance: u32 = 0;
-    }
-
-    impl balances::Trait for Test {
-        /// The type for recording an account's balance.
-        type Balance = u64;
-        /// What to do if an account's free balance gets zeroed.
-        type OnFreeBalanceZero = ();
-        /// What to do if a new account is created.
-        type OnNewAccount = ();
-        /// The ubiquitous event type.
-        type Event = ();
-
-        type DustRemoval = ();
-        type TransferPayment = ();
-        type ExistentialDeposit = ExistentialDeposit;
-        type TransferFee = TransferFee;
-        type CreationFee = CreationFee;
-    }
-
-    impl council::Trait for Test {
-        type Event = ();
-        type CouncilTermEnded = ();
-    }
-
-    impl GovernanceCurrency for Test {
-        type Currency = balances::Module<Self>;
-    }
-
-    impl membership::members::Trait for Test {
-        type Event = ();
-        type MemberId = u32;
-        type PaidTermId = u32;
-        type SubscriptionId = u32;
-        type ActorId = u32;
-        type InitialMembersBalance = InitialMembersBalance;
-    }
-
-    impl Trait for Test {
-        type Event = ();
-    }
-
-    type System = system::Module<Test>;
-    type Balances = balances::Module<Test>;
-    type Proposals = Module<Test>;
-
-    const COUNCILOR1: u64 = 1;
-    const COUNCILOR2: u64 = 2;
-    const COUNCILOR3: u64 = 3;
-    const COUNCILOR4: u64 = 4;
-    const COUNCILOR5: u64 = 5;
-
-    const PROPOSER1: u64 = 11;
-    const PROPOSER2: u64 = 12;
-
-    const NOT_COUNCILOR: u64 = 22;
-
-    const ALL_COUNCILORS: [u64; 5] = [COUNCILOR1, COUNCILOR2, COUNCILOR3, COUNCILOR4, COUNCILOR5];
-
-    // TODO Figure out how to test Events in test... (low priority)
-    // mod proposals {
-    //     pub use ::Event;
-    // }
-    // impl_outer_event!{
-    //     pub enum TestEvent for Test {
-    //         balances<T>,system<T>,proposals<T>,
-    //     }
-    // }
-
-    // This function basically just builds a genesis storage key/value store according to
-    // our desired mockup.
-    fn new_test_ext() -> runtime_io::TestExternalities {
-        let mut t = system::GenesisConfig::default()
-            .build_storage::<Test>()
-            .unwrap();
-
-        // balances doesn't contain GenesisConfig anymore
-        // // We use default for brevity, but you can configure as desired if needed.
-        // balances::GenesisConfig::<Test>::default()
-        //     .assimilate_storage(&mut t)
-        //     .unwrap();
-
-        let council_mock: council::Seats<u64, u64> = ALL_COUNCILORS
-            .iter()
-            .map(|&c| council::Seat {
-                member: c,
-                stake: 0u64,
-                backers: vec![],
-            })
-            .collect();
-
-        council::GenesisConfig::<Test> {
-            active_council: council_mock,
-            term_ends_at: 0,
-        }
-        .assimilate_storage(&mut t)
-        .unwrap();
-
-        membership::members::GenesisConfig::<Test> {
-            default_paid_membership_fee: 0,
-            members: vec![
-                (PROPOSER1, "alice".into(), "".into(), "".into()),
-                (PROPOSER2, "bobby".into(), "".into(), "".into()),
-                (COUNCILOR1, "councilor1".into(), "".into(), "".into()),
-                (COUNCILOR2, "councilor2".into(), "".into(), "".into()),
-                (COUNCILOR3, "councilor3".into(), "".into(), "".into()),
-                (COUNCILOR4, "councilor4".into(), "".into(), "".into()),
-                (COUNCILOR5, "councilor5".into(), "".into(), "".into()),
-            ],
-        }
-        .assimilate_storage(&mut t)
-        .unwrap();
-        // t.extend(GenesisConfig::<Test>{
-        //     // Here we can override defaults.
-        // }.build_storage().unwrap().0);
-
-        t.into()
-    }
-
-    /// A shortcut to get minimum stake in tests.
-    fn min_stake() -> u64 {
-        Proposals::min_stake()
-    }
-
-    /// A shortcut to get cancellation fee in tests.
-    fn cancellation_fee() -> u64 {
-        Proposals::cancellation_fee()
-    }
-
-    /// A shortcut to get rejection fee in tests.
-    fn rejection_fee() -> u64 {
-        Proposals::rejection_fee()
-    }
-
-    /// Initial balance of Proposer 1.
-    fn initial_balance() -> u64 {
-        (min_stake() as f64 * 2.5) as u64
-    }
-
-    fn name() -> Vec<u8> {
-        b"Proposal Name".to_vec()
-    }
-
-    fn description() -> Vec<u8> {
-        b"Proposal Description".to_vec()
-    }
-
-    fn wasm_code() -> Vec<u8> {
-        b"Proposal Wasm Code".to_vec()
-    }
-
-    fn _create_default_proposal() -> dispatch::Result {
-        _create_proposal(None, None, None, None, None)
-    }
-
-    fn _create_proposal(
-        origin: Option<u64>,
-        stake: Option<u64>,
-        name: Option<Vec<u8>>,
-        description: Option<Vec<u8>>,
-        wasm_code: Option<Vec<u8>>,
-    ) -> dispatch::Result {
-        Proposals::create_proposal(
-            Origin::signed(origin.unwrap_or(PROPOSER1)),
-            stake.unwrap_or(min_stake()),
-            name.unwrap_or(self::name()),
-            description.unwrap_or(self::description()),
-            wasm_code.unwrap_or(self::wasm_code()),
-        )
-    }
-
-    fn get_runtime_code() -> Option<Vec<u8>> {
-        storage::unhashed::get_raw(well_known_keys::CODE)
-    }
-
-    macro_rules! assert_runtime_code_empty {
-        () => {
-            assert_eq!(get_runtime_code(), Some(vec![]))
-        };
-    }
-
-    macro_rules! assert_runtime_code {
-        ($code:expr) => {
-            assert_eq!(get_runtime_code(), Some($code))
-        };
-    }
-
-    #[test]
-    fn check_default_values() {
-        new_test_ext().execute_with(|| {
-            assert_eq!(Proposals::approval_quorum(), DEFAULT_APPROVAL_QUORUM);
-            assert_eq!(
-                Proposals::min_stake(),
-                BalanceOf::<Test>::from(DEFAULT_MIN_STAKE)
-            );
-            assert_eq!(
-                Proposals::cancellation_fee(),
-                BalanceOf::<Test>::from(DEFAULT_CANCELLATION_FEE)
-            );
-            assert_eq!(
-                Proposals::rejection_fee(),
-                BalanceOf::<Test>::from(DEFAULT_REJECTION_FEE)
-            );
-            assert_eq!(Proposals::name_max_len(), DEFAULT_NAME_MAX_LEN);
-            assert_eq!(
-                Proposals::description_max_len(),
-                DEFAULT_DESCRIPTION_MAX_LEN
-            );
-            assert_eq!(Proposals::wasm_code_max_len(), DEFAULT_WASM_CODE_MAX_LEN);
-            assert_eq!(Proposals::proposal_count(), 0);
-            assert!(Proposals::active_proposal_ids().is_empty());
-        });
-    }
-
-    #[test]
-    fn member_create_proposal() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-            assert_eq!(Proposals::active_proposal_ids().len(), 1);
-            assert_eq!(Proposals::active_proposal_ids()[0], 1);
-
-            let wasm_hash = BlakeTwo256::hash(&wasm_code());
-            let expected_proposal = RuntimeUpgradeProposal {
-                id: 1,
-                proposer: PROPOSER1,
-                stake: min_stake(),
-                name: name(),
-                description: description(),
-                wasm_hash,
-                proposed_at: 1,
-                status: Active,
-            };
-            assert_eq!(Proposals::proposals(1), expected_proposal);
-
-            // Check that stake amount has been locked on proposer's balance:
-            assert_eq!(
-                Balances::free_balance(PROPOSER1),
-                initial_balance() - min_stake()
-            );
-            assert_eq!(Balances::reserved_balance(PROPOSER1), min_stake());
-
-            // TODO expect event ProposalCreated(AccountId, u32)
-        });
-    }
-
-    #[test]
-    fn not_member_cannot_create_proposal() {
-        new_test_ext().execute_with(|| {
-            // In this test a proposer has an empty balance
-            // thus he is not considered as a member.
-            assert_eq!(
-                _create_default_proposal(),
-                Err(MSG_ONLY_MEMBERS_CAN_PROPOSE)
-            );
-        });
-    }
-
-    #[test]
-    fn cannot_create_proposal_with_small_stake() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_eq!(
-                _create_proposal(None, Some(min_stake() - 1), None, None, None),
-                Err(MSG_STAKE_IS_TOO_LOW)
-            );
-
-            // Check that balances remain unchanged afer a failed attempt to create a proposal:
-            assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-        });
-    }
-
-    #[test]
-    fn cannot_create_proposal_when_stake_is_greater_than_balance() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_eq!(
-                _create_proposal(None, Some(initial_balance() + 1), None, None, None),
-                Err(MSG_STAKE_IS_GREATER_THAN_BALANCE)
-            );
-
-            // Check that balances remain unchanged afer a failed attempt to create a proposal:
-            assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-        });
-    }
-
-    #[test]
-    fn cannot_create_proposal_with_empty_values() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            // Empty name:
-            assert_eq!(
-                _create_proposal(None, None, Some(vec![]), None, None),
-                Err(MSG_EMPTY_NAME_PROVIDED)
-            );
-
-            // Empty description:
-            assert_eq!(
-                _create_proposal(None, None, None, Some(vec![]), None),
-                Err(MSG_EMPTY_DESCRIPTION_PROVIDED)
-            );
-
-            // Empty WASM code:
-            assert_eq!(
-                _create_proposal(None, None, None, None, Some(vec![])),
-                Err(MSG_EMPTY_WASM_CODE_PROVIDED)
-            );
-        });
-    }
-
-    #[test]
-    fn cannot_create_proposal_with_too_long_values() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            // Too long name:
-            assert_eq!(
-                _create_proposal(None, None, Some(too_long_name()), None, None),
-                Err(MSG_TOO_LONG_NAME)
-            );
-
-            // Too long description:
-            assert_eq!(
-                _create_proposal(None, None, None, Some(too_long_description()), None),
-                Err(MSG_TOO_LONG_DESCRIPTION)
-            );
-
-            // Too long WASM code:
-            assert_eq!(
-                _create_proposal(None, None, None, None, Some(too_long_wasm_code())),
-                Err(MSG_TOO_LONG_WASM_CODE)
-            );
-        });
-    }
-
-    fn too_long_name() -> Vec<u8> {
-        vec![65; Proposals::name_max_len() as usize + 1]
-    }
-
-    fn too_long_description() -> Vec<u8> {
-        vec![65; Proposals::description_max_len() as usize + 1]
-    }
-
-    fn too_long_wasm_code() -> Vec<u8> {
-        vec![65; Proposals::wasm_code_max_len() as usize + 1]
-    }
-
-    // -------------------------------------------------------------------
-    // Cancellation
-
-    #[test]
-    fn owner_cancel_proposal() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-            assert_ok!(Proposals::cancel_proposal(Origin::signed(PROPOSER1), 1));
-            assert_eq!(Proposals::proposals(1).status, Cancelled);
-            assert!(Proposals::active_proposal_ids().is_empty());
-
-            // Check that proposer's balance reduced by cancellation fee and other part of his stake returned to his balance:
-            assert_eq!(
-                Balances::free_balance(PROPOSER1),
-                initial_balance() - cancellation_fee()
-            );
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-
-            // TODO expect event ProposalCancelled(AccountId, u32)
-        });
-    }
-
-    #[test]
-    fn owner_cannot_cancel_proposal_if_its_finalized() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-            assert_ok!(Proposals::cancel_proposal(Origin::signed(PROPOSER1), 1));
-            assert_eq!(Proposals::proposals(1).status, Cancelled);
-
-            // Get balances updated after cancelling a proposal:
-            let updated_free_balance = Balances::free_balance(PROPOSER1);
-            let updated_reserved_balance = Balances::reserved_balance(PROPOSER1);
-
-            assert_eq!(
-                Proposals::cancel_proposal(Origin::signed(PROPOSER1), 1),
-                Err(MSG_PROPOSAL_FINALIZED)
-            );
-
-            // Check that proposer's balance and locked stake haven't been changed:
-            assert_eq!(Balances::free_balance(PROPOSER1), updated_free_balance);
-            assert_eq!(
-                Balances::reserved_balance(PROPOSER1),
-                updated_reserved_balance
-            );
-        });
-    }
-
-    #[test]
-    fn not_owner_cannot_cancel_proposal() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-            let _ = Balances::deposit_creating(&PROPOSER2, initial_balance());
-            assert_ok!(_create_default_proposal());
-            assert_eq!(
-                Proposals::cancel_proposal(Origin::signed(PROPOSER2), 1),
-                Err(MSG_YOU_DONT_OWN_THIS_PROPOSAL)
-            );
-        });
-    }
-
-    // -------------------------------------------------------------------
-    // Voting
-
-    #[test]
-    fn councilor_vote_on_proposal() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            assert_ok!(Proposals::vote_on_proposal(
-                Origin::signed(COUNCILOR1),
-                1,
-                Approve
-            ));
-
-            // Check that a vote has been saved:
-            assert_eq!(Proposals::votes_by_proposal(1), vec![(COUNCILOR1, Approve)]);
-            assert_eq!(
-                Proposals::vote_by_account_and_proposal((COUNCILOR1, 1)),
-                Approve
-            );
-
-            // TODO expect event Voted(PROPOSER1, 1, Approve)
-        });
-    }
-
-    #[test]
-    fn councilor_cannot_vote_on_proposal_twice() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            assert_ok!(Proposals::vote_on_proposal(
-                Origin::signed(COUNCILOR1),
-                1,
-                Approve
-            ));
-            assert_eq!(
-                Proposals::vote_on_proposal(Origin::signed(COUNCILOR1), 1, Approve),
-                Err(MSG_YOU_ALREADY_VOTED)
-            );
-        });
-    }
-
-    #[test]
-    fn autovote_with_approve_when_councilor_creates_proposal() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&COUNCILOR1, initial_balance());
-
-            assert_ok!(_create_proposal(Some(COUNCILOR1), None, None, None, None));
-
-            // Check that a vote has been sent automatically,
-            // such as the proposer is a councilor:
-            assert_eq!(Proposals::votes_by_proposal(1), vec![(COUNCILOR1, Approve)]);
-            assert_eq!(
-                Proposals::vote_by_account_and_proposal((COUNCILOR1, 1)),
-                Approve
-            );
-        });
-    }
-
-    #[test]
-    fn not_councilor_cannot_vote_on_proposal() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-            assert_eq!(
-                Proposals::vote_on_proposal(Origin::signed(NOT_COUNCILOR), 1, Approve),
-                Err(MSG_ONLY_COUNCILORS_CAN_VOTE)
-            );
-        });
-    }
-
-    #[test]
-    fn councilor_cannot_vote_on_proposal_if_it_has_been_cancelled() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-            assert_ok!(Proposals::cancel_proposal(Origin::signed(PROPOSER1), 1));
-            assert_eq!(
-                Proposals::vote_on_proposal(Origin::signed(COUNCILOR1), 1, Approve),
-                Err(MSG_PROPOSAL_FINALIZED)
-            );
-        });
-    }
-
-    #[test]
-    fn councilor_cannot_vote_on_proposal_if_tally_has_been_finalized() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            // All councilors vote with 'Approve' on proposal:
-            let mut expected_votes: Vec<(u64, VoteKind)> = vec![];
-            for &councilor in ALL_COUNCILORS.iter() {
-                expected_votes.push((councilor, Approve));
-                assert_ok!(Proposals::vote_on_proposal(
-                    Origin::signed(councilor),
-                    1,
-                    Approve
-                ));
-                assert_eq!(
-                    Proposals::vote_by_account_and_proposal((councilor, 1)),
-                    Approve
-                );
-            }
-            assert_eq!(Proposals::votes_by_proposal(1), expected_votes);
-
-            System::set_block_number(2);
-            let _ = Proposals::end_block(2);
-
-            assert!(Proposals::active_proposal_ids().is_empty());
-            assert_eq!(Proposals::proposals(1).status, Approved);
-
-            // Try to vote on finalized proposal:
-            assert_eq!(
-                Proposals::vote_on_proposal(Origin::signed(COUNCILOR1), 1, Reject),
-                Err(MSG_PROPOSAL_FINALIZED)
-            );
-        });
-    }
-
-    // -------------------------------------------------------------------
-    // Tally + Outcome:
-
-    #[test]
-    fn approve_proposal_when_all_councilors_approved_it() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            // All councilors approved:
-            let mut expected_votes: Vec<(u64, VoteKind)> = vec![];
-            for &councilor in ALL_COUNCILORS.iter() {
-                expected_votes.push((councilor, Approve));
-                assert_ok!(Proposals::vote_on_proposal(
-                    Origin::signed(councilor),
-                    1,
-                    Approve
-                ));
-                assert_eq!(
-                    Proposals::vote_by_account_and_proposal((councilor, 1)),
-                    Approve
-                );
-            }
-            assert_eq!(Proposals::votes_by_proposal(1), expected_votes);
-
-            assert_runtime_code_empty!();
-
-            System::set_block_number(2);
-            let _ = Proposals::end_block(2);
-
-            // Check that runtime code has been updated after proposal approved.
-            assert_runtime_code!(wasm_code());
-
-            assert!(Proposals::active_proposal_ids().is_empty());
-            assert_eq!(Proposals::proposals(1).status, Approved);
-            assert_eq!(
-                Proposals::tally_results(1),
-                TallyResult {
-                    proposal_id: 1,
-                    abstentions: 0,
-                    approvals: ALL_COUNCILORS.len() as u32,
-                    rejections: 0,
-                    slashes: 0,
-                    status: Approved,
-                    finalized_at: 2
-                }
-            );
-
-            // Check that proposer's stake has been added back to his balance:
-            assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-
-            // TODO expect event ProposalStatusUpdated(1, Approved)
-        });
-    }
-
-    #[test]
-    fn approve_proposal_when_all_councilors_voted_and_only_quorum_approved() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            // Only a quorum of councilors approved, others rejected:
-            let councilors = Proposals::councilors_count();
-            let approvals = Proposals::approval_quorum_seats();
-            let rejections = councilors - approvals;
-            for i in 0..councilors as usize {
-                let vote = if (i as u32) < approvals {
-                    Approve
-                } else {
-                    Reject
-                };
-                assert_ok!(Proposals::vote_on_proposal(
-                    Origin::signed(ALL_COUNCILORS[i]),
-                    1,
-                    vote
-                ));
-            }
-            assert_eq!(Proposals::votes_by_proposal(1).len() as u32, councilors);
-
-            assert_runtime_code_empty!();
-
-            System::set_block_number(2);
-            let _ = Proposals::end_block(2);
-
-            // Check that runtime code has been updated after proposal approved.
-            assert_runtime_code!(wasm_code());
-
-            assert!(Proposals::active_proposal_ids().is_empty());
-            assert_eq!(Proposals::proposals(1).status, Approved);
-            assert_eq!(
-                Proposals::tally_results(1),
-                TallyResult {
-                    proposal_id: 1,
-                    abstentions: 0,
-                    approvals: approvals,
-                    rejections: rejections,
-                    slashes: 0,
-                    status: Approved,
-                    finalized_at: 2
-                }
-            );
-
-            // Check that proposer's stake has been added back to his balance:
-            assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-
-            // TODO expect event ProposalStatusUpdated(1, Approved)
-        });
-    }
-
-    #[test]
-    fn approve_proposal_when_voting_period_expired_if_only_quorum_voted() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            // Only quorum of councilors approved, other councilors didn't vote:
-            let approvals = Proposals::approval_quorum_seats();
-            for i in 0..approvals as usize {
-                let vote = if (i as u32) < approvals {
-                    Approve
-                } else {
-                    Slash
-                };
-                assert_ok!(Proposals::vote_on_proposal(
-                    Origin::signed(ALL_COUNCILORS[i]),
-                    1,
-                    vote
-                ));
-            }
-            assert_eq!(Proposals::votes_by_proposal(1).len() as u32, approvals);
-
-            assert_runtime_code_empty!();
-
-            let expiration_block = System::block_number() + Proposals::voting_period();
-            System::set_block_number(2);
-            let _ = Proposals::end_block(2);
-
-            // Check that runtime code has NOT been updated yet,
-            // because not all councilors voted and voting period is not expired yet.
-            assert_runtime_code_empty!();
-
-            System::set_block_number(expiration_block);
-            let _ = Proposals::end_block(expiration_block);
-
-            // Check that runtime code has been updated after proposal approved.
-            assert_runtime_code!(wasm_code());
-
-            assert!(Proposals::active_proposal_ids().is_empty());
-            assert_eq!(Proposals::proposals(1).status, Approved);
-            assert_eq!(
-                Proposals::tally_results(1),
-                TallyResult {
-                    proposal_id: 1,
-                    abstentions: 0,
-                    approvals: approvals,
-                    rejections: 0,
-                    slashes: 0,
-                    status: Approved,
-                    finalized_at: expiration_block
-                }
-            );
-
-            // Check that proposer's stake has been added back to his balance:
-            assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-
-            // TODO expect event ProposalStatusUpdated(1, Approved)
-        });
-    }
-
-    #[test]
-    fn reject_proposal_when_all_councilors_voted_and_quorum_not_reached() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            // Less than a quorum of councilors approved, while others abstained:
-            let councilors = Proposals::councilors_count();
-            let approvals = Proposals::approval_quorum_seats() - 1;
-            let abstentions = councilors - approvals;
-            for i in 0..councilors as usize {
-                let vote = if (i as u32) < approvals {
-                    Approve
-                } else {
-                    Abstain
-                };
-                assert_ok!(Proposals::vote_on_proposal(
-                    Origin::signed(ALL_COUNCILORS[i]),
-                    1,
-                    vote
-                ));
-            }
-            assert_eq!(Proposals::votes_by_proposal(1).len() as u32, councilors);
-
-            assert_runtime_code_empty!();
-
-            System::set_block_number(2);
-            let _ = Proposals::end_block(2);
-
-            // Check that runtime code has NOT been updated after proposal slashed.
-            assert_runtime_code_empty!();
-
-            assert!(Proposals::active_proposal_ids().is_empty());
-            assert_eq!(Proposals::proposals(1).status, Rejected);
-            assert_eq!(
-                Proposals::tally_results(1),
-                TallyResult {
-                    proposal_id: 1,
-                    abstentions: abstentions,
-                    approvals: approvals,
-                    rejections: 0,
-                    slashes: 0,
-                    status: Rejected,
-                    finalized_at: 2
-                }
-            );
-
-            // Check that proposer's balance reduced by burnt stake:
-            assert_eq!(
-                Balances::free_balance(PROPOSER1),
-                initial_balance() - rejection_fee()
-            );
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-
-            // TODO expect event ProposalStatusUpdated(1, Rejected)
-        });
-    }
-
-    #[test]
-    fn reject_proposal_when_all_councilors_rejected_it() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            // All councilors rejected:
-            let mut expected_votes: Vec<(u64, VoteKind)> = vec![];
-            for &councilor in ALL_COUNCILORS.iter() {
-                expected_votes.push((councilor, Reject));
-                assert_ok!(Proposals::vote_on_proposal(
-                    Origin::signed(councilor),
-                    1,
-                    Reject
-                ));
-                assert_eq!(
-                    Proposals::vote_by_account_and_proposal((councilor, 1)),
-                    Reject
-                );
-            }
-            assert_eq!(Proposals::votes_by_proposal(1), expected_votes);
-
-            assert_runtime_code_empty!();
-
-            System::set_block_number(2);
-            let _ = Proposals::end_block(2);
-
-            // Check that runtime code has NOT been updated after proposal rejected.
-            assert_runtime_code_empty!();
-
-            assert!(Proposals::active_proposal_ids().is_empty());
-            assert_eq!(Proposals::proposals(1).status, Rejected);
-            assert_eq!(
-                Proposals::tally_results(1),
-                TallyResult {
-                    proposal_id: 1,
-                    abstentions: 0,
-                    approvals: 0,
-                    rejections: ALL_COUNCILORS.len() as u32,
-                    slashes: 0,
-                    status: Rejected,
-                    finalized_at: 2
-                }
-            );
-
-            // Check that proposer's balance reduced by burnt stake:
-            assert_eq!(
-                Balances::free_balance(PROPOSER1),
-                initial_balance() - rejection_fee()
-            );
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-
-            // TODO expect event ProposalStatusUpdated(1, Rejected)
-        });
-    }
-
-    #[test]
-    fn slash_proposal_when_all_councilors_slashed_it() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            // All councilors slashed:
-            let mut expected_votes: Vec<(u64, VoteKind)> = vec![];
-            for &councilor in ALL_COUNCILORS.iter() {
-                expected_votes.push((councilor, Slash));
-                assert_ok!(Proposals::vote_on_proposal(
-                    Origin::signed(councilor),
-                    1,
-                    Slash
-                ));
-                assert_eq!(
-                    Proposals::vote_by_account_and_proposal((councilor, 1)),
-                    Slash
-                );
-            }
-            assert_eq!(Proposals::votes_by_proposal(1), expected_votes);
-
-            assert_runtime_code_empty!();
-
-            System::set_block_number(2);
-            let _ = Proposals::end_block(2);
-
-            // Check that runtime code has NOT been updated after proposal slashed.
-            assert_runtime_code_empty!();
-
-            assert!(Proposals::active_proposal_ids().is_empty());
-            assert_eq!(Proposals::proposals(1).status, Slashed);
-            assert_eq!(
-                Proposals::tally_results(1),
-                TallyResult {
-                    proposal_id: 1,
-                    abstentions: 0,
-                    approvals: 0,
-                    rejections: 0,
-                    slashes: ALL_COUNCILORS.len() as u32,
-                    status: Slashed,
-                    finalized_at: 2
-                }
-            );
-
-            // Check that proposer's balance reduced by burnt stake:
-            assert_eq!(
-                Balances::free_balance(PROPOSER1),
-                initial_balance() - min_stake()
-            );
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-
-            // TODO expect event ProposalStatusUpdated(1, Slashed)
-            // TODO fix: event log assertion doesn't work and return empty event in every record
-            // assert_eq!(*System::events().last().unwrap(),
-            //     EventRecord {
-            //         phase: Phase::ApplyExtrinsic(0),
-            //         event: RawEvent::ProposalStatusUpdated(1, Slashed),
-            //     }
-            // );
-        });
-    }
-
-    // In this case a proposal will be marked as 'Expired'
-    // and it will be processed in the same way as if it has been rejected.
-    #[test]
-    fn expire_proposal_when_not_all_councilors_voted_and_quorum_not_reached() {
-        new_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
-
-            assert_ok!(_create_default_proposal());
-
-            // Less than a quorum of councilors approved:
-            let approvals = Proposals::approval_quorum_seats() - 1;
-            for i in 0..approvals as usize {
-                let vote = if (i as u32) < approvals {
-                    Approve
-                } else {
-                    Slash
-                };
-                assert_ok!(Proposals::vote_on_proposal(
-                    Origin::signed(ALL_COUNCILORS[i]),
-                    1,
-                    vote
-                ));
-            }
-            assert_eq!(Proposals::votes_by_proposal(1).len() as u32, approvals);
-
-            assert_runtime_code_empty!();
-
-            let expiration_block = System::block_number() + Proposals::voting_period();
-            System::set_block_number(expiration_block);
-            let _ = Proposals::end_block(expiration_block);
-
-            // Check that runtime code has NOT been updated after proposal slashed.
-            assert_runtime_code_empty!();
-
-            assert!(Proposals::active_proposal_ids().is_empty());
-            assert_eq!(Proposals::proposals(1).status, Expired);
-            assert_eq!(
-                Proposals::tally_results(1),
-                TallyResult {
-                    proposal_id: 1,
-                    abstentions: 0,
-                    approvals: approvals,
-                    rejections: 0,
-                    slashes: 0,
-                    status: Expired,
-                    finalized_at: expiration_block
-                }
-            );
-
-            // Check that proposer's balance reduced by burnt stake:
-            assert_eq!(
-                Balances::free_balance(PROPOSER1),
-                initial_balance() - rejection_fee()
-            );
-            assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
-
-            // TODO expect event ProposalStatusUpdated(1, Rejected)
-        });
-    }
-}

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

@@ -5,5 +5,5 @@ pub mod genesis;
 pub mod members;
 pub mod role_types;
 
-mod mock;
+pub(crate) mod mock;
 mod tests;

+ 24 - 10
modules/proposals/codex/Cargo.toml → runtime-modules/proposals/codex/Cargo.toml

@@ -20,6 +20,7 @@ std = [
     'proposal_discussion/std',
     'stake/std',
     'balances/std',
+    'membership/std',
 ]
 
 
@@ -42,50 +43,53 @@ version = '1.0.0'
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'substrate-primitives'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.rstd]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'sr-std'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.runtime-primitives]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'sr-primitives'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.srml-support]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-support'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.system]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-system'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.timestamp]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-timestamp'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.balances]
 package = 'srml-balances'
 default-features = false
 git = 'https://github.com/paritytech/substrate.git'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.stake]
 default_features = false
-git = 'https://github.com/joystream/substrate-stake-module'
 package = 'substrate-stake-module'
-rev = '0516efe9230da112bc095e28f34a3715c2e03ca8'
+path = '../../stake'
 
+[dependencies.membership]
+default_features = false
+package = 'substrate-membership-module'
+path = '../../membership'
 
 [dependencies.proposal_engine]
 default_features = false
@@ -97,8 +101,18 @@ default_features = false
 package = 'substrate-proposals-discussion-module'
 path = '../discussion'
 
+[dependencies.common]
+default_features = false
+package = 'substrate-common-module'
+path = '../../common'
+
 [dev-dependencies.runtime-io]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'sr-io'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
+
+[dev-dependencies.governance]
+default_features = false
+package = 'substrate-governance-module'
+path = '../../governance'

+ 129 - 30
modules/proposals/codex/src/lib.rs → runtime-modules/proposals/codex/src/lib.rs

@@ -11,28 +11,37 @@
 // Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
 //#![warn(missing_docs)]
 
-pub use proposal_types::{ProposalType, RuntimeUpgradeProposalExecutable, TextProposalExecutable};
-
 mod proposal_types;
 #[cfg(test)]
 mod tests;
 
 use codec::Encode;
-use proposal_engine::*;
 use rstd::clone::Clone;
-use rstd::marker::PhantomData;
 use rstd::prelude::*;
+use rstd::str::from_utf8;
 use rstd::vec::Vec;
-use srml_support::{decl_error, decl_module, decl_storage, ensure};
-use system::RawOrigin;
+use srml_support::{decl_error, decl_module, decl_storage, ensure, print};
+use system::{ensure_root, RawOrigin};
+
+use common::origin_validator::ActorOriginValidator;
+use proposal_engine::ProposalParameters;
 
 /// 'Proposals codex' substrate module Trait
-pub trait Trait: system::Trait + proposal_engine::Trait + proposal_discussion::Trait {
+pub trait Trait:
+    system::Trait + proposal_engine::Trait + membership::members::Trait + proposal_discussion::Trait
+{
     /// Defines max allowed text proposal length.
     type TextProposalMaxLength: Get<u32>;
 
     /// Defines max wasm code length of the runtime upgrade proposal.
     type RuntimeUpgradeWasmProposalMaxLength: Get<u32>;
+
+    /// Validates member id and origin combination
+    type MembershipOriginValidator: ActorOriginValidator<
+        Self::Origin,
+        MemberId<Self>,
+        Self::AccountId,
+    >;
 }
 use srml_support::traits::{Currency, Get};
 
@@ -44,6 +53,8 @@ pub type BalanceOf<T> =
 pub type NegativeImbalance<T> =
     <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
 
+type MemberId<T> = <T as membership::members::Trait>::MemberId;
+
 decl_error! {
     pub enum Error {
         /// The size of the provided text for text proposal exceeded the limit
@@ -57,6 +68,42 @@ decl_error! {
 
         /// Provided WASM code for the runtime upgrade proposal is empty
         RuntimeProposalIsEmpty,
+
+        /// Require root origin in extrinsics
+        RequireRootOrigin,
+
+        /// Errors from the proposal engine
+        ProposalsEngineError
+    }
+}
+
+impl From<system::Error> for Error {
+    fn from(error: system::Error) -> Self {
+        match error {
+            system::Error::Other(msg) => Error::Other(msg),
+            system::Error::RequireRootOrigin => Error::RequireRootOrigin,
+            _ => Error::Other(error.into()),
+        }
+    }
+}
+
+impl From<proposal_engine::Error> for Error {
+    fn from(error: proposal_engine::Error) -> Self {
+        match error {
+            proposal_engine::Error::Other(msg) => Error::Other(msg),
+            proposal_engine::Error::RequireRootOrigin => Error::RequireRootOrigin,
+            _ => Error::Other(error.into()),
+        }
+    }
+}
+
+impl From<proposal_discussion::Error> for Error {
+    fn from(error: proposal_discussion::Error) -> Self {
+        match error {
+            proposal_discussion::Error::Other(msg) => Error::Other(msg),
+            proposal_discussion::Error::RequireRootOrigin => Error::RequireRootOrigin,
+            _ => Error::Other(error.into()),
+        }
     }
 }
 
@@ -78,39 +125,47 @@ decl_module! {
         /// Create text (signal) proposal type. On approval prints its content.
         pub fn create_text_proposal(
             origin,
+            member_id: MemberId<T>,
             title: Vec<u8>,
             description: Vec<u8>,
             text: Vec<u8>,
             stake_balance: Option<BalanceOf<T>>,
         ) {
+            let account_id = T::MembershipOriginValidator::ensure_actor_origin(origin, member_id.clone())?;
+
             let parameters = proposal_types::parameters::text_proposal::<T>();
 
             ensure!(!text.is_empty(), Error::TextProposalIsEmpty);
             ensure!(text.len() as u32 <=  T::TextProposalMaxLength::get(),
                 Error::TextProposalSizeExceeded);
 
-            let text_proposal = TextProposalExecutable{
-                title: title.clone(),
-                description: description.clone(),
-                text,
-               };
-            let proposal_code = text_proposal.encode();
+            <proposal_engine::Module<T>>::ensure_create_proposal_parameters_are_valid(
+                &parameters,
+                &title,
+                &description,
+                stake_balance,
+            )?;
 
-            let (cloned_origin1, cloned_origin2) =  Self::double_origin(origin);
+            <proposal_discussion::Module<T>>::ensure_can_create_thread(
+                &title,
+                member_id.clone(),
+            )?;
+
+            let proposal_code = <Call<T>>::text_proposal(title.clone(), description.clone(), text);
 
             let discussion_thread_id = <proposal_discussion::Module<T>>::create_thread(
-                cloned_origin1,
+                member_id,
                 title.clone(),
             )?;
 
             let proposal_id = <proposal_engine::Module<T>>::create_proposal(
-                cloned_origin2,
+                account_id,
+                member_id,
                 parameters,
                 title,
                 description,
                 stake_balance,
-                text_proposal.proposal_type(),
-                proposal_code,
+                proposal_code.encode(),
             )?;
 
              <ThreadIdByProposalId<T>>::insert(proposal_id, discussion_thread_id);
@@ -119,44 +174,88 @@ decl_module! {
         /// Create runtime upgrade proposal type. On approval prints its content.
         pub fn create_runtime_upgrade_proposal(
             origin,
+            member_id: MemberId<T>,
             title: Vec<u8>,
             description: Vec<u8>,
             wasm: Vec<u8>,
             stake_balance: Option<BalanceOf<T>>,
         ) {
+            let account_id = T::MembershipOriginValidator::ensure_actor_origin(origin, member_id.clone())?;
+
             let parameters = proposal_types::parameters::upgrade_runtime::<T>();
 
             ensure!(!wasm.is_empty(), Error::RuntimeProposalIsEmpty);
             ensure!(wasm.len() as u32 <= T::RuntimeUpgradeWasmProposalMaxLength::get(),
                 Error::RuntimeProposalSizeExceeded);
 
-            let proposal = RuntimeUpgradeProposalExecutable{
-                title: title.clone(),
-                description: description.clone(),
-                wasm,
-                marker : PhantomData::<T>
-               };
-            let proposal_code = proposal.encode();
+            <proposal_engine::Module<T>>::ensure_create_proposal_parameters_are_valid(
+                &parameters,
+                &title,
+                &description,
+                stake_balance,
+            )?;
 
-            let (cloned_origin1, cloned_origin2) =  Self::double_origin(origin);
+            <proposal_discussion::Module<T>>::ensure_can_create_thread(
+                &title,
+                member_id.clone(),
+            )?;
+
+            let proposal_code = <Call<T>>::text_proposal(title.clone(), description.clone(), wasm);
 
             let discussion_thread_id = <proposal_discussion::Module<T>>::create_thread(
-                cloned_origin1,
+                member_id,
                 title.clone(),
             )?;
 
             let proposal_id = <proposal_engine::Module<T>>::create_proposal(
-                cloned_origin2,
+                account_id,
+                member_id,
                 parameters,
                 title,
                 description,
                 stake_balance,
-                proposal.proposal_type(),
-                proposal_code,
+                proposal_code.encode(),
             )?;
 
             <ThreadIdByProposalId<T>>::insert(proposal_id, discussion_thread_id);
         }
+
+// *************** Extrinsic to execute
+
+        /// Text proposal extrinsic. Should be used as callable object to pass to the engine module.
+        fn text_proposal(
+            origin,
+            title: Vec<u8>,
+            _description: Vec<u8>,
+            _text: Vec<u8>,
+        ) {
+            ensure_root(origin)?;
+            print("Text proposal: ");
+            let title_string_result = from_utf8(title.as_slice());
+            if let Ok(title_string) = title_string_result{
+                print(title_string);
+            }
+        }
+
+        /// Runtime upgrade proposal extrinsic.
+        /// Should be used as callable object to pass to the engine module.
+        fn runtime_upgrade_proposal(
+            origin,
+            title: Vec<u8>,
+            _description: Vec<u8>,
+            wasm: Vec<u8>,
+        ) {
+            let (cloned_origin1, cloned_origin2) =  Self::double_origin(origin);
+            ensure_root(cloned_origin1)?;
+
+            print("Runtime upgrade proposal: ");
+            let title_string_result = from_utf8(title.as_slice());
+            if let Ok(title_string) = title_string_result{
+                print(title_string);
+            }
+
+            <system::Module<T>>::set_code(cloned_origin2, wasm)?;
+        }
     }
 }
 

+ 31 - 0
runtime-modules/proposals/codex/src/proposal_types/mod.rs

@@ -0,0 +1,31 @@
+pub(crate) mod parameters {
+    use crate::{BalanceOf, ProposalParameters};
+
+    // Proposal parameters for the upgrade runtime proposal
+    pub(crate) fn upgrade_runtime<T: crate::Trait>(
+    ) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+        ProposalParameters {
+            voting_period: T::BlockNumber::from(50000u32),
+            grace_period: T::BlockNumber::from(10000u32),
+            approval_quorum_percentage: 80,
+            approval_threshold_percentage: 80,
+            slashing_quorum_percentage: 80,
+            slashing_threshold_percentage: 80,
+            required_stake: Some(<BalanceOf<T>>::from(50000u32)),
+        }
+    }
+
+    // Proposal parameters for the text proposal
+    pub(crate) fn text_proposal<T: crate::Trait>(
+    ) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+        ProposalParameters {
+            voting_period: T::BlockNumber::from(50000u32),
+            grace_period: T::BlockNumber::from(10000u32),
+            approval_quorum_percentage: 40,
+            approval_threshold_percentage: 51,
+            slashing_quorum_percentage: 80,
+            slashing_threshold_percentage: 80,
+            required_stake: Some(<BalanceOf<T>>::from(500u32)),
+        }
+    }
+}

+ 39 - 25
modules/proposals/codex/src/tests/mock.rs → runtime-modules/proposals/codex/src/tests/mock.rs

@@ -7,7 +7,7 @@ pub use runtime_primitives::{
     testing::{Digest, DigestItem, Header, UintAuthorityId},
     traits::{BlakeTwo256, Convert, IdentityLookup, OnFinalize},
     weights::Weight,
-    BuildStorage, Perbill,
+    BuildStorage, DispatchError, Perbill,
 };
 
 use proposal_engine::VotersParameters;
@@ -36,6 +36,19 @@ impl_outer_dispatch! {
     }
 }
 
+impl common::currency::GovernanceCurrency for Test {
+    type Currency = balances::Module<Self>;
+}
+
+impl membership::members::Trait for Test {
+    type Event = ();
+    type MemberId = u64;
+    type PaidTermId = u64;
+    type SubscriptionId = u64;
+    type ActorId = u64;
+    type InitialMembersBalance = ();
+}
+
 parameter_types! {
     pub const ExistentialDeposit: u32 = 0;
     pub const TransferFee: u32 = 0;
@@ -77,50 +90,52 @@ parameter_types! {
 
 impl proposal_engine::Trait for Test {
     type Event = ();
-
-    type ProposalOrigin = system::EnsureSigned<Self::AccountId>;
-
-    type VoteOrigin = system::EnsureSigned<Self::AccountId>;
-
+    type ProposerOriginValidator = ();
+    type VoterOriginValidator = ();
     type TotalVotersCounter = MockVotersParameters;
-
-    type ProposalCodeDecoder = crate::ProposalType;
-
     type ProposalId = u32;
-
-    type ProposerId = u64;
-
-    type VoterId = u64;
-
     type StakeHandlerProvider = proposal_engine::DefaultStakeHandlerProvider;
-
     type CancellationFee = CancellationFee;
-
     type RejectionFee = RejectionFee;
-
     type TitleMaxLength = TitleMaxLength;
-
     type DescriptionMaxLength = DescriptionMaxLength;
-
     type MaxActiveProposalLimit = MaxActiveProposalLimit;
+    type DispatchableCallCode = crate::Call<Test>;
+}
+
+impl Default for crate::Call<Test> {
+    fn default() -> Self {
+        panic!("shouldn't call default for Call");
+    }
+}
+
+impl governance::council::Trait for Test {
+    type Event = ();
+    type CouncilTermEnded = ();
+}
+
+impl common::origin_validator::ActorOriginValidator<Origin, u64, u64> for () {
+    fn ensure_actor_origin(_: Origin, _: u64) -> Result<u64, &'static str> {
+        Ok(1)
+    }
 }
 
 parameter_types! {
     pub const MaxPostEditionNumber: u32 = 5;
+    pub const MaxThreadInARowNumber: u32 = 3;
     pub const ThreadTitleLengthLimit: u32 = 200;
     pub const PostLengthLimit: u32 = 2000;
 }
 
 impl proposal_discussion::Trait for Test {
-    type ThreadAuthorOrigin = system::EnsureSigned<Self::AccountId>;
-    type PostAuthorOrigin = system::EnsureSigned<Self::AccountId>;
+    type Event = ();
+    type PostAuthorOriginValidator = ();
     type ThreadId = u32;
     type PostId = u32;
-    type ThreadAuthorId = u64;
-    type PostAuthorId = u64;
     type MaxPostEditionNumber = MaxPostEditionNumber;
     type ThreadTitleLengthLimit = ThreadTitleLengthLimit;
     type PostLengthLimit = PostLengthLimit;
+    type MaxThreadInARowNumber = MaxThreadInARowNumber;
 }
 
 pub struct MockVotersParameters;
@@ -138,6 +153,7 @@ parameter_types! {
 impl crate::Trait for Test {
     type TextProposalMaxLength = TextProposalMaxLength;
     type RuntimeUpgradeWasmProposalMaxLength = RuntimeUpgradeWasmProposalMaxLength;
+    type MembershipOriginValidator = ();
 }
 
 impl system::Trait for Test {
@@ -164,8 +180,6 @@ impl timestamp::Trait for Test {
     type MinimumPeriod = MinimumPeriod;
 }
 
-// TODO add a Hook type to capture TriggerElection and CouncilElected hooks
-
 pub fn initial_test_ext() -> runtime_io::TestExternalities {
     let t = system::GenesisConfig::default()
         .build_storage::<Test>()

+ 19 - 4
modules/proposals/codex/src/tests/mod.rs → runtime-modules/proposals/codex/src/tests/mod.rs

@@ -11,6 +11,7 @@ use mock::*;
 fn create_text_proposal_codex_call_succeeds() {
     initial_test_ext().execute_with(|| {
         let account_id = 1;
+        let proposer_id = 1;
         let origin = RawOrigin::Signed(account_id).into();
 
         let required_stake = Some(<BalanceOf<Test>>::from(500u32));
@@ -19,6 +20,7 @@ fn create_text_proposal_codex_call_succeeds() {
         assert_eq!(
             ProposalCodex::create_text_proposal(
                 origin,
+                proposer_id,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 b"text".to_vec(),
@@ -39,12 +41,13 @@ fn create_text_proposal_codex_call_fails_with_invalid_stake() {
         assert_eq!(
             ProposalCodex::create_text_proposal(
                 RawOrigin::Signed(1).into(),
+                1,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 b"text".to_vec(),
                 None,
             ),
-            Err(Error::Other("Stake cannot be empty with this proposal"))
+            Err(Error::Other("EmptyStake"))
         );
 
         let invalid_stake = Some(<BalanceOf<Test>>::from(5000u32));
@@ -52,12 +55,13 @@ fn create_text_proposal_codex_call_fails_with_invalid_stake() {
         assert_eq!(
             ProposalCodex::create_text_proposal(
                 RawOrigin::Signed(1).into(),
+                1,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 b"text".to_vec(),
                 invalid_stake,
             ),
-            Err(Error::Other("Stake differs from the proposal requirements"))
+            Err(Error::Other("StakeDiffersFromRequired"))
         );
     });
 }
@@ -71,6 +75,7 @@ fn create_text_proposal_codex_call_fails_with_incorrect_text_size() {
         assert_eq!(
             ProposalCodex::create_text_proposal(
                 origin,
+                1,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 long_text,
@@ -82,6 +87,7 @@ fn create_text_proposal_codex_call_fails_with_incorrect_text_size() {
         assert_eq!(
             ProposalCodex::create_text_proposal(
                 RawOrigin::Signed(1).into(),
+                1,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 Vec::new(),
@@ -99,6 +105,7 @@ fn create_text_proposal_codex_call_fails_with_insufficient_rights() {
 
         assert!(ProposalCodex::create_text_proposal(
             origin,
+            1,
             b"title".to_vec(),
             b"body".to_vec(),
             b"text".to_vec(),
@@ -117,6 +124,7 @@ fn create_upgrade_runtime_proposal_codex_call_fails_with_incorrect_wasm_size() {
         assert_eq!(
             ProposalCodex::create_runtime_upgrade_proposal(
                 origin,
+                1,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 long_wasm,
@@ -128,6 +136,7 @@ fn create_upgrade_runtime_proposal_codex_call_fails_with_incorrect_wasm_size() {
         assert_eq!(
             ProposalCodex::create_runtime_upgrade_proposal(
                 RawOrigin::Signed(1).into(),
+                1,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 Vec::new(),
@@ -145,6 +154,7 @@ fn create_upgrade_runtime_proposal_codex_call_fails_with_insufficient_rights() {
 
         assert!(ProposalCodex::create_runtime_upgrade_proposal(
             origin,
+            1,
             b"title".to_vec(),
             b"body".to_vec(),
             b"wasm".to_vec(),
@@ -157,15 +167,17 @@ fn create_upgrade_runtime_proposal_codex_call_fails_with_insufficient_rights() {
 #[test]
 fn create_runtime_upgrade_proposal_codex_call_fails_with_invalid_stake() {
     initial_test_ext().execute_with(|| {
+        let proposer_id = 1;
         assert_eq!(
             ProposalCodex::create_runtime_upgrade_proposal(
                 RawOrigin::Signed(1).into(),
+                proposer_id,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 b"wasm".to_vec(),
                 None,
             ),
-            Err(Error::Other("Stake cannot be empty with this proposal"))
+            Err(Error::Other("EmptyStake"))
         );
 
         let invalid_stake = Some(<BalanceOf<Test>>::from(500u32));
@@ -173,12 +185,13 @@ fn create_runtime_upgrade_proposal_codex_call_fails_with_invalid_stake() {
         assert_eq!(
             ProposalCodex::create_runtime_upgrade_proposal(
                 RawOrigin::Signed(1).into(),
+                proposer_id,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 b"wasm".to_vec(),
                 invalid_stake,
             ),
-            Err(Error::Other("Stake differs from the proposal requirements"))
+            Err(Error::Other("StakeDiffersFromRequired"))
         );
     });
 }
@@ -187,6 +200,7 @@ fn create_runtime_upgrade_proposal_codex_call_fails_with_invalid_stake() {
 fn create_runtime_upgrade_proposal_codex_call_succeeds() {
     initial_test_ext().execute_with(|| {
         let account_id = 1;
+        let proposer_id = 1;
         let origin = RawOrigin::Signed(account_id).into();
 
         let required_stake = Some(<BalanceOf<Test>>::from(50000u32));
@@ -195,6 +209,7 @@ fn create_runtime_upgrade_proposal_codex_call_succeeds() {
         assert_eq!(
             ProposalCodex::create_runtime_upgrade_proposal(
                 origin,
+                proposer_id,
                 b"title".to_vec(),
                 b"body".to_vec(),
                 b"wasm".to_vec(),

+ 27 - 10
modules/proposals/discussion/Cargo.toml → runtime-modules/proposals/discussion/Cargo.toml

@@ -12,13 +12,14 @@ std = [
     'rstd/std',
     'srml-support/std',
     'primitives/std',
-    'runtime-primitives/std',
+    'sr-primitives/std',
     'system/std',
     'timestamp/std',
     'serde',
+    'membership/std',
+    'common/std',
 ]
 
-
 [dependencies.num_enum]
 default_features = false
 version = "0.4.2"
@@ -38,40 +39,56 @@ version = '1.0.0'
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'substrate-primitives'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.rstd]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'sr-std'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
-[dependencies.runtime-primitives]
+[dependencies.sr-primitives]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'sr-primitives'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.srml-support]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-support'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.system]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-system'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.timestamp]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-timestamp'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
+
+[dependencies.membership]
+default_features = false
+package = 'substrate-membership-module'
+path = '../../membership'
+
+[dependencies.common]
+default_features = false
+package = 'substrate-common-module'
+path = '../../common'
 
 [dev-dependencies.runtime-io]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'sr-io'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
+
+[dev-dependencies.balances]
+package = 'srml-balances'
+default-features = false
+git = 'https://github.com/paritytech/substrate.git'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'

+ 321 - 0
runtime-modules/proposals/discussion/src/lib.rs

@@ -0,0 +1,321 @@
+//! Proposals discussion module for the Joystream platform. Version 2.
+//! Contains discussion subsystem for the proposals engine.
+//!
+//! Supported extrinsics:
+//! - add_post - adds a post to existing discussion thread
+//! - update_post - updates existing post
+//!
+//! Public API:
+//! - create_discussion - creates a discussion
+//!
+
+// Ensure we're `no_std` when compiling for Wasm.
+#![cfg_attr(not(feature = "std"), no_std)]
+
+// Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
+//#![warn(missing_docs)]
+
+#[cfg(test)]
+mod tests;
+mod types;
+
+use rstd::clone::Clone;
+use rstd::prelude::*;
+use rstd::vec::Vec;
+use srml_support::{decl_error, decl_event, decl_module, decl_storage, ensure, Parameter};
+
+use srml_support::traits::Get;
+use types::{Post, Thread, ThreadCounter};
+
+use common::origin_validator::ActorOriginValidator;
+use srml_support::dispatch::DispatchResult;
+
+type MemberId<T> = <T as membership::members::Trait>::MemberId;
+
+decl_event!(
+    /// Proposals engine events
+    pub enum Event<T>
+    where
+        <T as Trait>::ThreadId,
+        MemberId = MemberId<T>,
+        <T as Trait>::PostId,
+    {
+    	/// Emits on thread creation.
+        ThreadCreated(ThreadId, MemberId),
+
+    	/// Emits on post creation.
+        PostCreated(PostId, MemberId),
+
+    	/// Emits on post update.
+        PostUpdated(PostId, MemberId),
+    }
+);
+
+/// 'Proposal discussion' substrate module Trait
+pub trait Trait: system::Trait + membership::members::Trait {
+    /// Engine event type.
+    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
+
+    /// Validates post author id and origin combination
+    type PostAuthorOriginValidator: ActorOriginValidator<
+        Self::Origin,
+        MemberId<Self>,
+        Self::AccountId,
+    >;
+
+    /// Discussion thread Id type
+    type ThreadId: From<u32> + Into<u32> + Parameter + Default + Copy;
+
+    /// Post Id type
+    type PostId: From<u32> + Parameter + Default + Copy;
+
+    /// Defines post edition number limit.
+    type MaxPostEditionNumber: Get<u32>;
+
+    /// Defines thread title length limit.
+    type ThreadTitleLengthLimit: Get<u32>;
+
+    /// Defines post length limit.
+    type PostLengthLimit: Get<u32>;
+
+    /// Defines max thread by same author in a row number limit.
+    type MaxThreadInARowNumber: Get<u32>;
+}
+
+decl_error! {
+    pub enum Error {
+        /// The size of the provided text for text proposal exceeded the limit
+        TextProposalSizeExceeded,
+
+        /// Author should match the post creator
+        NotAuthor,
+
+        ///  Post edition limit reached
+        PostEditionNumberExceeded,
+
+        /// Discussion cannot have an empty title
+        EmptyTitleProvided,
+
+        /// Title is too long
+        TitleIsTooLong,
+
+        /// Thread doesn't exist
+        ThreadDoesntExist,
+
+        /// Post doesn't exist
+        PostDoesntExist,
+
+        /// Post cannot be empty
+        EmptyPostProvided,
+
+        /// Post is too long
+        PostIsTooLong,
+
+        /// Max number of threads by same author in a row limit exceeded
+        MaxThreadInARowLimitExceeded,
+
+        /// Require root origin in extrinsics
+        RequireRootOrigin,
+    }
+}
+
+impl From<system::Error> for Error {
+    fn from(error: system::Error) -> Self {
+        match error {
+            system::Error::Other(msg) => Error::Other(msg),
+            system::Error::RequireRootOrigin => Error::RequireRootOrigin,
+            _ => Error::Other(error.into()),
+        }
+    }
+}
+
+// Storage for the proposals discussion module
+decl_storage! {
+    pub trait Store for Module<T: Trait> as ProposalDiscussion {
+        /// Map thread identifier to corresponding thread.
+        pub ThreadById get(thread_by_id): map T::ThreadId =>
+            Thread<MemberId<T>, T::BlockNumber>;
+
+        /// Count of all threads that have been created.
+        pub ThreadCount get(fn thread_count): u32;
+
+        /// Map thread id and post id to corresponding post.
+        pub PostThreadIdByPostId: double_map T::ThreadId, twox_128(T::PostId) =>
+             Post<MemberId<T>, T::BlockNumber, T::ThreadId>;
+
+        /// Count of all posts that have been created.
+        pub PostCount get(fn post_count): u32;
+
+        /// Last author thread counter (part of the antispam mechanism)
+        pub LastThreadAuthorCounter get(fn last_thread_author_counter):
+            Option<ThreadCounter<MemberId<T>>>;
+    }
+}
+
+decl_module! {
+    /// 'Proposal discussion' substrate module
+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+        /// Predefined errors
+        type Error = Error;
+
+        /// Emits an event. Default substrate implementation.
+        fn deposit_event() = default;
+
+        /// Adds a post with author origin check.
+        pub fn add_post(
+            origin,
+            post_author_id: MemberId<T>,
+            thread_id : T::ThreadId,
+            text : Vec<u8>
+        ) {
+            T::PostAuthorOriginValidator::ensure_actor_origin(
+                origin,
+                post_author_id.clone(),
+            )?;
+            ensure!(<ThreadById<T>>::exists(thread_id), Error::ThreadDoesntExist);
+
+            ensure!(!text.is_empty(),Error::EmptyPostProvided);
+            ensure!(
+                text.len() as u32 <= T::PostLengthLimit::get(),
+                Error::PostIsTooLong
+            );
+
+            // mutation
+
+            let next_post_count_value = Self::post_count() + 1;
+            let new_post_id = next_post_count_value;
+
+            let new_post = Post {
+                text,
+                created_at: Self::current_block(),
+                updated_at: Self::current_block(),
+                author_id: post_author_id.clone(),
+                edition_number : 0,
+                thread_id,
+            };
+
+            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));
+       }
+
+        /// Updates a post with author origin check. Update attempts number is limited.
+        pub fn update_post(
+            origin,
+            post_author_id: MemberId<T>,
+            thread_id: T::ThreadId,
+            post_id : T::PostId,
+            text : Vec<u8>
+        ){
+            T::PostAuthorOriginValidator::ensure_actor_origin(
+                origin,
+                post_author_id.clone(),
+            )?;
+
+            ensure!(<ThreadById<T>>::exists(thread_id), Error::ThreadDoesntExist);
+            ensure!(<PostThreadIdByPostId<T>>::exists(thread_id, post_id), Error::PostDoesntExist);
+
+            ensure!(!text.is_empty(), Error::EmptyPostProvided);
+            ensure!(
+                text.len() as u32 <= T::PostLengthLimit::get(),
+                Error::PostIsTooLong
+            );
+
+            let post = <PostThreadIdByPostId<T>>::get(&thread_id, &post_id);
+
+            ensure!(post.author_id == post_author_id, Error::NotAuthor);
+            ensure!(post.edition_number < T::MaxPostEditionNumber::get(),
+                Error::PostEditionNumberExceeded);
+
+            let new_post = Post {
+                text,
+                updated_at: Self::current_block(),
+                edition_number: post.edition_number + 1,
+                ..post
+            };
+
+            // mutation
+
+            <PostThreadIdByPostId<T>>::insert(thread_id, post_id, new_post);
+            Self::deposit_event(RawEvent::PostUpdated(post_id, post_author_id));
+       }
+    }
+}
+
+impl<T: Trait> Module<T> {
+    // Wrapper-function over system::block_number()
+    fn current_block() -> T::BlockNumber {
+        <system::Module<T>>::block_number()
+    }
+
+    /// Create the discussion thread. Cannot add more threads than 'predefined limit = MaxThreadInARowNumber'
+    /// times in a row by the same author.
+    pub fn create_thread(
+        thread_author_id: MemberId<T>,
+        title: Vec<u8>,
+    ) -> Result<T::ThreadId, Error> {
+        Self::ensure_can_create_thread(&title, thread_author_id.clone())?;
+
+        let next_thread_count_value = Self::thread_count() + 1;
+        let new_thread_id = next_thread_count_value;
+
+        let new_thread = Thread {
+            title,
+            created_at: Self::current_block(),
+            author_id: thread_author_id.clone(),
+        };
+
+        // get new 'threads in a row' counter for the author
+        let current_thread_counter = Self::get_updated_thread_counter(thread_author_id.clone());
+
+        // mutation
+
+        let thread_id = T::ThreadId::from(new_thread_id);
+        <ThreadById<T>>::insert(thread_id, new_thread);
+        ThreadCount::put(next_thread_count_value);
+        <LastThreadAuthorCounter<T>>::put(current_thread_counter);
+        Self::deposit_event(RawEvent::ThreadCreated(thread_id, thread_author_id));
+
+        Ok(thread_id)
+    }
+
+    // returns incremented thread counter if last thread author equals with provided parameter
+    fn get_updated_thread_counter(author_id: MemberId<T>) -> ThreadCounter<MemberId<T>> {
+        // if thread counter exists
+        if let Some(last_thread_author_counter) = Self::last_thread_author_counter() {
+            // if last(previous) author is the same as current author
+            if last_thread_author_counter.author_id == author_id {
+                return last_thread_author_counter.increment();
+            }
+        }
+
+        // else return new counter (set with 1 thread number)
+        ThreadCounter::new(author_id)
+    }
+
+    /// Ensures thread can be created.
+    /// Checks:
+    /// - title is valid
+    /// - max thread in a row by the same author
+    pub fn ensure_can_create_thread(
+        title: &[u8],
+        thread_author_id: MemberId<T>,
+    ) -> DispatchResult<Error> {
+        ensure!(!title.is_empty(), Error::EmptyTitleProvided);
+        ensure!(
+            title.len() as u32 <= T::ThreadTitleLengthLimit::get(),
+            Error::TitleIsTooLong
+        );
+
+        // get new 'threads in a row' counter for the author
+        let current_thread_counter = Self::get_updated_thread_counter(thread_author_id.clone());
+
+        ensure!(
+            current_thread_counter.counter as u32 <= T::MaxThreadInARowNumber::get(),
+            Error::MaxThreadInARowLimitExceeded
+        );
+
+        Ok(())
+    }
+}

+ 72 - 8
modules/proposals/discussion/src/tests/mock.rs → runtime-modules/proposals/discussion/src/tests/mock.rs

@@ -3,14 +3,15 @@
 pub use system;
 
 pub use primitives::{Blake2Hasher, H256};
-pub use runtime_primitives::{
+pub use sr_primitives::{
     testing::{Digest, DigestItem, Header, UintAuthorityId},
     traits::{BlakeTwo256, Convert, IdentityLookup, OnFinalize},
     weights::Weight,
     BuildStorage, Perbill,
 };
 
-use srml_support::{impl_outer_origin, parameter_types};
+use crate::ActorOriginValidator;
+use srml_support::{impl_outer_event, impl_outer_origin, parameter_types};
 
 impl_outer_origin! {
     pub enum Origin for Test {}
@@ -30,33 +31,95 @@ parameter_types! {
 
 parameter_types! {
     pub const MaxPostEditionNumber: u32 = 5;
+    pub const MaxThreadInARowNumber: u32 = 3;
     pub const ThreadTitleLengthLimit: u32 = 200;
     pub const PostLengthLimit: u32 = 2000;
 }
 
+mod discussion {
+    pub use crate::Event;
+}
+
+mod membership_mod {
+    pub use membership::members::Event;
+}
+
+impl_outer_event! {
+    pub enum TestEvent for Test {
+        discussion<T>,
+        balances<T>,
+        membership_mod<T>,
+    }
+}
+
+parameter_types! {
+    pub const ExistentialDeposit: u32 = 0;
+    pub const TransferFee: u32 = 0;
+    pub const CreationFee: u32 = 0;
+}
+
+impl balances::Trait for Test {
+    type Balance = u64;
+    /// What to do if an account's free balance gets zeroed.
+    type OnFreeBalanceZero = ();
+    type OnNewAccount = ();
+    type TransferPayment = ();
+    type DustRemoval = ();
+    type Event = TestEvent;
+    type ExistentialDeposit = ExistentialDeposit;
+    type TransferFee = TransferFee;
+    type CreationFee = CreationFee;
+}
+
+impl common::currency::GovernanceCurrency for Test {
+    type Currency = balances::Module<Self>;
+}
+
+impl membership::members::Trait for Test {
+    type Event = TestEvent;
+    type MemberId = u64;
+    type PaidTermId = u64;
+    type SubscriptionId = u64;
+    type ActorId = u64;
+    type InitialMembersBalance = ();
+}
+
 impl crate::Trait for Test {
-    type ThreadAuthorOrigin = system::EnsureSigned<Self::AccountId>;
-    type PostAuthorOrigin = system::EnsureSigned<Self::AccountId>;
+    type Event = TestEvent;
+    type PostAuthorOriginValidator = ();
     type ThreadId = u32;
     type PostId = u32;
-    type ThreadAuthorId = u64;
-    type PostAuthorId = u64;
     type MaxPostEditionNumber = MaxPostEditionNumber;
     type ThreadTitleLengthLimit = ThreadTitleLengthLimit;
     type PostLengthLimit = PostLengthLimit;
+    type MaxThreadInARowNumber = MaxThreadInARowNumber;
+}
+
+impl ActorOriginValidator<Origin, u64, u64> for () {
+    fn ensure_actor_origin(origin: Origin, actor_id: u64) -> Result<u64, &'static str> {
+        if system::ensure_none(origin).is_ok() {
+            return Ok(1);
+        }
+
+        if actor_id == 1 {
+            return Ok(1);
+        }
+
+        Err("Invalid author")
+    }
 }
 
 impl system::Trait for Test {
     type Origin = Origin;
+    type Call = ();
     type Index = u64;
     type BlockNumber = u64;
-    type Call = ();
     type Hash = H256;
     type Hashing = BlakeTwo256;
     type AccountId = u64;
     type Lookup = IdentityLookup<Self::AccountId>;
     type Header = Header;
-    type Event = ();
+    type Event = TestEvent;
     type BlockHashCount = BlockHashCount;
     type MaximumBlockWeight = MaximumBlockWeight;
     type MaximumBlockLength = MaximumBlockLength;
@@ -79,3 +142,4 @@ pub fn initial_test_ext() -> runtime_io::TestExternalities {
 }
 
 pub type Discussions = crate::Module<Test>;
+pub type System = system::Module<Test>;

+ 75 - 30
modules/proposals/discussion/src/tests/mod.rs → runtime-modules/proposals/discussion/src/tests/mod.rs

@@ -4,6 +4,23 @@ use mock::*;
 
 use crate::*;
 use system::RawOrigin;
+use system::{EventRecord, Phase};
+
+struct EventFixture;
+impl EventFixture {
+    fn assert_events(expected_raw_events: Vec<RawEvent<u32, u64, u32>>) {
+        let expected_events = expected_raw_events
+            .iter()
+            .map(|ev| EventRecord {
+                phase: Phase::ApplyExtrinsic(0),
+                event: TestEvent::discussion(ev.clone()),
+                topics: vec![],
+            })
+            .collect::<Vec<EventRecord<_, _>>>();
+
+        assert_eq!(System::events(), expected_events);
+    }
+}
 
 struct TestPostEntry {
     pub post_id: u32,
@@ -46,6 +63,7 @@ fn assert_thread_content(thread_entry: TestThreadEntry, post_entries: Vec<TestPo
 struct DiscussionFixture {
     pub title: Vec<u8>,
     pub origin: RawOrigin<u64>,
+    pub author_id: u64,
 }
 
 impl Default for DiscussionFixture {
@@ -53,6 +71,7 @@ impl Default for DiscussionFixture {
         DiscussionFixture {
             title: b"title".to_vec(),
             origin: RawOrigin::Signed(1),
+            author_id: 1,
         }
     }
 }
@@ -61,6 +80,15 @@ impl DiscussionFixture {
     fn with_title(self, title: Vec<u8>) -> Self {
         DiscussionFixture { title, ..self }
     }
+
+    fn create_discussion_and_assert(&self, result: Result<u32, Error>) -> Option<u32> {
+        let create_discussion_result =
+            Discussions::create_thread(self.author_id, self.title.clone());
+
+        assert_eq!(create_discussion_result, result);
+
+        create_discussion_result.ok()
+    }
 }
 
 struct PostFixture {
@@ -68,12 +96,14 @@ struct PostFixture {
     pub origin: RawOrigin<u64>,
     pub thread_id: u32,
     pub post_id: Option<u32>,
+    pub author_id: u64,
 }
 
 impl PostFixture {
     fn default_for_thread(thread_id: u32) -> Self {
         PostFixture {
             text: b"text".to_vec(),
+            author_id: 1,
             thread_id,
             origin: RawOrigin::Signed(1),
             post_id: None,
@@ -88,6 +118,10 @@ impl PostFixture {
         PostFixture { origin, ..self }
     }
 
+    fn with_author(self, author_id: u64) -> Self {
+        PostFixture { author_id, ..self }
+    }
+
     fn change_thread_id(self, thread_id: u32) -> Self {
         PostFixture { thread_id, ..self }
     }
@@ -99,9 +133,10 @@ impl PostFixture {
         }
     }
 
-    fn add_post_and_assert(&mut self, result: Result<(), &'static str>) -> Option<u32> {
+    fn add_post_and_assert(&mut self, result: Result<(), Error>) -> Option<u32> {
         let add_post_result = Discussions::add_post(
             self.origin.clone().into(),
+            self.author_id,
             self.thread_id,
             self.text.clone(),
         );
@@ -115,13 +150,10 @@ impl PostFixture {
         self.post_id
     }
 
-    fn update_post_with_text_and_assert(
-        &mut self,
-        new_text: Vec<u8>,
-        result: Result<(), &'static str>,
-    ) {
+    fn update_post_with_text_and_assert(&mut self, new_text: Vec<u8>, result: Result<(), Error>) {
         let add_post_result = Discussions::update_post(
             self.origin.clone().into(),
+            self.author_id,
             self.thread_id,
             self.post_id.unwrap(),
             new_text,
@@ -130,22 +162,11 @@ impl PostFixture {
         assert_eq!(add_post_result, result);
     }
 
-    fn update_post_and_assert(&mut self, result: Result<(), &'static str>) {
+    fn update_post_and_assert(&mut self, result: Result<(), Error>) {
         self.update_post_with_text_and_assert(self.text.clone(), result);
     }
 }
 
-impl DiscussionFixture {
-    fn create_discussion_and_assert(&self, result: Result<u32, &'static str>) -> Option<u32> {
-        let create_discussion_result =
-            Discussions::create_thread(self.origin.clone().into(), self.title.clone());
-
-        assert_eq!(create_discussion_result, result);
-
-        create_discussion_result.ok()
-    }
-}
-
 #[test]
 fn create_discussion_call_succeeds() {
     initial_test_ext().execute_with(|| {
@@ -183,6 +204,12 @@ fn update_post_call_succeeds() {
 
         post_fixture.add_post_and_assert(Ok(()));
         post_fixture.update_post_and_assert(Ok(()));
+
+        EventFixture::assert_events(vec![
+            RawEvent::ThreadCreated(1, 1),
+            RawEvent::PostCreated(1, 1),
+            RawEvent::PostUpdated(1, 1),
+        ]);
     });
 }
 
@@ -203,7 +230,7 @@ fn update_post_call_failes_because_of_post_edition_limit() {
             post_fixture.update_post_and_assert(Ok(()));
         }
 
-        post_fixture.update_post_and_assert(Err(MSG_POST_EDITION_NUMBER_EXCEEDED));
+        post_fixture.update_post_and_assert(Err(Error::PostEditionNumberExceeded));
     });
 }
 
@@ -220,9 +247,13 @@ fn update_post_call_failes_because_of_the_wrong_author() {
 
         post_fixture.add_post_and_assert(Ok(()));
 
-        post_fixture = post_fixture.with_origin(RawOrigin::Signed(2));
+        post_fixture = post_fixture.with_author(2);
+
+        post_fixture.update_post_and_assert(Err(Error::Other("Invalid author")));
+
+        post_fixture = post_fixture.with_origin(RawOrigin::None).with_author(2);
 
-        post_fixture.update_post_and_assert(Err(MSG_NOT_AUTHOR));
+        post_fixture.update_post_and_assert(Err(Error::NotAuthor));
     });
 }
 
@@ -267,10 +298,10 @@ fn thread_content_check_succeeded() {
 fn create_discussion_call_with_bad_title_failed() {
     initial_test_ext().execute_with(|| {
         let mut discussion_fixture = DiscussionFixture::default().with_title(Vec::new());
-        discussion_fixture.create_discussion_and_assert(Err(crate::MSG_EMPTY_TITLE_PROVIDED));
+        discussion_fixture.create_discussion_and_assert(Err(Error::EmptyTitleProvided));
 
         discussion_fixture = DiscussionFixture::default().with_title([0; 201].to_vec());
-        discussion_fixture.create_discussion_and_assert(Err(crate::MSG_TOO_LONG_TITLE));
+        discussion_fixture.create_discussion_and_assert(Err(Error::TitleIsTooLong));
     });
 }
 
@@ -283,7 +314,7 @@ fn add_post_call_with_invalid_thread_failed() {
             .unwrap();
 
         let mut post_fixture = PostFixture::default_for_thread(2);
-        post_fixture.add_post_and_assert(Err(MSG_THREAD_DOESNT_EXIST));
+        post_fixture.add_post_and_assert(Err(Error::ThreadDoesntExist));
     });
 }
 
@@ -299,7 +330,7 @@ fn update_post_call_with_invalid_post_failed() {
         post_fixture1.add_post_and_assert(Ok(())).unwrap();
 
         let mut post_fixture2 = post_fixture1.change_post_id(2);
-        post_fixture2.update_post_and_assert(Err(MSG_POST_DOESNT_EXIST));
+        post_fixture2.update_post_and_assert(Err(Error::PostDoesntExist));
     });
 }
 
@@ -315,7 +346,7 @@ fn update_post_call_with_invalid_thread_failed() {
         post_fixture1.add_post_and_assert(Ok(())).unwrap();
 
         let mut post_fixture2 = post_fixture1.change_thread_id(2);
-        post_fixture2.update_post_and_assert(Err(MSG_THREAD_DOESNT_EXIST));
+        post_fixture2.update_post_and_assert(Err(Error::ThreadDoesntExist));
     });
 }
 
@@ -328,11 +359,11 @@ fn add_post_call_with_invalid_text_failed() {
             .unwrap();
 
         let mut post_fixture1 = PostFixture::default_for_thread(thread_id).with_text(Vec::new());
-        post_fixture1.add_post_and_assert(Err(MSG_EMPTY_POST_PROVIDED));
+        post_fixture1.add_post_and_assert(Err(Error::EmptyPostProvided));
 
         let mut post_fixture2 =
             PostFixture::default_for_thread(thread_id).with_text([0; 2001].to_vec());
-        post_fixture2.add_post_and_assert(Err(MSG_TOO_LONG_POST));
+        post_fixture2.add_post_and_assert(Err(Error::PostIsTooLong));
     });
 }
 
@@ -348,9 +379,23 @@ fn update_post_call_with_invalid_text_failed() {
         post_fixture1.add_post_and_assert(Ok(()));
 
         let mut post_fixture2 = post_fixture1.with_text(Vec::new());
-        post_fixture2.update_post_and_assert(Err(MSG_EMPTY_POST_PROVIDED));
+        post_fixture2.update_post_and_assert(Err(Error::EmptyPostProvided));
 
         let mut post_fixture3 = post_fixture2.with_text([0; 2001].to_vec());
-        post_fixture3.update_post_and_assert(Err(MSG_TOO_LONG_POST));
+        post_fixture3.update_post_and_assert(Err(Error::PostIsTooLong));
+    });
+}
+
+#[test]
+fn add_discussion_thread_fails_because_of_max_thread_by_same_author_in_a_row_limit_exceeded() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+        for idx in 1..=3 {
+            discussion_fixture
+                .create_discussion_and_assert(Ok(idx))
+                .unwrap();
+        }
+
+        discussion_fixture.create_discussion_and_assert(Err(Error::MaxThreadInARowLimitExceeded));
     });
 }

+ 31 - 2
modules/proposals/discussion/src/types.rs → runtime-modules/proposals/discussion/src/types.rs

@@ -1,8 +1,8 @@
-use rstd::prelude::*;
+use codec::{Decode, Encode};
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 
-use codec::{Decode, Encode};
+use rstd::prelude::*;
 
 /// Represents a discussion thread
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
@@ -40,3 +40,32 @@ pub struct Post<PostAuthorId, BlockNumber, ThreadId> {
     /// Defines how many times this post was edited. Zero on creation.
     pub edition_number: u32,
 }
+
+/// Post for the discussion thread
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Default, Clone, Copy, PartialEq, Eq)]
+pub struct ThreadCounter<ThreadAuthorId> {
+    /// Author of the threads.
+    pub author_id: ThreadAuthorId,
+
+    /// ThreadCount
+    pub counter: u32,
+}
+
+impl<ThreadAuthorId: Clone> ThreadCounter<ThreadAuthorId> {
+    /// Increments existing counter
+    pub fn increment(&self) -> Self {
+        ThreadCounter {
+            counter: self.counter + 1,
+            author_id: self.author_id.clone(),
+        }
+    }
+
+    /// Creates new counter by author_id. Counter instantiated with 1.
+    pub fn new(author_id: ThreadAuthorId) -> Self {
+        ThreadCounter {
+            author_id,
+            counter: 1,
+        }
+    }
+}

+ 28 - 18
modules/proposals/engine/Cargo.toml → runtime-modules/proposals/engine/Cargo.toml

@@ -12,12 +12,15 @@ std = [
     'rstd/std',
     'srml-support/std',
     'primitives/std',
-    'runtime-primitives/std',
     'system/std',
     'timestamp/std',
     'serde',
     'stake/std',
     'balances/std',
+    'sr-primitives/std',
+    'membership/std',
+    'common/std',
+
 ]
 
 
@@ -40,49 +43,58 @@ version = '1.0.0'
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'substrate-primitives'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.rstd]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'sr-std'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
-
-[dependencies.runtime-primitives]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-primitives'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.srml-support]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-support'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.system]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-system'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.timestamp]
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-timestamp'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.balances]
 package = 'srml-balances'
 default-features = false
 git = 'https://github.com/paritytech/substrate.git'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
+
+[dependencies.sr-primitives]
+default_features = false
+git = 'https://github.com/paritytech/substrate.git'
+package = 'sr-primitives'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
 [dependencies.stake]
 default_features = false
-git = 'https://github.com/joystream/substrate-stake-module'
 package = 'substrate-stake-module'
-rev = '0516efe9230da112bc095e28f34a3715c2e03ca8'
+path = '../../stake'
+
+[dependencies.membership]
+default_features = false
+package = 'substrate-membership-module'
+path = '../../membership'
+
+[dependencies.common]
+default_features = false
+package = 'substrate-common-module'
+path = '../../common'
 
 [dev-dependencies]
 mockall = "0.6.0"
@@ -91,6 +103,4 @@ mockall = "0.6.0"
 default_features = false
 git = 'https://github.com/paritytech/substrate.git'
 package = 'sr-io'
-rev = '0e3001a1ad6fa3d1ba7da7342a8d0d3b3facb2f3'
-
-
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'

+ 0 - 4
modules/proposals/engine/src/errors.rs → runtime-modules/proposals/engine/src/errors.rs

@@ -12,7 +12,3 @@ pub const MSG_STAKE_SHOULD_BE_EMPTY: &str = "Stake should be empty for this prop
 pub const MSG_STAKE_DIFFERS_FROM_REQUIRED: &str = "Stake differs from the proposal requirements";
 pub const MSG_INVALID_PARAMETER_APPROVAL_THRESHOLD: &str = "Approval threshold cannot be zero";
 pub const MSG_INVALID_PARAMETER_SLASHING_THRESHOLD: &str = "Slashing threshold cannot be zero";
-
-//pub const MSG_STAKE_IS_GREATER_THAN_BALANCE: &str = "Balance is too low to be staked";
-//pub const MSG_ONLY_MEMBERS_CAN_PROPOSE: &str = "Only members can make a proposal";
-//pub const MSG_ONLY_COUNCILORS_CAN_VOTE: &str = "Only councilors can vote on proposals";

+ 305 - 180
modules/proposals/engine/src/lib.rs → runtime-modules/proposals/engine/src/lib.rs

@@ -20,60 +20,63 @@
 // TODO: Test module after the https://github.com/Joystream/substrate-runtime-joystream/issues/161
 // issue will be fixed: "Fix stake module and allow slashing and unstaking in the same block."
 // TODO: Test cancellation, rejection fees
+// TODO: Test StakingEventHandler
+// TODO: Test refund_proposal_stake()
 
-pub use types::BalanceOf;
 use types::FinalizedProposalData;
 use types::ProposalStakeManager;
-pub use types::VotingResults;
 pub use types::{
-    ApprovedProposalStatus, FinalizationData, Proposal, ProposalDecisionStatus, ProposalParameters,
-    ProposalStatus,
+    ActiveStake, ApprovedProposalStatus, FinalizationData, Proposal, ProposalDecisionStatus,
+    ProposalParameters, ProposalStatus, VotingResults,
 };
+pub use types::{BalanceOf, CurrencyOf, NegativeImbalance};
 pub use types::{DefaultStakeHandlerProvider, StakeHandler, StakeHandlerProvider};
 pub use types::{ProposalCodeDecoder, ProposalExecutable};
 pub use types::{VoteKind, VotersParameters};
 
-mod errors;
 pub(crate) mod types;
 
 #[cfg(test)]
 mod tests;
 
+use codec::Decode;
 use rstd::prelude::*;
-
-use runtime_primitives::traits::{EnsureOrigin, Zero};
-use srml_support::traits::Get;
+use sr_primitives::traits::{DispatchResult, Zero};
+use srml_support::traits::{Currency, Get};
 use srml_support::{
-    decl_event, decl_module, decl_storage, dispatch, ensure, Parameter, StorageDoubleMap,
+    decl_error, decl_event, decl_module, decl_storage, ensure, print, Parameter, StorageDoubleMap,
 };
-use system::ensure_root;
+use system::{ensure_root, RawOrigin};
+
+use crate::types::ApprovedProposalData;
+use common::origin_validator::ActorOriginValidator;
+use srml_support::dispatch::Dispatchable;
+
+type MemberId<T> = <T as membership::members::Trait>::MemberId;
 
 /// Proposals engine trait.
-pub trait Trait: system::Trait + timestamp::Trait + stake::Trait {
+pub trait Trait:
+    system::Trait + timestamp::Trait + stake::Trait + membership::members::Trait
+{
     /// Engine event type.
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    /// Origin from which proposals must come.
-    type ProposalOrigin: EnsureOrigin<Self::Origin, Success = Self::AccountId>;
+    /// Validates proposer id and origin combination
+    type ProposerOriginValidator: ActorOriginValidator<
+        Self::Origin,
+        MemberId<Self>,
+        Self::AccountId,
+    >;
 
-    /// Origin from which votes must come.
-    type VoteOrigin: EnsureOrigin<Self::Origin, Success = Self::AccountId>;
+    /// Validates voter id and origin combination
+    type VoterOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
 
     /// Provides data for voting. Defines maximum voters count for the proposal.
     type TotalVotersCounter: VotersParameters;
 
-    /// Converts proposal code binary to executable representation
-    type ProposalCodeDecoder: ProposalCodeDecoder<Self>;
-
     /// Proposal Id type
     type ProposalId: From<u32> + Parameter + Default + Copy;
 
-    /// Type for the proposer id. Should be authenticated by account id.
-    type ProposerId: From<Self::AccountId> + Parameter + Default;
-
-    /// Type for the voter id. Should be authenticated by account id.
-    type VoterId: From<Self::AccountId> + Parameter + Default + Clone;
-
     /// Provides stake logic implementation. Can be used to mock stake logic.
     type StakeHandlerProvider: StakeHandlerProvider<Self>;
 
@@ -91,6 +94,9 @@ pub trait Trait: system::Trait + timestamp::Trait + stake::Trait {
 
     /// Defines max simultaneous active proposals number.
     type MaxActiveProposalLimit: Get<u32>;
+
+    /// Proposals executable code. Can be instantiated by external module Call enum members.
+    type DispatchableCallCode: Parameter + Dispatchable<Origin = Self::Origin> + Default;
 }
 
 decl_event!(
@@ -98,42 +104,102 @@ decl_event!(
     pub enum Event<T>
     where
         <T as Trait>::ProposalId,
-        <T as Trait>::ProposerId,
-        <T as Trait>::VoterId,
+        MemberId = MemberId<T>,
         <T as system::Trait>::BlockNumber,
+        <T as system::Trait>::AccountId,
+        <T as stake::Trait>::StakeId,
     {
     	/// Emits on proposal creation.
         /// Params:
-        /// - Account id of a proposer.
+        /// - Member id of a proposer.
         /// - Id of a newly created proposal after it was saved in storage.
-        ProposalCreated(ProposerId, ProposalId),
+        ProposalCreated(MemberId, ProposalId),
 
         /// Emits on proposal status change.
         /// Params:
         /// - Id of a updated proposal.
         /// - New proposal status
-        ProposalStatusUpdated(ProposalId, ProposalStatus<BlockNumber>),
+        ProposalStatusUpdated(ProposalId, ProposalStatus<BlockNumber, StakeId, AccountId>),
 
         /// Emits on voting for the proposal
         /// Params:
-        /// - Voter - an account id of a voter.
+        /// - Voter - member id of a voter.
         /// - Id of a proposal.
         /// - Kind of vote.
-        Voted(VoterId, ProposalId, VoteKind),
+        Voted(MemberId, ProposalId, VoteKind),
     }
 );
 
+decl_error! {
+    pub enum Error {
+        /// Proposal cannot have an empty title"
+        EmptyTitleProvided,
+
+        /// Proposal cannot have an empty body
+        EmptyDescriptionProvided,
+
+        /// Title is too long
+        TitleIsTooLong,
+
+        /// Description is too long
+        DescriptionIsTooLong,
+
+        /// The proposal does not exist
+        ProposalNotFound,
+
+        /// Proposal is finalized already
+        ProposalFinalized,
+
+        /// The proposal have been already voted on
+        AlreadyVoted,
+
+        /// Not an author
+        NotAuthor,
+
+        /// Max active proposals number exceeded
+        MaxActiveProposalNumberExceeded,
+
+        /// Stake cannot be empty with this proposal
+        EmptyStake,
+
+        /// Stake should be empty for this proposal
+        StakeShouldBeEmpty,
+
+        /// Stake differs from the proposal requirements
+        StakeDiffersFromRequired,
+
+        /// Approval threshold cannot be zero
+        InvalidParameterApprovalThreshold,
+
+        /// Slashing threshold cannot be zero
+        InvalidParameterSlashingThreshold,
+
+        /// Require root origin in extrinsics
+        RequireRootOrigin,
+    }
+}
+
+impl From<system::Error> for Error {
+    fn from(error: system::Error) -> Self {
+        match error {
+            system::Error::Other(msg) => Error::Other(msg),
+            system::Error::RequireRootOrigin => Error::RequireRootOrigin,
+            _ => Error::Other(error.into()),
+        }
+    }
+}
+
 // Storage for the proposals engine module
 decl_storage! {
     pub trait Store for Module<T: Trait> as ProposalEngine{
         /// Map proposal by its id.
-        pub Proposals get(fn proposals): map T::ProposalId => ProposalObject<T>;
+        pub Proposals get(fn proposals): map T::ProposalId => ProposalOf<T>;
 
         /// Count of all proposals that have been created.
         pub ProposalCount get(fn proposal_count): u32;
 
         /// Map proposal executable code by proposal id.
-        pub ProposalCode get(fn proposal_codes): map T::ProposalId =>  Vec<u8>;
+        pub DispatchableCallCode get(fn proposal_codes): map T::ProposalId =>  Vec<u8>;
 
         /// Count of active proposals.
         pub ActiveProposalCount get(fn active_proposal_count): u32;
@@ -146,33 +212,40 @@ decl_storage! {
 
         /// Double map for preventing duplicate votes. Should be cleaned after usage.
         pub VoteExistsByProposalByVoter get(fn vote_by_proposal_by_voter):
-            double_map T::ProposalId, twox_256(T::VoterId) => VoteKind;
+            double_map T::ProposalId, twox_256(MemberId<T>) => VoteKind;
+
+        /// Map proposal id by stake id. Required by StakingEventsHandler callback call
+        pub StakesProposals get(fn stakes_proposals): map T::StakeId =>  T::ProposalId;
     }
 }
 
 decl_module! {
     /// 'Proposal engine' substrate module
     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+        /// Predefined errors
+        type Error = Error;
 
         /// Emits an event. Default substrate implementation.
         fn deposit_event() = default;
 
         /// Vote extrinsic. Conditions:  origin must allow votes.
-        pub fn vote(origin, proposal_id: T::ProposalId, vote: VoteKind)  {
-            let account_id = T::VoteOrigin::ensure_origin(origin)?;
-            let voter_id = T::VoterId::from(account_id);
+        pub fn vote(origin, voter_id: MemberId<T>, proposal_id: T::ProposalId, vote: VoteKind)  {
+            T::VoterOriginValidator::ensure_actor_origin(
+                origin,
+                voter_id.clone(),
+            )?;
 
-            ensure!(<Proposals<T>>::exists(proposal_id), errors::MSG_PROPOSAL_NOT_FOUND);
+            ensure!(<Proposals<T>>::exists(proposal_id), Error::ProposalNotFound);
             let mut proposal = Self::proposals(proposal_id);
 
-            ensure!(proposal.status == ProposalStatus::Active, errors::MSG_PROPOSAL_FINALIZED);
+            ensure!(matches!(proposal.status, ProposalStatus::Active{..}), Error::ProposalFinalized);
 
             let did_not_vote_before = !<VoteExistsByProposalByVoter<T>>::exists(
                 proposal_id,
                 voter_id.clone(),
             );
 
-            ensure!(did_not_vote_before, errors::MSG_YOU_ALREADY_VOTED);
+            ensure!(did_not_vote_before, Error::AlreadyVoted);
 
             proposal.voting_results.add_vote(vote.clone());
 
@@ -184,15 +257,17 @@ decl_module! {
         }
 
         /// Cancel a proposal by its original proposer.
-        pub fn cancel_proposal(origin, proposal_id: T::ProposalId) {
-            let account_id = T::ProposalOrigin::ensure_origin(origin)?;
-            let proposer_id = T::ProposerId::from(account_id);
+        pub fn cancel_proposal(origin, proposer_id: MemberId<T>, proposal_id: T::ProposalId) {
+            T::ProposerOriginValidator::ensure_actor_origin(
+                origin,
+                proposer_id.clone(),
+            )?;
 
-            ensure!(<Proposals<T>>::exists(proposal_id), errors::MSG_PROPOSAL_NOT_FOUND);
+            ensure!(<Proposals<T>>::exists(proposal_id), Error::ProposalNotFound);
             let proposal = Self::proposals(proposal_id);
 
-            ensure!(proposer_id == proposal.proposer_id, errors::MSG_YOU_DONT_OWN_THIS_PROPOSAL);
-            ensure!(proposal.status == ProposalStatus::Active, errors::MSG_PROPOSAL_FINALIZED);
+            ensure!(proposer_id == proposal.proposer_id, Error::NotAuthor);
+            ensure!(matches!(proposal.status, ProposalStatus::Active{..}), Error::ProposalFinalized);
 
             // mutation
 
@@ -203,10 +278,10 @@ decl_module! {
         pub fn veto_proposal(origin, proposal_id: T::ProposalId) {
             ensure_root(origin)?;
 
-            ensure!(<Proposals<T>>::exists(proposal_id), errors::MSG_PROPOSAL_NOT_FOUND);
+            ensure!(<Proposals<T>>::exists(proposal_id), Error::ProposalNotFound);
             let proposal = Self::proposals(proposal_id);
 
-            ensure!(proposal.status == ProposalStatus::Active, errors::MSG_PROPOSAL_FINALIZED);
+            ensure!(matches!(proposal.status, ProposalStatus::Active{..}), Error::ProposalFinalized);
 
             // mutation
 
@@ -227,12 +302,12 @@ decl_module! {
                 Self::finalize_proposal(proposal_data.proposal_id, proposal_data.status);
             }
 
-            let executable_proposal_ids =
-                Self::get_approved_proposal_with_expired_grace_period_ids();
+            let executable_proposals =
+                Self::get_approved_proposal_with_expired_grace_period();
 
             // Execute approved proposals with expired grace period
-            for  proposal_id in executable_proposal_ids {
-                Self::execute_proposal(proposal_id);
+            for approved_proosal in executable_proposals {
+                Self::execute_proposal(approved_proosal);
             }
         }
     }
@@ -241,17 +316,14 @@ decl_module! {
 impl<T: Trait> Module<T> {
     /// Create proposal. Requires 'proposal origin' membership.
     pub fn create_proposal(
-        origin: T::Origin,
+        account_id: T::AccountId,
+        proposer_id: MemberId<T>,
         parameters: ProposalParameters<T::BlockNumber, types::BalanceOf<T>>,
         title: Vec<u8>,
         description: Vec<u8>,
         stake_balance: Option<types::BalanceOf<T>>,
-        proposal_type: u32,
-        proposal_code: Vec<u8>,
-    ) -> Result<T::ProposalId, &'static str> {
-        let account_id = T::ProposalOrigin::ensure_origin(origin)?;
-        let proposer_id = T::ProposerId::from(account_id.clone());
-
+        encoded_dispatchable_call_code: Vec<u8>,
+    ) -> Result<T::ProposalId, Error> {
         Self::ensure_create_proposal_parameters_are_valid(
             &parameters,
             &title,
@@ -264,28 +336,38 @@ impl<T: Trait> Module<T> {
 
         let next_proposal_count_value = Self::proposal_count() + 1;
         let new_proposal_id = next_proposal_count_value;
+        let proposal_id = T::ProposalId::from(new_proposal_id);
 
         // Check stake_balance for value and create stake if value exists, else take None
         // If create_stake() returns error - return error from extrinsic
-        let stake_id = stake_balance
-            .map(|stake_amount| ProposalStakeManager::<T>::create_stake(stake_amount, account_id))
+        let stake_id_result = stake_balance
+            .map(|stake_amount| {
+                ProposalStakeManager::<T>::create_stake(stake_amount, account_id.clone())
+            })
             .transpose()?;
 
+        let mut stake_data = None;
+        if let Some(stake_id) = stake_id_result {
+            stake_data = Some(ActiveStake {
+                stake_id,
+                source_account_id: account_id,
+            });
+
+            <StakesProposals<T>>::insert(stake_id, proposal_id);
+        }
+
         let new_proposal = Proposal {
             created_at: Self::current_block(),
             parameters,
             title,
             description,
             proposer_id: proposer_id.clone(),
-            proposal_type,
-            status: ProposalStatus::Active,
+            status: ProposalStatus::Active(stake_data),
             voting_results: VotingResults::default(),
-            stake_id,
         };
 
-        let proposal_id = T::ProposalId::from(new_proposal_id);
         <Proposals<T>>::insert(proposal_id, new_proposal);
-        <ProposalCode<T>>::insert(proposal_id, proposal_code);
+        <DispatchableCallCode<T>>::insert(proposal_id, encoded_dispatchable_call_code);
         <ActiveProposalIds<T>>::insert(proposal_id, ());
         ProposalCount::put(next_proposal_count_value);
         Self::increase_active_proposal_counter();
@@ -294,6 +376,87 @@ impl<T: Trait> Module<T> {
 
         Ok(proposal_id)
     }
+
+    /// Performs all checks for the proposal creation:
+    /// - title, body lengths
+    /// - mac active proposal
+    /// - provided parameters: approval_threshold_percentage and slashing_threshold_percentage > 0
+    /// - provided stake balance and parameters.required_stake are valid
+    pub fn ensure_create_proposal_parameters_are_valid(
+        parameters: &ProposalParameters<T::BlockNumber, types::BalanceOf<T>>,
+        title: &[u8],
+        description: &[u8],
+        stake_balance: Option<types::BalanceOf<T>>,
+    ) -> DispatchResult<Error> {
+        ensure!(!title.is_empty(), Error::EmptyTitleProvided);
+        ensure!(
+            title.len() as u32 <= T::TitleMaxLength::get(),
+            Error::TitleIsTooLong
+        );
+
+        ensure!(!description.is_empty(), Error::EmptyDescriptionProvided);
+        ensure!(
+            description.len() as u32 <= T::DescriptionMaxLength::get(),
+            Error::DescriptionIsTooLong
+        );
+
+        ensure!(
+            (Self::active_proposal_count()) < T::MaxActiveProposalLimit::get(),
+            Error::MaxActiveProposalNumberExceeded
+        );
+
+        ensure!(
+            parameters.approval_threshold_percentage > 0,
+            Error::InvalidParameterApprovalThreshold
+        );
+
+        ensure!(
+            parameters.slashing_threshold_percentage > 0,
+            Error::InvalidParameterSlashingThreshold
+        );
+
+        // check stake parameters
+        if let Some(required_stake) = parameters.required_stake {
+            if let Some(staked_balance) = stake_balance {
+                ensure!(
+                    required_stake == staked_balance,
+                    Error::StakeDiffersFromRequired
+                );
+            } else {
+                return Err(Error::EmptyStake);
+            }
+        }
+
+        if stake_balance.is_some() && parameters.required_stake.is_none() {
+            return Err(Error::StakeShouldBeEmpty);
+        }
+
+        Ok(())
+    }
+
+    //TODO: candidate for invariant break or error saving to the state
+    /// Callback from StakingEventsHandler. Refunds unstaked imbalance back to the source account
+    pub fn refund_proposal_stake(stake_id: T::StakeId, imbalance: NegativeImbalance<T>) {
+        if <StakesProposals<T>>::exists(stake_id) {
+            //TODO: handle non existence
+
+            let proposal_id = Self::stakes_proposals(stake_id);
+
+            if <Proposals<T>>::exists(proposal_id) {
+                let proposal = Self::proposals(proposal_id);
+
+                if let ProposalStatus::Active(active_stake_result) = proposal.status {
+                    if let Some(active_stake) = active_stake_result {
+                        //TODO: handle the result
+                        let _ = CurrencyOf::<T>::resolve_into_existing(
+                            &active_stake.source_account_id,
+                            imbalance,
+                        );
+                    }
+                }
+            }
+        }
+    }
 }
 
 impl<T: Trait> Module<T> {
@@ -331,42 +494,38 @@ impl<T: Trait> Module<T> {
     }
 
     // Executes approved proposal code
-    fn execute_proposal(proposal_id: T::ProposalId) {
-        let mut proposal = Self::proposals(proposal_id);
+    fn execute_proposal(approved_proposal: ApprovedProposal<T>) {
+        let proposal_code = Self::proposal_codes(approved_proposal.proposal_id);
 
-        // Execute only proposals with correct status
-        if let ProposalStatus::Finalized(finalized_status) = proposal.status.clone() {
-            let proposal_code = Self::proposal_codes(proposal_id);
+        let proposal_code_result = T::DispatchableCallCode::decode(&mut &proposal_code[..]);
 
-            let proposal_code_result =
-                T::ProposalCodeDecoder::decode_proposal(proposal.proposal_type, proposal_code);
-
-            let approved_proposal_status = match proposal_code_result {
-                Ok(proposal_code) => {
-                    if let Err(error) = proposal_code.execute() {
-                        ApprovedProposalStatus::failed_execution(error)
-                    } else {
-                        ApprovedProposalStatus::Executed
-                    }
+        let approved_proposal_status = match proposal_code_result {
+            Ok(proposal_code) => {
+                if let Err(error) = proposal_code.dispatch(T::Origin::from(RawOrigin::Root)) {
+                    ApprovedProposalStatus::failed_execution(
+                        error.into().message.unwrap_or("Dispatch error"),
+                    )
+                } else {
+                    ApprovedProposalStatus::Executed
                 }
-                Err(error) => ApprovedProposalStatus::failed_execution(error),
-            };
+            }
+            Err(error) => ApprovedProposalStatus::failed_execution(error.what()),
+        };
 
-            let proposal_execution_status =
-                finalized_status.create_approved_proposal_status(approved_proposal_status);
+        let proposal_execution_status = approved_proposal
+            .finalisation_status_data
+            .create_approved_proposal_status(approved_proposal_status);
 
-            proposal.status = proposal_execution_status.clone();
-            <Proposals<T>>::insert(proposal_id, proposal);
+        let mut proposal = approved_proposal.proposal;
+        proposal.status = proposal_execution_status.clone();
+        <Proposals<T>>::insert(approved_proposal.proposal_id, proposal);
 
-            Self::deposit_event(RawEvent::ProposalStatusUpdated(
-                proposal_id,
-                proposal_execution_status,
-            ));
-        }
+        Self::deposit_event(RawEvent::ProposalStatusUpdated(
+            approved_proposal.proposal_id,
+            proposal_execution_status,
+        ));
 
-        // Remove proposals from the 'pending execution' queue even in case of not finalized status
-        // to prevent eternal cycles.
-        <PendingExecutionProposalIds<T>>::remove(&proposal_id);
+        <PendingExecutionProposalIds<T>>::remove(&approved_proposal.proposal_id);
     }
 
     // Performs all actions on proposal finalization:
@@ -381,43 +540,45 @@ impl<T: Trait> Module<T> {
 
         let mut proposal = Self::proposals(proposal_id);
 
-        if let ProposalDecisionStatus::Approved { .. } = decision_status {
-            <PendingExecutionProposalIds<T>>::insert(proposal_id, ());
-        }
-
-        // deal with stakes if necessary
-        let slash_balance = Self::calculate_slash_balance(&decision_status, &proposal.parameters);
-        let slash_and_unstake_result = Self::slash_and_unstake(proposal.stake_id, slash_balance);
+        if let ProposalStatus::Active(active_stake) = proposal.status.clone() {
+            if let ProposalDecisionStatus::Approved { .. } = decision_status {
+                <PendingExecutionProposalIds<T>>::insert(proposal_id, ());
+            }
 
-        if slash_and_unstake_result.is_ok() {
-            proposal.stake_id = None;
-        }
+            // deal with stakes if necessary
+            let slash_balance =
+                Self::calculate_slash_balance(&decision_status, &proposal.parameters);
+            let slash_and_unstake_result =
+                Self::slash_and_unstake(active_stake.clone(), slash_balance);
 
-        // create finalized proposal status with error if any
-        let new_proposal_status = //TODO rename without an error
-            ProposalStatus::finalized_with_error(decision_status, slash_and_unstake_result.err(), Self::current_block());
+            // create finalized proposal status with error if any
+            let new_proposal_status = //TODO rename without an error
+            ProposalStatus::finalized_with_error(decision_status, slash_and_unstake_result.err(), active_stake, Self::current_block());
 
-        proposal.status = new_proposal_status.clone();
-        <Proposals<T>>::insert(proposal_id, proposal);
+            proposal.status = new_proposal_status.clone();
+            <Proposals<T>>::insert(proposal_id, proposal);
 
-        Self::deposit_event(RawEvent::ProposalStatusUpdated(
-            proposal_id,
-            new_proposal_status,
-        ));
+            Self::deposit_event(RawEvent::ProposalStatusUpdated(
+                proposal_id,
+                new_proposal_status,
+            ));
+        } else {
+            print("Broken invariant: proposal cannot be non-active during the finalisation");
+        }
     }
 
     // Slashes the stake and perform unstake only in case of existing stake
     fn slash_and_unstake(
-        current_stake_id: Option<T::StakeId>,
+        current_stake_data: Option<ActiveStake<T::StakeId, T::AccountId>>,
         slash_balance: BalanceOf<T>,
     ) -> Result<(), &'static str> {
         // only if stake exists
-        if let Some(stake_id) = current_stake_id {
+        if let Some(stake_data) = current_stake_data {
             if !slash_balance.is_zero() {
-                ProposalStakeManager::<T>::slash(stake_id, slash_balance)?;
+                ProposalStakeManager::<T>::slash(stake_data.stake_id, slash_balance)?;
             }
 
-            ProposalStakeManager::<T>::remove_stake(stake_id)?;
+            ProposalStakeManager::<T>::remove_stake(stake_data.stake_id)?;
         }
 
         Ok(())
@@ -444,13 +605,22 @@ impl<T: Trait> Module<T> {
     }
 
     // Enumerates approved proposals and checks their grace period expiration
-    fn get_approved_proposal_with_expired_grace_period_ids() -> Vec<T::ProposalId> {
+    fn get_approved_proposal_with_expired_grace_period() -> Vec<ApprovedProposal<T>> {
         <PendingExecutionProposalIds<T>>::enumerate()
             .filter_map(|(proposal_id, _)| {
                 let proposal = Self::proposals(proposal_id);
 
                 if proposal.is_grace_period_expired(Self::current_block()) {
-                    Some(proposal_id)
+                    // this should be true, because it was tested inside is_grace_period_expired()
+                    if let ProposalStatus::Finalized(finalisation_data) = proposal.status.clone() {
+                        Some(ApprovedProposalData {
+                            proposal_id,
+                            proposal,
+                            finalisation_status_data: finalisation_data,
+                        })
+                    } else {
+                        None
+                    }
                 } else {
                     None
                 }
@@ -473,78 +643,33 @@ impl<T: Trait> Module<T> {
             ActiveProposalCount::put(next_active_proposal_count_value);
         };
     }
-
-    // Performs all checks for the proposal creation:
-    // - title, body lengths
-    // - mac active proposal
-    // - provided parameters: approval_threshold_percentage and slashing_threshold_percentage > 0
-    // - provided stake balance and parameters.required_stake are valid
-    fn ensure_create_proposal_parameters_are_valid(
-        parameters: &ProposalParameters<T::BlockNumber, types::BalanceOf<T>>,
-        title: &[u8],
-        body: &[u8],
-        stake_balance: Option<types::BalanceOf<T>>,
-    ) -> dispatch::Result {
-        ensure!(!title.is_empty(), errors::MSG_EMPTY_TITLE_PROVIDED);
-        ensure!(
-            title.len() as u32 <= T::TitleMaxLength::get(),
-            errors::MSG_TOO_LONG_TITLE
-        );
-
-        ensure!(!body.is_empty(), errors::MSG_EMPTY_BODY_PROVIDED);
-        ensure!(
-            body.len() as u32 <= T::DescriptionMaxLength::get(),
-            errors::MSG_TOO_LONG_BODY
-        );
-
-        ensure!(
-            (Self::active_proposal_count()) < T::MaxActiveProposalLimit::get(),
-            errors::MSG_MAX_ACTIVE_PROPOSAL_NUMBER_EXCEEDED
-        );
-
-        ensure!(
-            parameters.approval_threshold_percentage > 0,
-            errors::MSG_INVALID_PARAMETER_APPROVAL_THRESHOLD
-        );
-
-        ensure!(
-            parameters.slashing_threshold_percentage > 0,
-            errors::MSG_INVALID_PARAMETER_SLASHING_THRESHOLD
-        );
-
-        // check stake parameters
-        if let Some(required_stake) = parameters.required_stake {
-            if let Some(staked_balance) = stake_balance {
-                ensure!(
-                    required_stake == staked_balance,
-                    errors::MSG_STAKE_DIFFERS_FROM_REQUIRED
-                );
-            } else {
-                return Err(errors::MSG_STAKE_IS_EMPTY);
-            }
-        }
-
-        if stake_balance.is_some() && parameters.required_stake.is_none() {
-            return Err(errors::MSG_STAKE_SHOULD_BE_EMPTY);
-        }
-
-        Ok(())
-    }
 }
 
 // Simplification of the 'FinalizedProposalData' type
 type FinalizedProposal<T> = FinalizedProposalData<
     <T as Trait>::ProposalId,
     <T as system::Trait>::BlockNumber,
-    <T as Trait>::ProposerId,
+    MemberId<T>,
+    types::BalanceOf<T>,
+    <T as stake::Trait>::StakeId,
+    <T as system::Trait>::AccountId,
+>;
+
+// Simplification of the 'ApprovedProposalData' type
+type ApprovedProposal<T> = ApprovedProposalData<
+    <T as Trait>::ProposalId,
+    <T as system::Trait>::BlockNumber,
+    MemberId<T>,
     types::BalanceOf<T>,
     <T as stake::Trait>::StakeId,
+    <T as system::Trait>::AccountId,
 >;
 
 // Simplification of the 'Proposal' type
-type ProposalObject<T> = Proposal<
+type ProposalOf<T> = Proposal<
     <T as system::Trait>::BlockNumber,
-    <T as Trait>::ProposerId,
+    MemberId<T>,
     types::BalanceOf<T>,
     <T as stake::Trait>::StakeId,
+    <T as system::Trait>::AccountId,
 >;

+ 1 - 1
modules/proposals/engine/src/tests/mock/balance_manager.rs → runtime-modules/proposals/engine/src/tests/mock/balance_manager.rs

@@ -1,6 +1,6 @@
 #![cfg(test)]
 
-pub use runtime_primitives::traits::Zero;
+pub use sr_primitives::traits::Zero;
 use srml_support::traits::{Currency, Imbalance};
 
 use super::*;

+ 44 - 33
modules/proposals/engine/src/tests/mock/mod.rs → runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -8,17 +8,17 @@
 
 #![cfg(test)]
 pub use primitives::{Blake2Hasher, H256};
-pub use runtime_primitives::{
+pub use sr_primitives::{
     testing::{Digest, DigestItem, Header, UintAuthorityId},
     traits::{BlakeTwo256, Convert, IdentityLookup, OnFinalize, Zero},
     weights::Weight,
-    BuildStorage, Perbill,
+    BuildStorage, DispatchError, Perbill,
 };
-use srml_support::{impl_outer_dispatch, impl_outer_event, impl_outer_origin, parameter_types};
+use srml_support::{impl_outer_event, impl_outer_origin, parameter_types};
 pub use system;
 
 mod balance_manager;
-mod proposals;
+pub(crate) mod proposals;
 mod stakes;
 
 use balance_manager::*;
@@ -33,20 +33,19 @@ impl_outer_origin! {
     pub enum Origin for Test {}
 }
 
-impl_outer_dispatch! {
-    pub enum Call for Test where origin: Origin {
-        proposals::ProposalsEngine,
-    }
-}
-
 mod engine {
     pub use crate::Event;
 }
 
+mod membership_mod {
+    pub use membership::members::Event;
+}
+
 impl_outer_event! {
     pub enum TestEvent for Test {
         balances<T>,
         engine<T>,
+        membership_mod<T>,
     }
 }
 
@@ -64,15 +63,21 @@ impl balances::Trait for Test {
     /// What to do if a new account is created.
     type OnNewAccount = ();
 
-    type Event = TestEvent;
+    type TransferPayment = ();
 
     type DustRemoval = ();
-    type TransferPayment = ();
+    type Event = TestEvent;
     type ExistentialDeposit = ExistentialDeposit;
     type TransferFee = TransferFee;
     type CreationFee = CreationFee;
 }
 
+impl common::currency::GovernanceCurrency for Test {
+    type Currency = balances::Module<Self>;
+}
+
+impl proposals::Trait for Test {}
+
 impl stake::Trait for Test {
     type Currency = Balances;
     type StakePoolId = StakePoolId;
@@ -89,34 +94,42 @@ parameter_types! {
     pub const MaxActiveProposalLimit: u32 = 100;
 }
 
-impl crate::Trait for Test {
+impl membership::members::Trait for Test {
     type Event = TestEvent;
+    type MemberId = u64;
+    type PaidTermId = u64;
+    type SubscriptionId = u64;
+    type ActorId = u64;
+    type InitialMembersBalance = ();
+}
 
-    type ProposalOrigin = system::EnsureSigned<Self::AccountId>;
-
-    type VoteOrigin = system::EnsureSigned<Self::AccountId>;
-
+impl crate::Trait for Test {
+    type Event = TestEvent;
+    type ProposerOriginValidator = ();
+    type VoterOriginValidator = ();
     type TotalVotersCounter = ();
-
-    type ProposalCodeDecoder = ProposalType;
-
     type ProposalId = u32;
-
-    type ProposerId = u64;
-
-    type VoterId = u64;
-
     type StakeHandlerProvider = stakes::TestStakeHandlerProvider;
-
     type CancellationFee = CancellationFee;
-
     type RejectionFee = RejectionFee;
-
     type TitleMaxLength = TitleMaxLength;
-
     type DescriptionMaxLength = DescriptionMaxLength;
-
     type MaxActiveProposalLimit = MaxActiveProposalLimit;
+    type DispatchableCallCode = proposals::Call<Test>;
+}
+
+impl Default for proposals::Call<Test> {
+    fn default() -> Self {
+        panic!("shouldn't call default for Call");
+    }
+}
+
+impl common::origin_validator::ActorOriginValidator<Origin, u64, u64> for () {
+    fn ensure_actor_origin(origin: Origin, _account_id: u64) -> Result<u64, &'static str> {
+        let signed_account_id = system::ensure_signed(origin)?;
+
+        Ok(signed_account_id)
+    }
 }
 
 // If changing count is required, we can upgrade the implementation as shown here:
@@ -138,9 +151,9 @@ parameter_types! {
 
 impl system::Trait for Test {
     type Origin = Origin;
+    type Call = ();
     type Index = u64;
     type BlockNumber = u64;
-    type Call = ();
     type Hash = H256;
     type Hashing = BlakeTwo256;
     type AccountId = u64;
@@ -160,8 +173,6 @@ impl timestamp::Trait for Test {
     type MinimumPeriod = MinimumPeriod;
 }
 
-// TODO add a Hook type to capture TriggerElection and CouncilElected hooks
-
 pub fn initial_test_ext() -> runtime_io::TestExternalities {
     let t = system::GenesisConfig::default()
         .build_storage::<Test>()

+ 18 - 0
runtime-modules/proposals/engine/src/tests/mock/proposals.rs

@@ -0,0 +1,18 @@
+//! Contains executable proposal extrinsic mocks
+
+use rstd::prelude::*;
+use rstd::vec::Vec;
+use srml_support::decl_module;
+pub trait Trait: system::Trait {}
+
+decl_module! {
+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+        /// Working extrinsic test
+        pub fn dummy_proposal(_origin, _title: Vec<u8>, _description: Vec<u8>) {}
+
+        /// Broken extrinsic test
+        pub fn faulty_proposal(_origin, _title: Vec<u8>, _description: Vec<u8>,) {
+             Err("ExecutionFailed")?
+        }
+    }
+}

+ 0 - 0
modules/proposals/engine/src/tests/mock/stakes.rs → runtime-modules/proposals/engine/src/tests/mock/stakes.rs


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

@@ -1,12 +1,12 @@
-mod mock;
+pub(crate) mod mock;
 
 use crate::*;
 use mock::*;
 
 use codec::Encode;
 use rstd::rc::Rc;
-use runtime_primitives::traits::{OnFinalize, OnInitialize};
-use srml_support::{dispatch, StorageMap, StorageValue};
+use sr_primitives::traits::{DispatchResult, OnFinalize, OnInitialize};
+use srml_support::{StorageMap, StorageValue};
 use system::RawOrigin;
 use system::{EventRecord, Phase};
 
@@ -58,8 +58,8 @@ impl Default for ProposalParametersFixture {
 #[derive(Clone)]
 struct DummyProposalFixture {
     parameters: ProposalParameters<u64, u64>,
-    origin: RawOrigin<u64>,
-    proposal_type: u32,
+    account_id: u64,
+    proposer_id: u64,
     proposal_code: Vec<u8>,
     title: Vec<u8>,
     description: Vec<u8>,
@@ -68,10 +68,10 @@ struct DummyProposalFixture {
 
 impl Default for DummyProposalFixture {
     fn default() -> Self {
-        let dummy_proposal = DummyExecutable {
-            title: b"title".to_vec(),
-            description: b"description".to_vec(),
-        };
+        let title = b"title".to_vec();
+        let description = b"description".to_vec();
+        let dummy_proposal =
+            mock::proposals::Call::<Test>::dummy_proposal(title.clone(), description.clone());
 
         DummyProposalFixture {
             parameters: ProposalParameters {
@@ -83,11 +83,11 @@ impl Default for DummyProposalFixture {
                 grace_period: 0,
                 required_stake: None,
             },
-            origin: RawOrigin::Signed(1),
-            proposal_type: dummy_proposal.proposal_type(),
+            account_id: 1,
+            proposer_id: 1,
             proposal_code: dummy_proposal.encode(),
-            title: dummy_proposal.title,
-            description: dummy_proposal.description,
+            title,
+            description,
             stake_balance: None,
         }
     }
@@ -106,8 +106,8 @@ impl DummyProposalFixture {
         DummyProposalFixture { parameters, ..self }
     }
 
-    fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        DummyProposalFixture { origin, ..self }
+    fn with_account_id(self, account_id: u64) -> Self {
+        DummyProposalFixture { account_id, ..self }
     }
 
     fn with_stake(self, stake_balance: BalanceOf<Test>) -> Self {
@@ -117,22 +117,21 @@ impl DummyProposalFixture {
         }
     }
 
-    fn with_proposal_type_and_code(self, proposal_type: u32, proposal_code: Vec<u8>) -> Self {
+    fn with_proposal_code(self, proposal_code: Vec<u8>) -> Self {
         DummyProposalFixture {
-            proposal_type,
             proposal_code,
             ..self
         }
     }
 
-    fn create_proposal_and_assert(self, result: Result<u32, &'static str>) -> Option<u32> {
+    fn create_proposal_and_assert(self, result: Result<u32, Error>) -> Option<u32> {
         let proposal_id_result = ProposalsEngine::create_proposal(
-            self.origin.into(),
+            self.account_id,
+            self.proposer_id,
             self.parameters,
             self.title,
             self.description,
             self.stake_balance,
-            self.proposal_type,
             self.proposal_code,
         );
         assert_eq!(proposal_id_result, result);
@@ -144,6 +143,7 @@ impl DummyProposalFixture {
 struct CancelProposalFixture {
     origin: RawOrigin<u64>,
     proposal_id: u32,
+    proposer_id: u64,
 }
 
 impl CancelProposalFixture {
@@ -151,6 +151,7 @@ impl CancelProposalFixture {
         CancelProposalFixture {
             proposal_id,
             origin: RawOrigin::Signed(1),
+            proposer_id: 1,
         }
     }
 
@@ -158,9 +159,20 @@ impl CancelProposalFixture {
         CancelProposalFixture { origin, ..self }
     }
 
-    fn cancel_and_assert(self, expected_result: dispatch::Result) {
+    fn with_proposer(self, proposer_id: u64) -> Self {
+        CancelProposalFixture {
+            proposer_id,
+            ..self
+        }
+    }
+
+    fn cancel_and_assert(self, expected_result: DispatchResult<Error>) {
         assert_eq!(
-            ProposalsEngine::cancel_proposal(self.origin.into(), self.proposal_id,),
+            ProposalsEngine::cancel_proposal(
+                self.origin.into(),
+                self.proposer_id,
+                self.proposal_id
+            ),
             expected_result
         );
     }
@@ -182,7 +194,7 @@ impl VetoProposalFixture {
         VetoProposalFixture { origin, ..self }
     }
 
-    fn veto_and_assert(self, expected_result: dispatch::Result) {
+    fn veto_and_assert(self, expected_result: DispatchResult<Error>) {
         assert_eq!(
             ProposalsEngine::veto_proposal(self.origin.into(), self.proposal_id,),
             expected_result
@@ -193,6 +205,7 @@ impl VetoProposalFixture {
 struct VoteGenerator {
     proposal_id: u32,
     current_account_id: u64,
+    current_voter_id: u64,
     pub auto_increment_voter_id: bool,
 }
 
@@ -200,6 +213,7 @@ impl VoteGenerator {
     fn new(proposal_id: u32) -> Self {
         VoteGenerator {
             proposal_id,
+            current_voter_id: 0,
             current_account_id: 0,
             auto_increment_voter_id: true,
         }
@@ -208,17 +222,19 @@ impl VoteGenerator {
         self.vote_and_assert(vote_kind, Ok(()));
     }
 
-    fn vote_and_assert(&mut self, vote_kind: VoteKind, expected_result: dispatch::Result) {
+    fn vote_and_assert(&mut self, vote_kind: VoteKind, expected_result: DispatchResult<Error>) {
         assert_eq!(self.vote(vote_kind.clone()), expected_result);
     }
 
-    fn vote(&mut self, vote_kind: VoteKind) -> dispatch::Result {
+    fn vote(&mut self, vote_kind: VoteKind) -> DispatchResult<Error> {
         if self.auto_increment_voter_id {
             self.current_account_id += 1;
+            self.current_voter_id += 1;
         }
 
         ProposalsEngine::vote(
             system::RawOrigin::Signed(self.current_account_id).into(),
+            self.current_voter_id,
             self.proposal_id,
             vote_kind,
         )
@@ -227,7 +243,7 @@ impl VoteGenerator {
 
 struct EventFixture;
 impl EventFixture {
-    fn assert_events(expected_raw_events: Vec<RawEvent<u32, u64, u64, u64>>) {
+    fn assert_events(expected_raw_events: Vec<RawEvent<u32, u64, u64, u64, u64>>) {
         let expected_events = expected_raw_events
             .iter()
             .map(|ev| EventRecord {
@@ -267,15 +283,6 @@ fn create_dummy_proposal_succeeds() {
     });
 }
 
-#[test]
-fn create_dummy_proposal_fails_with_insufficient_rights() {
-    initial_test_ext().execute_with(|| {
-        let dummy_proposal = DummyProposalFixture::default().with_origin(RawOrigin::None);
-
-        dummy_proposal.create_proposal_and_assert(Err("Invalid origin"));
-    });
-}
-
 #[test]
 fn vote_succeeds() {
     initial_test_ext().execute_with(|| {
@@ -291,8 +298,8 @@ fn vote_succeeds() {
 fn vote_fails_with_insufficient_rights() {
     initial_test_ext().execute_with(|| {
         assert_eq!(
-            ProposalsEngine::vote(system::RawOrigin::None.into(), 1, VoteKind::Approve),
-            Err("Invalid origin")
+            ProposalsEngine::vote(system::RawOrigin::None.into(), 1, 1, VoteKind::Approve),
+            Err(Error::Other("RequireSignedOrigin"))
         );
     });
 }
@@ -321,7 +328,6 @@ fn proposal_execution_succeeds() {
         assert_eq!(
             proposal,
             Proposal {
-                proposal_type: 1,
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
                 created_at: 1,
@@ -334,7 +340,6 @@ fn proposal_execution_succeeds() {
                     rejections: 0,
                     slashes: 0,
                 },
-                stake_id: None,
             }
         );
 
@@ -347,11 +352,15 @@ fn proposal_execution_succeeds() {
 fn proposal_execution_failed() {
     initial_test_ext().execute_with(|| {
         let parameters_fixture = ProposalParametersFixture::default();
-        let faulty_proposal = FaultyExecutable;
+
+        let faulty_proposal = mock::proposals::Call::<Test>::faulty_proposal(
+            b"title".to_vec(),
+            b"description".to_vec(),
+        );
 
         let dummy_proposal = DummyProposalFixture::default()
             .with_parameters(parameters_fixture.params())
-            .with_proposal_type_and_code(faulty_proposal.proposal_type(), faulty_proposal.encode());
+            .with_proposal_code(faulty_proposal.encode());
 
         let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
 
@@ -368,7 +377,6 @@ fn proposal_execution_failed() {
         assert_eq!(
             proposal,
             Proposal {
-                proposal_type: faulty_proposal.proposal_type(),
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
                 created_at: 1,
@@ -384,7 +392,6 @@ fn proposal_execution_failed() {
                     rejections: 0,
                     slashes: 0,
                 },
-                stake_id: None,
             }
         )
     });
@@ -468,21 +475,21 @@ fn create_proposal_fails_with_invalid_body_or_title() {
     initial_test_ext().execute_with(|| {
         let mut dummy_proposal =
             DummyProposalFixture::default().with_title_and_body(Vec::new(), b"body".to_vec());
-        dummy_proposal.create_proposal_and_assert(Err("Proposal cannot have an empty title"));
+        dummy_proposal.create_proposal_and_assert(Err(Error::EmptyTitleProvided.into()));
 
         dummy_proposal =
             DummyProposalFixture::default().with_title_and_body(b"title".to_vec(), Vec::new());
-        dummy_proposal.create_proposal_and_assert(Err("Proposal cannot have an empty body"));
+        dummy_proposal.create_proposal_and_assert(Err(Error::EmptyDescriptionProvided.into()));
 
         let too_long_title = vec![0; 200];
         dummy_proposal =
             DummyProposalFixture::default().with_title_and_body(too_long_title, b"body".to_vec());
-        dummy_proposal.create_proposal_and_assert(Err("Title is too long"));
+        dummy_proposal.create_proposal_and_assert(Err(Error::TitleIsTooLong.into()));
 
         let too_long_body = vec![0; 11000];
         dummy_proposal =
             DummyProposalFixture::default().with_title_and_body(b"title".to_vec(), too_long_body);
-        dummy_proposal.create_proposal_and_assert(Err("Body is too long"));
+        dummy_proposal.create_proposal_and_assert(Err(Error::DescriptionIsTooLong.into()));
     });
 }
 
@@ -495,7 +502,7 @@ fn vote_fails_with_expired_voting_period() {
         run_to_block_and_finalize(6);
 
         let mut vote_generator = VoteGenerator::new(proposal_id);
-        vote_generator.vote_and_assert(VoteKind::Approve, Err("Proposal is finalized already"));
+        vote_generator.vote_and_assert(VoteKind::Approve, Err(Error::ProposalFinalized));
     });
 }
 
@@ -514,8 +521,7 @@ fn vote_fails_with_not_active_proposal() {
         run_to_block_and_finalize(2);
 
         let mut vote_generator_to_fail = VoteGenerator::new(proposal_id);
-        vote_generator_to_fail
-            .vote_and_assert(VoteKind::Approve, Err("Proposal is finalized already"));
+        vote_generator_to_fail.vote_and_assert(VoteKind::Approve, Err(Error::ProposalFinalized));
     });
 }
 
@@ -523,7 +529,7 @@ fn vote_fails_with_not_active_proposal() {
 fn vote_fails_with_absent_proposal() {
     initial_test_ext().execute_with(|| {
         let mut vote_generator = VoteGenerator::new(2);
-        vote_generator.vote_and_assert(VoteKind::Approve, Err("This proposal does not exist"));
+        vote_generator.vote_and_assert(VoteKind::Approve, Err(Error::ProposalNotFound));
     });
 }
 
@@ -537,10 +543,7 @@ fn vote_fails_on_double_voting() {
         vote_generator.auto_increment_voter_id = false;
 
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
-        vote_generator.vote_and_assert(
-            VoteKind::Approve,
-            Err("You have already voted on this proposal"),
-        );
+        vote_generator.vote_and_assert(VoteKind::Approve, Err(Error::AlreadyVoted));
     });
 }
 
@@ -560,7 +563,6 @@ fn cancel_proposal_succeeds() {
         assert_eq!(
             proposal,
             Proposal {
-                proposal_type: 1,
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
                 created_at: 1,
@@ -568,7 +570,6 @@ fn cancel_proposal_succeeds() {
                 title: b"title".to_vec(),
                 description: b"description".to_vec(),
                 voting_results: VotingResults::default(),
-                stake_id: None,
             }
         )
     });
@@ -583,7 +584,7 @@ fn cancel_proposal_fails_with_not_active_proposal() {
         run_to_block_and_finalize(6);
 
         let cancel_proposal = CancelProposalFixture::new(proposal_id);
-        cancel_proposal.cancel_and_assert(Err("Proposal is finalized already"));
+        cancel_proposal.cancel_and_assert(Err(Error::ProposalFinalized));
     });
 }
 
@@ -591,7 +592,7 @@ fn cancel_proposal_fails_with_not_active_proposal() {
 fn cancel_proposal_fails_with_not_existing_proposal() {
     initial_test_ext().execute_with(|| {
         let cancel_proposal = CancelProposalFixture::new(2);
-        cancel_proposal.cancel_and_assert(Err("This proposal does not exist"));
+        cancel_proposal.cancel_and_assert(Err(Error::ProposalNotFound));
     });
 }
 
@@ -601,9 +602,10 @@ fn cancel_proposal_fails_with_insufficient_rights() {
         let dummy_proposal = DummyProposalFixture::default();
         let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
 
-        let cancel_proposal =
-            CancelProposalFixture::new(proposal_id).with_origin(RawOrigin::Signed(2));
-        cancel_proposal.cancel_and_assert(Err("You do not own this proposal"));
+        let cancel_proposal = CancelProposalFixture::new(proposal_id)
+            .with_origin(RawOrigin::Signed(2))
+            .with_proposer(2);
+        cancel_proposal.cancel_and_assert(Err(Error::NotAuthor));
     });
 }
 
@@ -629,7 +631,6 @@ fn veto_proposal_succeeds() {
         assert_eq!(
             proposal,
             Proposal {
-                proposal_type: 1,
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
                 created_at: 1,
@@ -637,7 +638,6 @@ fn veto_proposal_succeeds() {
                 title: b"title".to_vec(),
                 description: b"description".to_vec(),
                 voting_results: VotingResults::default(),
-                stake_id: None,
             }
         );
 
@@ -655,7 +655,7 @@ fn veto_proposal_fails_with_not_active_proposal() {
         run_to_block_and_finalize(6);
 
         let veto_proposal = VetoProposalFixture::new(proposal_id);
-        veto_proposal.veto_and_assert(Err("Proposal is finalized already"));
+        veto_proposal.veto_and_assert(Err(Error::ProposalFinalized));
     });
 }
 
@@ -663,7 +663,7 @@ fn veto_proposal_fails_with_not_active_proposal() {
 fn veto_proposal_fails_with_not_existing_proposal() {
     initial_test_ext().execute_with(|| {
         let veto_proposal = VetoProposalFixture::new(2);
-        veto_proposal.veto_and_assert(Err("This proposal does not exist"));
+        veto_proposal.veto_and_assert(Err(Error::ProposalNotFound));
     });
 }
 
@@ -674,7 +674,7 @@ fn veto_proposal_fails_with_insufficient_rights() {
         let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
 
         let veto_proposal = VetoProposalFixture::new(proposal_id).with_origin(RawOrigin::Signed(2));
-        veto_proposal.veto_and_assert(Err("RequireRootOrigin"));
+        veto_proposal.veto_and_assert(Err(Error::RequireRootOrigin));
     });
 }
 
@@ -722,7 +722,8 @@ fn cancel_proposal_event_emitted() {
                 1,
                 ProposalStatus::Finalized(FinalizationData {
                     proposal_status: ProposalDecisionStatus::Canceled,
-                    finalization_error: None,
+                    encoded_unstaking_error_due_to_broken_runtime: None,
+                    stake_data_after_unstaking_error: None,
                     finalized_at: 1,
                 }),
             ),
@@ -761,7 +762,6 @@ fn create_proposal_and_expire_it() {
         assert_eq!(
             proposal,
             Proposal {
-                proposal_type: 1,
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
                 created_at: 1,
@@ -769,7 +769,6 @@ fn create_proposal_and_expire_it() {
                 title: b"title".to_vec(),
                 description: b"description".to_vec(),
                 voting_results: VotingResults::default(),
-                stake_id: None,
             }
         )
     });
@@ -803,7 +802,6 @@ fn proposal_execution_postponed_because_of_grace_period() {
         assert_eq!(
             proposal,
             Proposal {
-                proposal_type: 1,
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
                 created_at: 1,
@@ -816,7 +814,6 @@ fn proposal_execution_postponed_because_of_grace_period() {
                     rejections: 0,
                     slashes: 0,
                 },
-                stake_id: None,
             }
         );
     });
@@ -846,7 +843,6 @@ fn proposal_execution_succeeds_after_the_grace_period() {
         let mut proposal = <crate::Proposals<Test>>::get(proposal_id);
 
         let mut expected_proposal = Proposal {
-            proposal_type: 1,
             parameters: parameters_fixture.params(),
             proposer_id: 1,
             created_at: 1,
@@ -859,7 +855,6 @@ fn proposal_execution_succeeds_after_the_grace_period() {
                 rejections: 0,
                 slashes: 0,
             },
-            stake_id: None,
         };
 
         assert_eq!(proposal, expected_proposal);
@@ -890,7 +885,8 @@ fn create_proposal_fails_on_exceeding_max_active_proposals_count() {
         }
 
         let dummy_proposal = DummyProposalFixture::default();
-        dummy_proposal.create_proposal_and_assert(Err("Max active proposals number exceeded"));
+        dummy_proposal
+            .create_proposal_and_assert(Err(Error::MaxActiveProposalNumberExceeded.into()));
         // internal active proposal counter check
         assert_eq!(<ActiveProposalCount>::get(), 100);
     });
@@ -938,7 +934,7 @@ fn create_dummy_proposal_succeeds_with_stake() {
 
         let dummy_proposal = DummyProposalFixture::default()
             .with_parameters(parameters_fixture.params())
-            .with_origin(RawOrigin::Signed(account_id))
+            .with_account_id(account_id)
             .with_stake(200);
 
         let _imbalance = <Test as stake::Trait>::Currency::deposit_creating(&account_id, 500);
@@ -950,15 +946,16 @@ fn create_dummy_proposal_succeeds_with_stake() {
         assert_eq!(
             proposal,
             Proposal {
-                proposal_type: 1,
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
                 created_at: 1,
-                status: ProposalStatus::Active,
+                status: ProposalStatus::Active(Some(ActiveStake {
+                    stake_id: 0, // valid stake_id
+                    source_account_id: 1
+                })),
                 title: b"title".to_vec(),
                 description: b"description".to_vec(),
                 voting_results: VotingResults::default(),
-                stake_id: Some(0), // valid stake_id
             }
         )
     });
@@ -974,10 +971,11 @@ fn create_dummy_proposal_fail_with_stake_on_empty_account() {
             ProposalParametersFixture::default().with_required_stake(required_stake);
         let dummy_proposal = DummyProposalFixture::default()
             .with_parameters(parameters_fixture.params())
-            .with_origin(RawOrigin::Signed(account_id))
+            .with_account_id(account_id)
             .with_stake(required_stake);
 
-        dummy_proposal.create_proposal_and_assert(Err("too few free funds in account"));
+        dummy_proposal
+            .create_proposal_and_assert(Err(Error::Other("too few free funds in account")));
     });
 }
 
@@ -990,21 +988,20 @@ fn create_proposal_fais_with_invalid_stake_parameters() {
             .with_parameters(parameters_fixture.params())
             .with_stake(200);
 
-        dummy_proposal.create_proposal_and_assert(Err("Stake should be empty for this proposal"));
+        dummy_proposal.create_proposal_and_assert(Err(Error::StakeShouldBeEmpty.into()));
 
         let parameters_fixture_stake_200 = parameters_fixture.with_required_stake(200);
         dummy_proposal =
             DummyProposalFixture::default().with_parameters(parameters_fixture_stake_200.params());
 
-        dummy_proposal.create_proposal_and_assert(Err("Stake cannot be empty with this proposal"));
+        dummy_proposal.create_proposal_and_assert(Err(Error::EmptyStake.into()));
 
         let parameters_fixture_stake_300 = parameters_fixture.with_required_stake(300);
         dummy_proposal = DummyProposalFixture::default()
             .with_parameters(parameters_fixture_stake_300.params())
             .with_stake(200);
 
-        dummy_proposal
-            .create_proposal_and_assert(Err("Stake differs from the proposal requirements"));
+        dummy_proposal.create_proposal_and_assert(Err(Error::StakeDiffersFromRequired.into()));
     });
 }
 /* TODO: restore after the https://github.com/Joystream/substrate-runtime-joystream/issues/161
@@ -1114,7 +1111,7 @@ fn finalize_proposal_using_stake_mocks_succeeds() {
                 ProposalParametersFixture::default().with_required_stake(stake_amount);
             let dummy_proposal = DummyProposalFixture::default()
                 .with_parameters(parameters_fixture.params())
-                .with_origin(RawOrigin::Signed(account_id))
+                .with_account_id(account_id)
                 .with_stake(stake_amount);
 
             let _proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
@@ -1156,8 +1153,9 @@ fn proposal_slashing_succeeds() {
             proposal.status,
             ProposalStatus::Finalized(FinalizationData {
                 proposal_status: ProposalDecisionStatus::Slashed,
-                finalization_error: None,
+                encoded_unstaking_error_due_to_broken_runtime: None,
                 finalized_at: 1,
+                stake_data_after_unstaking_error: None,
             }),
         );
         assert!(!<ActiveProposalIds<Test>>::exists(proposal_id));
@@ -1197,7 +1195,7 @@ fn finalize_proposal_using_stake_mocks_failed() {
                 ProposalParametersFixture::default().with_required_stake(stake_amount);
             let dummy_proposal = DummyProposalFixture::default()
                 .with_parameters(parameters_fixture.params())
-                .with_origin(RawOrigin::Signed(account_id))
+                .with_account_id(account_id)
                 .with_stake(stake_amount);
 
             let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
@@ -1208,19 +1206,21 @@ fn finalize_proposal_using_stake_mocks_failed() {
             assert_eq!(
                 proposal,
                 Proposal {
-                    proposal_type: 1,
                     parameters: parameters_fixture.params(),
                     proposer_id: 1,
                     created_at: 1,
                     status: ProposalStatus::finalized_with_error(
                         ProposalDecisionStatus::Expired,
                         Some("Cannot remove stake"),
+                        Some(ActiveStake {
+                            stake_id: 1,
+                            source_account_id: 1
+                        }),
                         4,
                     ),
                     title: b"title".to_vec(),
                     description: b"description".to_vec(),
                     voting_results: VotingResults::default(),
-                    stake_id: Some(1),
                 }
             );
         });

+ 52 - 16
modules/proposals/engine/src/types/mod.rs → runtime-modules/proposals/engine/src/types/mod.rs

@@ -111,13 +111,21 @@ impl VotingResults {
     }
 }
 
+/// Contains created stake id and source account for the stake balance
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, Copy, PartialEq, Eq, Debug)]
+pub struct ActiveStake<StakeId, AccountId> {
+    /// Created stake id for the proposal
+    pub stake_id: StakeId,
+
+    /// Source account of the stake balance. Refund if any will be provided using this account
+    pub source_account_id: AccountId,
+}
+
 /// 'Proposal' contains information necessary for the proposal system functioning.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct Proposal<BlockNumber, ProposerId, Balance, StakeId> {
-    /// Proposal type id
-    pub proposal_type: u32,
-
+pub struct Proposal<BlockNumber, ProposerId, Balance, StakeId, AccountId> {
     /// Proposals parameter, characterize different proposal types.
     pub parameters: ProposalParameters<BlockNumber, Balance>,
 
@@ -134,18 +142,18 @@ pub struct Proposal<BlockNumber, ProposerId, Balance, StakeId> {
     pub created_at: BlockNumber,
 
     /// Current proposal status
-    pub status: ProposalStatus<BlockNumber>,
+    pub status: ProposalStatus<BlockNumber, StakeId, AccountId>,
 
     /// Curring voting result for the proposal
     pub voting_results: VotingResults,
-
-    /// Created stake id for the proposal
-    pub stake_id: Option<StakeId>,
 }
 
-impl<BlockNumber, ProposerId, Balance, StakeId> Proposal<BlockNumber, ProposerId, Balance, StakeId>
+impl<BlockNumber, ProposerId, Balance, StakeId, AccountId>
+    Proposal<BlockNumber, ProposerId, Balance, StakeId, AccountId>
 where
     BlockNumber: Add<Output = BlockNumber> + PartialOrd + Copy,
+    StakeId: Clone,
+    AccountId: Clone,
 {
     /// Returns whether voting period expired by now
     pub fn is_voting_period_expired(&self, now: BlockNumber) -> bool {
@@ -211,8 +219,8 @@ pub trait VotersParameters {
 }
 
 // Calculates quorum, votes threshold, expiration status
-struct ProposalStatusResolution<'a, BlockNumber, ProposerId, Balance, StakeId> {
-    proposal: &'a Proposal<BlockNumber, ProposerId, Balance, StakeId>,
+struct ProposalStatusResolution<'a, BlockNumber, ProposerId, Balance, StakeId, AccountId> {
+    proposal: &'a Proposal<BlockNumber, ProposerId, Balance, StakeId, AccountId>,
     now: BlockNumber,
     votes_count: u32,
     total_voters_count: u32,
@@ -220,10 +228,12 @@ struct ProposalStatusResolution<'a, BlockNumber, ProposerId, Balance, StakeId> {
     slashes: u32,
 }
 
-impl<'a, BlockNumber, ProposerId, Balance, StakeId>
-    ProposalStatusResolution<'a, BlockNumber, ProposerId, Balance, StakeId>
+impl<'a, BlockNumber, ProposerId, Balance, StakeId, AccountId>
+    ProposalStatusResolution<'a, BlockNumber, ProposerId, Balance, StakeId, AccountId>
 where
     BlockNumber: Add<Output = BlockNumber> + PartialOrd + Copy,
+    StakeId: Clone,
+    AccountId: Clone,
 {
     // Proposal has been expired and quorum not reached.
     pub fn is_expired(&self) -> bool {
@@ -307,12 +317,19 @@ pub type NegativeImbalance<T> =
 pub type CurrencyOf<T> = <T as stake::Trait>::Currency;
 
 /// Data container for the finalized proposal results
-pub(crate) struct FinalizedProposalData<ProposalId, BlockNumber, ProposerId, Balance, StakeId> {
+pub(crate) struct FinalizedProposalData<
+    ProposalId,
+    BlockNumber,
+    ProposerId,
+    Balance,
+    StakeId,
+    AccountId,
+> {
     /// Proposal id
     pub proposal_id: ProposalId,
 
     /// Proposal to be finalized
-    pub proposal: Proposal<BlockNumber, ProposerId, Balance, StakeId>,
+    pub proposal: Proposal<BlockNumber, ProposerId, Balance, StakeId, AccountId>,
 
     /// Proposal finalization status
     pub status: ProposalDecisionStatus,
@@ -321,12 +338,31 @@ pub(crate) struct FinalizedProposalData<ProposalId, BlockNumber, ProposerId, Bal
     pub finalized_at: BlockNumber,
 }
 
+/// Data container for the approved proposal results
+pub(crate) struct ApprovedProposalData<
+    ProposalId,
+    BlockNumber,
+    ProposerId,
+    Balance,
+    StakeId,
+    AccountId,
+> {
+    /// Proposal id
+    pub proposal_id: ProposalId,
+
+    /// Proposal to be finalized
+    pub proposal: Proposal<BlockNumber, ProposerId, Balance, StakeId, AccountId>,
+
+    /// Proposal finalisation status data
+    pub finalisation_status_data: FinalizationData<BlockNumber, StakeId, AccountId>,
+}
+
 #[cfg(test)]
 mod tests {
     use crate::*;
 
     // Alias introduced for simplicity of changing Proposal exact types.
-    type ProposalObject = Proposal<u64, u64, u64, u64>;
+    type ProposalObject = Proposal<u64, u64, u64, u64, u64>;
 
     #[test]
     fn proposal_voting_period_expired() {

+ 27 - 19
modules/proposals/engine/src/types/proposal_statuses.rs → runtime-modules/proposals/engine/src/types/proposal_statuses.rs

@@ -1,45 +1,49 @@
 use codec::{Decode, Encode};
 use rstd::prelude::*;
 
+use crate::ActiveStake;
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 
 /// Current status of the proposal
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub enum ProposalStatus<BlockNumber> {
-    /// A new proposal that is available for voting.
-    Active,
+pub enum ProposalStatus<BlockNumber, StakeId, AccountId> {
+    /// A new proposal status that is available for voting (with optional stake data).
+    Active(Option<ActiveStake<StakeId, AccountId>>),
 
     /// The proposal decision was made.
-    Finalized(FinalizationData<BlockNumber>),
+    Finalized(FinalizationData<BlockNumber, StakeId, AccountId>),
 }
 
-impl<BlockNumber> Default for ProposalStatus<BlockNumber> {
+impl<BlockNumber, StakeId, AccountId> Default for ProposalStatus<BlockNumber, StakeId, AccountId> {
     fn default() -> Self {
-        ProposalStatus::Active
+        ProposalStatus::Active(None)
     }
 }
 
-impl<BlockNumber> ProposalStatus<BlockNumber> {
+impl<BlockNumber, StakeId, AccountId> ProposalStatus<BlockNumber, StakeId, AccountId> {
     /// Creates finalized proposal status with provided ProposalDecisionStatus
     pub fn finalized(
         decision_status: ProposalDecisionStatus,
         now: BlockNumber,
-    ) -> ProposalStatus<BlockNumber> {
-        Self::finalized_with_error(decision_status, None, now)
+    ) -> ProposalStatus<BlockNumber, StakeId, AccountId> {
+        Self::finalized_with_error(decision_status, None, None, now)
     }
 
     /// Creates finalized proposal status with provided ProposalDecisionStatus and error
     pub fn finalized_with_error(
         decision_status: ProposalDecisionStatus,
-        finalization_error: Option<&str>,
+        encoded_unstaking_error_due_to_broken_runtime: Option<&str>,
+        active_stake: Option<ActiveStake<StakeId, AccountId>>,
         now: BlockNumber,
-    ) -> ProposalStatus<BlockNumber> {
+    ) -> ProposalStatus<BlockNumber, StakeId, AccountId> {
         ProposalStatus::Finalized(FinalizationData {
             proposal_status: decision_status,
-            finalization_error: finalization_error.map(|err| err.as_bytes().to_vec()),
+            encoded_unstaking_error_due_to_broken_runtime:
+                encoded_unstaking_error_due_to_broken_runtime.map(|err| err.as_bytes().to_vec()),
             finalized_at: now,
+            stake_data_after_unstaking_error: active_stake,
         })
     }
 
@@ -47,11 +51,12 @@ impl<BlockNumber> ProposalStatus<BlockNumber> {
     pub fn approved(
         approved_status: ApprovedProposalStatus,
         now: BlockNumber,
-    ) -> ProposalStatus<BlockNumber> {
+    ) -> ProposalStatus<BlockNumber, StakeId, AccountId> {
         ProposalStatus::Finalized(FinalizationData {
             proposal_status: ProposalDecisionStatus::Approved(approved_status),
-            finalization_error: None,
+            encoded_unstaking_error_due_to_broken_runtime: None,
             finalized_at: now,
+            stake_data_after_unstaking_error: None,
         })
     }
 }
@@ -59,23 +64,26 @@ impl<BlockNumber> ProposalStatus<BlockNumber> {
 /// Final proposal status and potential error.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub struct FinalizationData<BlockNumber> {
+pub struct FinalizationData<BlockNumber, StakeId, AccountId> {
     /// Final proposal status
     pub proposal_status: ProposalDecisionStatus,
 
     /// Proposal finalization block number
     pub finalized_at: BlockNumber,
 
-    /// Error occured during the proposal finalization
-    pub finalization_error: Option<Vec<u8>>,
+    /// Error occured during the proposal finalization - unstaking failed in the stake module
+    pub encoded_unstaking_error_due_to_broken_runtime: Option<Vec<u8>>,
+
+    /// Stake data for the proposal, filled if the unstaking wasn't successful
+    pub stake_data_after_unstaking_error: Option<ActiveStake<StakeId, AccountId>>,
 }
 
-impl<BlockNumber> FinalizationData<BlockNumber> {
+impl<BlockNumber, StakeId, AccountId> FinalizationData<BlockNumber, StakeId, AccountId> {
     /// FinalizationData helper, creates ApprovedProposalStatus
     pub fn create_approved_proposal_status(
         self,
         approved_status: ApprovedProposalStatus,
-    ) -> ProposalStatus<BlockNumber> {
+    ) -> ProposalStatus<BlockNumber, StakeId, AccountId> {
         ProposalStatus::Finalized(FinalizationData {
             proposal_status: ProposalDecisionStatus::Approved(approved_status),
             ..self

+ 2 - 1
modules/proposals/engine/src/types/stakes.rs → runtime-modules/proposals/engine/src/types/stakes.rs

@@ -3,7 +3,7 @@ use crate::Trait;
 use rstd::convert::From;
 use rstd::marker::PhantomData;
 use rstd::rc::Rc;
-use runtime_primitives::traits::Zero;
+use sr_primitives::traits::Zero;
 use srml_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
 
 // Mocking dependencies for testing
@@ -153,6 +153,7 @@ impl<T: Trait> ProposalStakeManager<T> {
     pub fn remove_stake(stake_id: T::StakeId) -> Result<(), &'static str> {
         T::StakeHandlerProvider::stakes().unstake(stake_id)?;
 
+        //TODO: can't remove stake before refunding
         T::StakeHandlerProvider::stakes().remove_stake(stake_id)?;
 
         Ok(())

+ 18 - 0
runtime/Cargo.toml

@@ -353,3 +353,21 @@ default_features = false
 package = 'substrate-storage-module'
 path = '../runtime-modules/storage'
 version = '1.0.0'
+
+[dependencies.proposals_engine]
+default_features = false
+package = 'substrate-proposals-engine-module'
+path = '../runtime-modules/proposals/engine'
+version = '2.0.0'
+
+[dependencies.proposals_discussion]
+default_features = false
+package = 'substrate-proposals-discussion-module'
+path = '../runtime-modules/proposals/discussion'
+version = '2.0.0'
+
+[dependencies.proposals_codex]
+default_features = false
+package = 'substrate-proposals-codex-module'
+path = '../runtime-modules/proposals/codex'
+version = '2.0.0'

+ 13 - 8
runtime/build.rs

@@ -15,7 +15,7 @@
 // along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
 
 use std::{env, process::Command, string::String};
-use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource};
+use wasm_builder_runner::{WasmBuilder, WasmBuilderSource};
 
 fn main() {
     if !in_real_cargo_environment() {
@@ -23,13 +23,18 @@ fn main() {
         println!("Building DUMMY Wasm binary");
     }
 
-    build_current_project_with_rustflags(
-        "wasm_binary.rs",
-        WasmBuilderSource::Crates("1.0.8"),
-        // This instructs LLD to export __heap_base as a global variable, which is used by the
-        // external memory allocator.
-        "-Clink-arg=--export=__heap_base",
-    );
+    let file_name = "wasm_binary.rs";
+    let wasm_builder_source = WasmBuilderSource::Crates("1.0.8");
+    // This instructs LLD to export __heap_base as a global variable, which is used by the
+    // external memory allocator.
+    let default_rust_flags = "-Clink-arg=--export=__heap_base";
+
+    WasmBuilder::new()
+        .with_current_project()
+        .with_wasm_builder_source(wasm_builder_source)
+        .append_to_rust_flags(default_rust_flags)
+        .set_file_name(file_name)
+        .build()
 }
 
 fn in_real_cargo_environment() -> bool {

+ 1 - 0
runtime/src/integration/mod.rs

@@ -0,0 +1 @@
+pub mod proposals;

+ 206 - 0
runtime/src/integration/proposals/council_origin_validator.rs

@@ -0,0 +1,206 @@
+use rstd::marker::PhantomData;
+
+use common::origin_validator::ActorOriginValidator;
+use proposals_engine::VotersParameters;
+
+use super::{MemberId, MembershipOriginValidator};
+
+/// Handles work with the council.
+/// Provides implementations for ActorOriginValidator and VotersParameters.
+pub struct CouncilManager<T> {
+    marker: PhantomData<T>,
+}
+
+impl<T: governance::council::Trait + membership::members::Trait>
+    ActorOriginValidator<<T as system::Trait>::Origin, MemberId<T>, <T as system::Trait>::AccountId>
+    for CouncilManager<T>
+{
+    /// Check for valid combination of origin and actor_id. Actor_id should be valid member_id of
+    /// the membership module
+    fn ensure_actor_origin(
+        origin: <T as system::Trait>::Origin,
+        actor_id: MemberId<T>,
+    ) -> Result<<T as system::Trait>::AccountId, &'static str> {
+        let account_id = <MembershipOriginValidator<T>>::ensure_actor_origin(origin, actor_id)?;
+
+        if <governance::council::Module<T>>::is_councilor(&account_id) {
+            return Ok(account_id);
+        }
+
+        Err("Council validation failed: account id doesn't belong to a council member")
+    }
+}
+
+impl<T: governance::council::Trait> VotersParameters for CouncilManager<T> {
+    /// Implement total_voters_count() as council size
+    fn total_voters_count() -> u32 {
+        <governance::council::Module<T>>::active_council().len() as u32
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::CouncilManager;
+    use crate::Runtime;
+    use common::origin_validator::ActorOriginValidator;
+    use membership::members::UserInfo;
+    use proposals_engine::VotersParameters;
+    use sr_primitives::AccountId32;
+    use system::RawOrigin;
+
+    type Council = governance::council::Module<Runtime>;
+
+    fn initial_test_ext() -> runtime_io::TestExternalities {
+        let t = system::GenesisConfig::default()
+            .build_storage::<Runtime>()
+            .unwrap();
+
+        t.into()
+    }
+
+    type Membership = membership::members::Module<Runtime>;
+
+    #[test]
+    fn council_origin_validator_fails_with_unregistered_member() {
+        initial_test_ext().execute_with(|| {
+            let origin = RawOrigin::Signed(AccountId32::default());
+            let member_id = 1;
+            let error = "Membership validation failed: cannot find a profile for a member";
+
+            let validation_result =
+                CouncilManager::<Runtime>::ensure_actor_origin(origin.into(), member_id);
+
+            assert_eq!(validation_result, Err(error));
+        });
+    }
+
+    #[test]
+    fn council_origin_validator_succeeds() {
+        initial_test_ext().execute_with(|| {
+            let councilor1 = AccountId32::default();
+            let councilor2: [u8; 32] = [2; 32];
+            let councilor3: [u8; 32] = [3; 32];
+
+            assert!(Council::set_council(
+                system::RawOrigin::Root.into(),
+                vec![councilor1, councilor2.into(), councilor3.into()]
+            )
+            .is_ok());
+
+            let account_id = AccountId32::default();
+            let origin = RawOrigin::Signed(account_id.clone());
+            let authority_account_id = AccountId32::default();
+            Membership::set_screening_authority(
+                RawOrigin::Root.into(),
+                authority_account_id.clone(),
+            )
+            .unwrap();
+
+            Membership::add_screened_member(
+                RawOrigin::Signed(authority_account_id).into(),
+                account_id.clone(),
+                UserInfo {
+                    handle: Some(b"handle".to_vec()),
+                    avatar_uri: None,
+                    about: None,
+                },
+            )
+            .unwrap();
+            let member_id = 0; // newly created member_id
+
+            let validation_result =
+                CouncilManager::<Runtime>::ensure_actor_origin(origin.into(), member_id);
+
+            assert_eq!(validation_result, Ok(account_id));
+        });
+    }
+
+    #[test]
+    fn council_origin_validator_fails_with_incompatible_account_id_and_member_id() {
+        initial_test_ext().execute_with(|| {
+            let account_id = AccountId32::default();
+            let error =
+                "Membership validation failed: given account doesn't match with profile accounts";
+            let authority_account_id = AccountId32::default();
+            Membership::set_screening_authority(
+                RawOrigin::Root.into(),
+                authority_account_id.clone(),
+            )
+            .unwrap();
+
+            Membership::add_screened_member(
+                RawOrigin::Signed(authority_account_id).into(),
+                account_id.clone(),
+                UserInfo {
+                    handle: Some(b"handle".to_vec()),
+                    avatar_uri: None,
+                    about: None,
+                },
+            )
+            .unwrap();
+            let member_id = 0; // newly created member_id
+
+            let invalid_account_id: [u8; 32] = [2; 32];
+            let validation_result = CouncilManager::<Runtime>::ensure_actor_origin(
+                RawOrigin::Signed(invalid_account_id.into()).into(),
+                member_id,
+            );
+
+            assert_eq!(validation_result, Err(error));
+        });
+    }
+
+    #[test]
+    fn council_origin_validator_fails_with_not_council_account_id() {
+        initial_test_ext().execute_with(|| {
+            let account_id = AccountId32::default();
+            let origin = RawOrigin::Signed(account_id.clone());
+            let error = "Council validation failed: account id doesn't belong to a council member";
+            let authority_account_id = AccountId32::default();
+            Membership::set_screening_authority(
+                RawOrigin::Root.into(),
+                authority_account_id.clone(),
+            )
+            .unwrap();
+
+            Membership::add_screened_member(
+                RawOrigin::Signed(authority_account_id).into(),
+                account_id,
+                UserInfo {
+                    handle: Some(b"handle".to_vec()),
+                    avatar_uri: None,
+                    about: None,
+                },
+            )
+            .unwrap();
+            let member_id = 0; // newly created member_id
+
+            let validation_result =
+                CouncilManager::<Runtime>::ensure_actor_origin(origin.into(), member_id);
+
+            assert_eq!(validation_result, Err(error));
+        });
+    }
+
+    #[test]
+    fn council_size_calculation_aka_total_voters_count_succeeds() {
+        initial_test_ext().execute_with(|| {
+            let councilor1 = AccountId32::default();
+            let councilor2: [u8; 32] = [2; 32];
+            let councilor3: [u8; 32] = [3; 32];
+            let councilor4: [u8; 32] = [4; 32];
+            assert!(Council::set_council(
+                system::RawOrigin::Root.into(),
+                vec![
+                    councilor1,
+                    councilor2.into(),
+                    councilor3.into(),
+                    councilor4.into()
+                ]
+            )
+            .is_ok());
+
+            assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 4)
+        });
+    }
+}

+ 141 - 0
runtime/src/integration/proposals/membership_origin_validator.rs

@@ -0,0 +1,141 @@
+use rstd::marker::PhantomData;
+
+use common::origin_validator::ActorOriginValidator;
+use system::ensure_signed;
+
+/// Member of the Joystream organization
+pub type MemberId<T> = <T as crate::members::Trait>::MemberId;
+
+/// Default membership actor origin validator.
+pub struct MembershipOriginValidator<T> {
+    marker: PhantomData<T>,
+}
+
+impl<T: crate::members::Trait>
+    ActorOriginValidator<<T as system::Trait>::Origin, MemberId<T>, <T as system::Trait>::AccountId>
+    for MembershipOriginValidator<T>
+{
+    /// Check for valid combination of origin and actor_id. Actor_id should be valid member_id of
+    /// the membership module
+    fn ensure_actor_origin(
+        origin: <T as system::Trait>::Origin,
+        actor_id: MemberId<T>,
+    ) -> Result<<T as system::Trait>::AccountId, &'static str> {
+        // check valid signed account_id
+        let account_id = ensure_signed(origin)?;
+
+        // check whether actor_id belongs to the registered member
+        let profile_result = <crate::members::Module<T>>::ensure_profile(actor_id);
+
+        if let Ok(profile) = profile_result {
+            // whether the account_id belongs to the actor
+            if profile.controller_account == account_id {
+                return Ok(account_id);
+            } else {
+                return Err("Membership validation failed: given account doesn't match with profile accounts");
+            }
+        }
+
+        Err("Membership validation failed: cannot find a profile for a member")
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::MembershipOriginValidator;
+    use crate::Runtime;
+    use common::origin_validator::ActorOriginValidator;
+    use membership::members::UserInfo;
+    use sr_primitives::AccountId32;
+    use system::RawOrigin;
+
+    type Membership = crate::members::Module<Runtime>;
+
+    fn initial_test_ext() -> runtime_io::TestExternalities {
+        let t = system::GenesisConfig::default()
+            .build_storage::<Runtime>()
+            .unwrap();
+
+        t.into()
+    }
+
+    #[test]
+    fn membership_origin_validator_fails_with_unregistered_member() {
+        initial_test_ext().execute_with(|| {
+            let origin = RawOrigin::Signed(AccountId32::default());
+            let member_id = 1;
+            let error = "Membership validation failed: cannot find a profile for a member";
+
+            let validation_result =
+                MembershipOriginValidator::<Runtime>::ensure_actor_origin(origin.into(), member_id);
+
+            assert_eq!(validation_result, Err(error));
+        });
+    }
+
+    #[test]
+    fn membership_origin_validator_succeeds() {
+        initial_test_ext().execute_with(|| {
+            let account_id = AccountId32::default();
+            let origin = RawOrigin::Signed(account_id.clone());
+            let authority_account_id = AccountId32::default();
+            Membership::set_screening_authority(
+                RawOrigin::Root.into(),
+                authority_account_id.clone(),
+            )
+            .unwrap();
+
+            Membership::add_screened_member(
+                RawOrigin::Signed(authority_account_id).into(),
+                account_id.clone(),
+                UserInfo {
+                    handle: Some(b"handle".to_vec()),
+                    avatar_uri: None,
+                    about: None,
+                },
+            )
+            .unwrap();
+            let member_id = 0; // newly created member_id
+
+            let validation_result =
+                MembershipOriginValidator::<Runtime>::ensure_actor_origin(origin.into(), member_id);
+
+            assert_eq!(validation_result, Ok(account_id));
+        });
+    }
+
+    #[test]
+    fn membership_origin_validator_fails_with_incompatible_account_id_and_member_id() {
+        initial_test_ext().execute_with(|| {
+            let account_id = AccountId32::default();
+            let error =
+                "Membership validation failed: given account doesn't match with profile accounts";
+            let authority_account_id = AccountId32::default();
+            Membership::set_screening_authority(
+                RawOrigin::Root.into(),
+                authority_account_id.clone(),
+            )
+            .unwrap();
+
+            Membership::add_screened_member(
+                RawOrigin::Signed(authority_account_id).into(),
+                account_id,
+                UserInfo {
+                    handle: Some(b"handle".to_vec()),
+                    avatar_uri: None,
+                    about: None,
+                },
+            )
+            .unwrap();
+            let member_id = 0; // newly created member_id
+
+            let invalid_account_id: [u8; 32] = [2; 32];
+            let validation_result = MembershipOriginValidator::<Runtime>::ensure_actor_origin(
+                RawOrigin::Signed(invalid_account_id.into()).into(),
+                member_id,
+            );
+
+            assert_eq!(validation_result, Err(error));
+        });
+    }
+}

+ 7 - 0
runtime/src/integration/proposals/mod.rs

@@ -0,0 +1,7 @@
+mod council_origin_validator;
+mod membership_origin_validator;
+mod staking_events_handler;
+
+pub use council_origin_validator::CouncilManager;
+pub use membership_origin_validator::{MemberId, MembershipOriginValidator};
+pub use staking_events_handler::StakingEventsHandler;

+ 47 - 0
runtime/src/integration/proposals/staking_events_handler.rs

@@ -0,0 +1,47 @@
+use rstd::marker::PhantomData;
+use srml_support::traits::{Currency, Imbalance};
+use srml_support::StorageMap;
+
+// Balance alias
+type BalanceOf<T> =
+    <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
+
+// Balance alias for staking
+type NegativeImbalance<T> =
+    <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
+
+/// Proposal implementation of the staking event handler from the stake module.
+/// 'marker' responsible for the 'Trait' binding.
+pub struct StakingEventsHandler<T> {
+    pub marker: PhantomData<T>,
+}
+
+impl<T: stake::Trait + proposals_engine::Trait> stake::StakingEventsHandler<T>
+    for StakingEventsHandler<T>
+{
+    /// Unstake remaining sum back to the source_account_id
+    fn unstaked(
+        id: &<T as stake::Trait>::StakeId,
+        _unstaked_amount: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        if <proposals_engine::StakesProposals<T>>::exists(id) {
+            <proposals_engine::Module<T>>::refund_proposal_stake(*id, remaining_imbalance);
+
+            return <NegativeImbalance<T>>::zero(); // imbalance was consumed
+        }
+
+        remaining_imbalance
+    }
+
+    /// Empty handler for slashing
+    fn slashed(
+        _: &<T as stake::Trait>::StakeId,
+        _: &<T as stake::Trait>::SlashId,
+        _: BalanceOf<T>,
+        _: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        remaining_imbalance
+    }
+}

+ 74 - 7
runtime/src/lib.rs

@@ -3,6 +3,9 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
 #![recursion_limit = "256"]
+// srml_staking_reward_curve::build! - substrate macro produces a warning.
+// TODO: remove after post-Rome substrate upgrade
+#![allow(array_into_iter)]
 
 // Make the WASM binary available.
 // This is required only by the node build.
@@ -10,6 +13,8 @@
 #[cfg(feature = "std")]
 include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
 
+mod integration;
+
 use authority_discovery_primitives::{
     AuthorityId as EncodedAuthorityId, Signature as EncodedSignature,
 };
@@ -51,6 +56,8 @@ pub use srml_support::{
 pub use staking::StakerStatus;
 pub use timestamp::Call as TimestampCall;
 
+use integration::proposals::{CouncilManager, MembershipOriginValidator};
+
 /// An index to a block.
 pub type BlockNumber = u32;
 
@@ -396,7 +403,7 @@ impl finality_tracker::Trait for Runtime {
 }
 
 pub use forum;
-use governance::{council, election, proposals};
+use governance::{council, election};
 use membership::members;
 use storage::{data_directory, data_object_storage_registry, data_object_type_registry};
 pub use versioned_store;
@@ -577,7 +584,10 @@ parameter_types! {
 impl stake::Trait for Runtime {
     type Currency = <Self as common::currency::GovernanceCurrency>::Currency;
     type StakePoolId = StakePoolId;
-    type StakingEventsHandler = ContentWorkingGroupStakingEventHandler;
+    type StakingEventsHandler = (
+        ContentWorkingGroupStakingEventHandler,
+        crate::integration::proposals::StakingEventsHandler<Self>,
+    );
     type StakeId = u64;
     type SlashId = u64;
 }
@@ -657,10 +667,6 @@ impl common::currency::GovernanceCurrency for Runtime {
     type Currency = balances::Module<Self>;
 }
 
-impl governance::proposals::Trait for Runtime {
-    type Event = Event;
-}
-
 impl governance::election::Trait for Runtime {
     type Event = Event;
     type CouncilElected = (Council,);
@@ -801,6 +807,63 @@ impl discovery::Trait for Runtime {
     type Roles = LookupRoles;
 }
 
+parameter_types! {
+    pub const ProposalCancellationFee: u64 = 5;
+    pub const ProposalRejectionFee: u64 = 3;
+    pub const ProposalTitleMaxLength: u32 = 100;
+    pub const ProposalDescriptionMaxLength: u32 = 10000;
+    pub const ProposalMaxActiveProposalLimit: u32 = 100;
+}
+
+impl proposals_engine::Trait for Runtime {
+    type Event = Event;
+    type ProposerOriginValidator = MembershipOriginValidator<Self>;
+    type VoterOriginValidator = CouncilManager<Self>;
+    type TotalVotersCounter = CouncilManager<Self>;
+    type ProposalId = u32;
+    type StakeHandlerProvider = proposals_engine::DefaultStakeHandlerProvider;
+    type CancellationFee = ProposalCancellationFee;
+    type RejectionFee = ProposalRejectionFee;
+    type TitleMaxLength = ProposalTitleMaxLength;
+    type DescriptionMaxLength = ProposalDescriptionMaxLength;
+    type MaxActiveProposalLimit = ProposalMaxActiveProposalLimit;
+    type DispatchableCallCode = Call;
+}
+impl Default for Call {
+    fn default() -> Self {
+        panic!("shouldn't call default for Call");
+    }
+}
+
+parameter_types! {
+    pub const ProposalMaxPostEditionNumber: u32 = 5;
+    pub const ProposalMaxThreadInARowNumber: u32 = 3;
+    pub const ProposalThreadTitleLengthLimit: u32 = 200;
+    pub const ProposalPostLengthLimit: u32 = 2000;
+}
+
+impl proposals_discussion::Trait for Runtime {
+    type Event = Event;
+    type PostAuthorOriginValidator = MembershipOriginValidator<Self>;
+    type ThreadId = u32;
+    type PostId = u32;
+    type MaxPostEditionNumber = ProposalMaxPostEditionNumber;
+    type ThreadTitleLengthLimit = ProposalThreadTitleLengthLimit;
+    type PostLengthLimit = ProposalPostLengthLimit;
+    type MaxThreadInARowNumber = ProposalMaxThreadInARowNumber;
+}
+
+parameter_types! {
+    pub const TextProposalMaxLength: u32 = 60_000;
+    pub const RuntimeUpgradeWasmProposalMaxLength: u32 = 2_000_000;
+}
+
+impl proposals_codex::Trait for Runtime {
+    type MembershipOriginValidator = MembershipOriginValidator<Self>;
+    type TextProposalMaxLength = TextProposalMaxLength;
+    type RuntimeUpgradeWasmProposalMaxLength = RuntimeUpgradeWasmProposalMaxLength;
+}
+
 construct_runtime!(
 	pub enum Runtime where
 		Block = Block,
@@ -825,7 +888,6 @@ construct_runtime!(
         RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage},
         Sudo: sudo,
         // Joystream
-        Proposals: proposals::{Module, Call, Storage, Event<T>, Config<T>},
         CouncilElection: election::{Module, Call, Storage, Event<T>, Config<T>},
         Council: council::{Module, Call, Storage, Event<T>, Config<T>},
         Memo: memo::{Module, Call, Storage, Event<T>},
@@ -844,6 +906,11 @@ construct_runtime!(
         RecurringRewards: recurringrewards::{Module, Call, Storage},
         Hiring: hiring::{Module, Call, Storage},
         ContentWorkingGroup: content_wg::{Module, Call, Storage, Event<T>, Config<T>},
+        // --- Proposals
+        ProposalsEngine: proposals_engine::{Module, Call, Storage, Event<T>},
+        ProposalsDiscussion: proposals_discussion::{Module, Call, Storage, Event<T>},
+        ProposalsCodex: proposals_codex::{Module, Call, Storage, Error},
+        // ---
 	}
 );