Преглед на файлове

Add CouncilElected hook for the proposals

- add CouncilElected hook for the proposals
- remove unused error.rs
- add some comments
- add reset_active_proposals() and tests
- add runtime integration test proposal_reset_succeeds()
Shamil Gadelshin преди 5 години
родител
ревизия
51628fbed4

+ 13 - 0
runtime-modules/governance/src/election.rs

@@ -73,6 +73,19 @@ impl<Elected, Term, X: CouncilElected<Elected, Term>> CouncilElected<Elected, Te
         X::council_elected(new_council, term);
     }
 }
+// Chain of handlers.
+impl<
+        Elected: Clone,
+        Term: Clone,
+        X: CouncilElected<Elected, Term>,
+        Y: CouncilElected<Elected, Term>,
+    > CouncilElected<Elected, Term> for (X, Y)
+{
+    fn council_elected(new_council: Elected, term: Term) {
+        X::council_elected(new_council.clone(), term.clone());
+        Y::council_elected(new_council, term);
+    }
+}
 
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
 #[derive(Clone, Copy, Encode, Decode, Default)]

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

@@ -1,14 +0,0 @@
-pub const MSG_EMPTY_TITLE_PROVIDED: &str = "Proposal cannot have an empty title";
-pub const MSG_EMPTY_BODY_PROVIDED: &str = "Proposal cannot have an empty body";
-pub const MSG_TOO_LONG_TITLE: &str = "Title is too long";
-pub const MSG_TOO_LONG_BODY: &str = "Body is too long";
-pub const MSG_PROPOSAL_NOT_FOUND: &str = "This proposal does not exist";
-pub const MSG_PROPOSAL_FINALIZED: &str = "Proposal is finalized already";
-pub const MSG_YOU_ALREADY_VOTED: &str = "You have already voted on this proposal";
-pub const MSG_YOU_DONT_OWN_THIS_PROPOSAL: &str = "You do not own this proposal";
-pub const MSG_MAX_ACTIVE_PROPOSAL_NUMBER_EXCEEDED: &str = "Max active proposals number exceeded";
-pub const MSG_STAKE_IS_EMPTY: &str = "Stake cannot be empty with this proposal";
-pub const MSG_STAKE_SHOULD_BE_EMPTY: &str = "Stake should be empty for this proposal";
-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";

+ 12 - 1
runtime-modules/proposals/engine/src/lib.rs

@@ -7,10 +7,11 @@
 //! - cancel_proposal - cancels the proposal (can be canceled only by owner)
 //! - veto_proposal - vetoes the proposal
 //!
-//! Public API (requires root origin):
+//! Public API:
 //! - create_proposal - creates proposal using provided parameters
 //! - ensure_create_proposal_parameters_are_valid - ensures that we can create the proposal
 //! - refund_proposal_stake - a callback for StakingHandlerEvents
+//! - reset_active_proposals - resets voting results for active proposals
 //!
 
 // Ensure we're `no_std` when compiling for Wasm.
@@ -461,6 +462,16 @@ impl<T: Trait> Module<T> {
             print("Broken invariant: stake doesn't exist");
         }
     }
+
+    /// Resets voting results for active proposals.
+    /// Possible application - after the new council elections.
+    pub fn reset_active_proposals() {
+        <ActiveProposalIds<T>>::enumerate().for_each(|(proposal_id, _)| {
+            <Proposals<T>>::mutate(proposal_id, |proposal| {
+                proposal.reset_proposal();
+            });
+        });
+    }
 }
 
 impl<T: Trait> Module<T> {

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

@@ -1326,3 +1326,46 @@ fn create_proposal_fails_with_invalid_threshold_parameters() {
             .create_proposal_and_assert(Err(Error::InvalidParameterSlashingThreshold.into()));
     });
 }
+
+#[test]
+fn proposal_reset_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let dummy_proposal = DummyProposalFixture::default();
+        let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
+
+        let mut vote_generator = VoteGenerator::new(proposal_id);
+        vote_generator.vote_and_assert_ok(VoteKind::Reject);
+        vote_generator.vote_and_assert_ok(VoteKind::Abstain);
+        vote_generator.vote_and_assert_ok(VoteKind::Slash);
+
+        assert!(<ActiveProposalIds<Test>>::exists(proposal_id));
+
+        run_to_block_and_finalize(2);
+
+        let proposal = <Proposals<Test>>::get(proposal_id);
+
+        assert_eq!(
+            proposal.voting_results,
+            VotingResults {
+                abstentions: 1,
+                approvals: 0,
+                rejections: 1,
+                slashes: 1,
+            }
+        );
+
+        ProposalsEngine::reset_active_proposals();
+
+        let updated_proposal = <Proposals<Test>>::get(proposal_id);
+
+        assert_eq!(
+            updated_proposal.voting_results,
+            VotingResults {
+                abstentions: 0,
+                approvals: 0,
+                rejections: 0,
+                slashes: 0,
+            }
+        );
+    });
+}

+ 8 - 0
runtime-modules/proposals/engine/src/types/mod.rs

@@ -212,6 +212,14 @@ where
             None
         }
     }
+
+    /// Reset the proposal in Active status. Proposal with other status won't be changed.
+    /// Reset proposal operation clears voting results.
+    pub fn reset_proposal(&mut self) {
+        if let ProposalStatus::Active(_) = self.status.clone() {
+            self.voting_results = VotingResults::default();
+        }
+    }
 }
 
 /// Provides data for voting.

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

@@ -1,3 +1,5 @@
+#![warn(missing_docs)]
+
 use codec::{Decode, Encode};
 use rstd::prelude::*;
 

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

@@ -1,3 +1,5 @@
+#![warn(missing_docs)]
+
 use super::{BalanceOf, CurrencyOf, NegativeImbalance};
 use crate::Trait;
 use rstd::convert::From;

+ 14 - 0
runtime/src/integration/proposals/council_elected_handler.rs

@@ -0,0 +1,14 @@
+#![warn(missing_docs)]
+
+use crate::Runtime;
+use governance::election::CouncilElected;
+
+/// 'Council elected' event handler. Should be applied to the 'election' substrate module.
+/// CouncilEvent is handled by resetting active proposals.
+pub struct CouncilElectedHandler;
+
+impl<Elected, Term> CouncilElected<Elected, Term> for CouncilElectedHandler {
+    fn council_elected(_new_council: Elected, _term: Term) {
+        <proposals_engine::Module<Runtime>>::reset_active_proposals();
+    }
+}

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

@@ -1,3 +1,5 @@
+#![warn(missing_docs)]
+
 use rstd::marker::PhantomData;
 
 use common::origin_validator::ActorOriginValidator;

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

@@ -1,3 +1,5 @@
+#![warn(missing_docs)]
+
 use rstd::marker::PhantomData;
 
 use common::origin_validator::ActorOriginValidator;

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

@@ -1,7 +1,11 @@
+#![warn(missing_docs)]
+
+mod council_elected_handler;
 mod council_origin_validator;
 mod membership_origin_validator;
 mod staking_events_handler;
 
+pub use council_elected_handler::CouncilElectedHandler;
 pub use council_origin_validator::CouncilManager;
 pub use membership_origin_validator::{MemberId, MembershipOriginValidator};
 pub use staking_events_handler::StakingEventsHandler;

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

@@ -1,3 +1,5 @@
+#![warn(missing_docs)]
+
 use rstd::marker::PhantomData;
 use srml_support::traits::{Currency, Imbalance};
 use srml_support::StorageMap;

+ 1 - 1
runtime/src/lib.rs

@@ -672,7 +672,7 @@ impl common::currency::GovernanceCurrency for Runtime {
 
 impl governance::election::Trait for Runtime {
     type Event = Event;
-    type CouncilElected = (Council,);
+    type CouncilElected = (Council, integration::proposals::CouncilElectedHandler);
 }
 
 impl governance::council::Trait for Runtime {

+ 146 - 13
runtime/src/test/proposals_integration.rs

@@ -4,16 +4,20 @@
 
 use crate::{ProposalCancellationFee, Runtime};
 use codec::Encode;
+use governance::election::CouncilElected;
 use membership::members;
 use proposals_engine::{
     ActiveStake, BalanceOf, Error, FinalizationData, Proposal, ProposalDecisionStatus,
-    ProposalParameters, ProposalStatus, VotingResults,
+    ProposalParameters, ProposalStatus, VoteKind, VotersParameters, VotingResults,
 };
 use sr_primitives::traits::DispatchResult;
 use sr_primitives::AccountId32;
 use srml_support::traits::Currency;
+use srml_support::StorageLinkedMap;
 use system::RawOrigin;
 
+use crate::CouncilManager;
+
 fn initial_test_ext() -> runtime_io::TestExternalities {
     let t = system::GenesisConfig::default()
         .build_storage::<Runtime>()
@@ -24,6 +28,91 @@ fn initial_test_ext() -> runtime_io::TestExternalities {
 
 type Membership = membership::members::Module<Runtime>;
 type ProposalsEngine = proposals_engine::Module<Runtime>;
+type Council = governance::council::Module<Runtime>;
+
+fn setup_members(count: u8) {
+    let authority_account_id = <Runtime as system::Trait>::AccountId::default();
+    Membership::set_screening_authority(RawOrigin::Root.into(), authority_account_id.clone())
+        .unwrap();
+
+    for i in 0..count {
+        let account_id: [u8; 32] = [i; 32];
+        Membership::add_screened_member(
+            RawOrigin::Signed(authority_account_id.clone().into()).into(),
+            account_id.clone().into(),
+            members::UserInfo {
+                handle: Some(account_id.to_vec()),
+                avatar_uri: None,
+                about: None,
+            },
+        )
+        .unwrap();
+    }
+}
+
+fn setup_council() {
+    let councilor0 = AccountId32::default();
+    let councilor1: [u8; 32] = [1; 32];
+    let councilor2: [u8; 32] = [2; 32];
+    let councilor3: [u8; 32] = [3; 32];
+    let councilor4: [u8; 32] = [4; 32];
+    let councilor5: [u8; 32] = [5; 32];
+    assert!(Council::set_council(
+        system::RawOrigin::Root.into(),
+        vec![
+            councilor0,
+            councilor1.into(),
+            councilor2.into(),
+            councilor3.into(),
+            councilor4.into(),
+            councilor5.into()
+        ]
+    )
+    .is_ok());
+}
+
+struct VoteGenerator {
+    proposal_id: u32,
+    current_account_id: AccountId32,
+    current_account_id_seed: u8,
+    current_voter_id: u64,
+    pub auto_increment_voter_id: bool,
+}
+
+impl VoteGenerator {
+    fn new(proposal_id: u32) -> Self {
+        VoteGenerator {
+            proposal_id,
+            current_voter_id: 0,
+            current_account_id_seed: 0,
+            current_account_id: AccountId32::default(),
+            auto_increment_voter_id: true,
+        }
+    }
+    fn vote_and_assert_ok(&mut self, vote_kind: VoteKind) {
+        self.vote_and_assert(vote_kind, Ok(()));
+    }
+
+    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) -> DispatchResult<Error> {
+        if self.auto_increment_voter_id {
+            self.current_account_id_seed += 1;
+            self.current_voter_id += 1;
+            let account_id: [u8; 32] = [self.current_account_id_seed; 32];
+            self.current_account_id = account_id.into();
+        }
+
+        ProposalsEngine::vote(
+            system::RawOrigin::Signed(self.current_account_id.clone()).into(),
+            self.current_voter_id,
+            self.proposal_id,
+            vote_kind,
+        )
+    }
+}
 
 #[derive(Clone)]
 struct DummyProposalFixture {
@@ -147,18 +236,7 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
     initial_test_ext().execute_with(|| {
         let account_id = <Runtime as system::Trait>::AccountId::default();
 
-        Membership::set_screening_authority(RawOrigin::Root.into(), account_id.clone()).unwrap();
-
-        Membership::add_screened_member(
-            RawOrigin::Signed(account_id.clone()).into(),
-            account_id.clone(),
-            members::UserInfo {
-                handle: Some(b"handle".to_vec()),
-                avatar_uri: None,
-                about: None,
-            },
-        )
-        .unwrap();
+        setup_members(2);
         let member_id = 0; // newly created member_id
 
         let stake_amount = 200u128;
@@ -232,3 +310,58 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
         );
     });
 }
+
+#[test]
+fn proposal_reset_succeeds() {
+    initial_test_ext().execute_with(|| {
+        setup_members(4);
+        setup_council();
+        // create proposal
+        let dummy_proposal = DummyProposalFixture::default();
+        let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
+
+        // create some votes
+        let mut vote_generator = VoteGenerator::new(proposal_id);
+        vote_generator.vote_and_assert_ok(VoteKind::Reject);
+        vote_generator.vote_and_assert_ok(VoteKind::Abstain);
+        vote_generator.vote_and_assert_ok(VoteKind::Slash);
+
+        assert!(<proposals_engine::ActiveProposalIds<Runtime>>::exists(
+            proposal_id
+        ));
+
+        // check
+        let proposal = ProposalsEngine::proposals(proposal_id);
+        assert_eq!(
+            proposal.voting_results,
+            VotingResults {
+                abstentions: 1,
+                approvals: 0,
+                rejections: 1,
+                slashes: 1,
+            }
+        );
+
+        // Ensure council was elected
+        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 6);
+
+        // Check proposals CouncilElected hook
+        // just trigger the election hook, we don't care about the parameters
+        <Runtime as governance::election::Trait>::CouncilElected::council_elected(Vec::new(), 10);
+
+        let updated_proposal = ProposalsEngine::proposals(proposal_id);
+
+        assert_eq!(
+            updated_proposal.voting_results,
+            VotingResults {
+                abstentions: 0,
+                approvals: 0,
+                rejections: 0,
+                slashes: 0,
+            }
+        );
+
+        // Check council CouncilElected hook. It should set current council. And we passed empty council.
+        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 0);
+    });
+}