Browse Source

runtime: proposals: Add constitutionality for proposals.

Shamil Gadelshin 4 years ago
parent
commit
74cda7b419

+ 2 - 2
runtime-modules/proposals/discussion/src/lib.rs

@@ -216,7 +216,7 @@ decl_module! {
 
             let new_post = DiscussionPost {
                 text,
-                created_at: Self::current_block(),
+                activated_at: Self::current_block(),
                 updated_at: Self::current_block(),
                 author_id: post_author_id,
                 edition_number : 0,
@@ -287,7 +287,7 @@ impl<T: Trait> Module<T> {
 
         let new_thread = DiscussionThread {
             title,
-            created_at: Self::current_block(),
+            activated_at: Self::current_block(),
             author_id: thread_author_id,
         };
 

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

@@ -40,7 +40,7 @@ fn assert_thread_content(thread_entry: TestThreadEntry, post_entries: Vec<TestPo
     let actual_thread = <ThreadById<Test>>::get(thread_entry.thread_id);
     let expected_thread = DiscussionThread {
         title: thread_entry.title,
-        created_at: 0,
+        activated_at: 0,
         author_id: 1,
     };
     assert_eq!(actual_thread, expected_thread);
@@ -50,7 +50,7 @@ fn assert_thread_content(thread_entry: TestThreadEntry, post_entries: Vec<TestPo
             <PostThreadIdByPostId<Test>>::get(thread_entry.thread_id, post_entry.post_id);
         let expected_post = DiscussionPost {
             text: post_entry.text,
-            created_at: 0,
+            activated_at: 0,
             updated_at: 0,
             author_id: 1,
             thread_id: thread_entry.thread_id,

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

@@ -13,7 +13,7 @@ pub struct DiscussionThread<ThreadAuthorId, BlockNumber> {
     pub title: Vec<u8>,
 
     /// When thread was established.
-    pub created_at: BlockNumber,
+    pub activated_at: BlockNumber,
 
     /// Author of the thread.
     pub author_id: ThreadAuthorId,
@@ -27,7 +27,7 @@ pub struct DiscussionPost<PostAuthorId, BlockNumber, ThreadId> {
     pub text: Vec<u8>,
 
     /// When post was added.
-    pub created_at: BlockNumber,
+    pub activated_at: BlockNumber,
 
     /// When post was updated last time.
     pub updated_at: BlockNumber,

+ 18 - 8
runtime-modules/proposals/engine/src/lib.rs

@@ -313,7 +313,11 @@ decl_storage! {
             T::ProposalId=> ();
 
         /// Ids of proposals that were approved and theirs grace period was not expired.
-        pub PendingExecutionProposalIds get(fn pending_proposal_ids): map hasher(blake2_128_concat)
+        pub PendingExecutionProposalIds get(fn pending_execution_proposal_ids): map hasher(blake2_128_concat)
+            T::ProposalId=> ();
+
+        /// Ids of proposals that were approved and needed more councils approvals to be executed.
+        pub PendingConstitutionalityProposalIds get(fn pending_consitutionality_proposal_ids): map hasher(blake2_128_concat)
             T::ProposalId=> ();
 
         /// Double map for preventing duplicate votes. Should be cleaned after usage.
@@ -485,7 +489,7 @@ impl<T: Trait> Module<T> {
             };
 
         let new_proposal = Proposal {
-            created_at: Self::current_block(),
+            activated_at: Self::current_block(),
             parameters: creation_params.proposal_parameters,
             title: creation_params.title,
             description: creation_params.description,
@@ -493,6 +497,7 @@ impl<T: Trait> Module<T> {
             status: ProposalStatus::Active(stake_data),
             voting_results: VotingResults::default(),
             exact_execution_block: creation_params.exact_execution_block,
+            current_constitutionality_level: 0,
         };
 
         <Proposals<T>>::insert(proposal_id, new_proposal);
@@ -700,11 +705,14 @@ impl<T: Trait> Module<T> {
         let mut proposal = Self::proposals(proposal_id);
 
         if let ProposalStatus::Active(active_stake) = proposal.status.clone() {
-            let mut clean_finilized_proposal = true;
-            if let ProposalDecisionStatus::Approved { .. } = decision_status {
-                <PendingExecutionProposalIds<T>>::insert(proposal_id, ());
+            if let ProposalDecisionStatus::Approved(approved_status) = decision_status.clone() {
+                proposal.current_constitutionality_level += 1;
 
-                clean_finilized_proposal = false; // keep pending execution proposal
+                if approved_status == ApprovedProposalStatus::PendingConstitutionality {
+                    <PendingConstitutionalityProposalIds<T>>::insert(proposal_id, ());
+                } else {
+                    <PendingExecutionProposalIds<T>>::insert(proposal_id, ());
+                }
             }
 
             // deal with stakes if necessary
@@ -714,9 +722,10 @@ impl<T: Trait> Module<T> {
 
             // create finalized proposal status with error if any
             let new_proposal_status =
-                ProposalStatus::finalized(decision_status, Self::current_block());
+                ProposalStatus::finalized(decision_status.clone(), Self::current_block());
 
-            if clean_finilized_proposal {
+            // update approved proposal or remove otherwise
+            if !matches!(decision_status, ProposalDecisionStatus::Approved(..)) {
                 Self::remove_proposal_data(&proposal_id);
             } else {
                 proposal.status = new_proposal_status.clone();
@@ -765,6 +774,7 @@ impl<T: Trait> Module<T> {
                 .required_stake
                 .clone()
                 .unwrap_or_else(BalanceOf::<T>::zero), // stake if set or zero
+            ProposalDecisionStatus::PendingConstitutionality => BalanceOf::<T>::zero(),
         }
     }
 

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

@@ -42,6 +42,14 @@ impl ProposalParametersFixture {
             },
         }
     }
+    fn with_constitutionality(&self, constitutionality: u32) -> Self {
+        ProposalParametersFixture {
+            parameters: ProposalParameters {
+                constitutionality,
+                ..self.parameters
+            },
+        }
+    }
 
     fn params(&self) -> ProposalParameters<u64, u64> {
         self.parameters.clone()
@@ -841,7 +849,7 @@ fn proposal_execution_postponed_because_of_grace_period() {
             Proposal {
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
-                created_at: 0,
+                activated_at: 0,
                 status: ProposalStatus::approved(ApprovedProposalStatus::PendingExecution, 0),
                 title: b"title".to_vec(),
                 description: b"description".to_vec(),
@@ -852,6 +860,7 @@ fn proposal_execution_postponed_because_of_grace_period() {
                     slashes: 0,
                 },
                 exact_execution_block: None,
+                current_constitutionality_level: 1,
             }
         );
     });
@@ -880,14 +889,14 @@ fn proposal_execution_vetoed_successfully_during_the_grace_period() {
             proposal_id
         ));
 
-        let proposal = <crate::Proposals<Test>>::get(proposal_id);
+        let pre_veto_proposal = <crate::Proposals<Test>>::get(proposal_id);
 
         assert_eq!(
-            proposal,
+            pre_veto_proposal,
             Proposal {
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
-                created_at: 0,
+                activated_at: 0,
                 status: ProposalStatus::approved(ApprovedProposalStatus::PendingExecution, 0),
                 title: b"title".to_vec(),
                 description: b"description".to_vec(),
@@ -898,6 +907,7 @@ fn proposal_execution_vetoed_successfully_during_the_grace_period() {
                     slashes: 0,
                 },
                 exact_execution_block: None,
+                current_constitutionality_level: 1,
             }
         );
 
@@ -909,19 +919,8 @@ fn proposal_execution_vetoed_successfully_during_the_grace_period() {
         assert_eq!(
             proposal,
             Proposal {
-                parameters: parameters_fixture.params(),
-                proposer_id: 1,
-                created_at: 0,
                 status: ProposalStatus::finalized(ProposalDecisionStatus::Vetoed, 2),
-                title: b"title".to_vec(),
-                description: b"description".to_vec(),
-                voting_results: VotingResults {
-                    abstentions: 0,
-                    approvals: 4,
-                    rejections: 0,
-                    slashes: 0,
-                },
-                exact_execution_block: None,
+                ..pre_veto_proposal.clone()
             }
         );
 
@@ -961,7 +960,7 @@ fn proposal_execution_succeeds_after_the_grace_period() {
         let expected_proposal = Proposal {
             parameters: parameters_fixture.params(),
             proposer_id: 1,
-            created_at: starting_block,
+            activated_at: starting_block,
             status: ProposalStatus::approved(
                 ApprovedProposalStatus::PendingExecution,
                 starting_block,
@@ -975,6 +974,7 @@ fn proposal_execution_succeeds_after_the_grace_period() {
                 slashes: 0,
             },
             exact_execution_block: None,
+            current_constitutionality_level: 1,
         };
 
         assert_eq!(proposal, expected_proposal);
@@ -1040,7 +1040,7 @@ fn create_dummy_proposal_succeeds_with_stake() {
             Proposal {
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
-                created_at: 0,
+                activated_at: 0,
                 status: ProposalStatus::Active(Some(ActiveStake {
                     source_account_id: 1
                 })),
@@ -1048,6 +1048,7 @@ fn create_dummy_proposal_succeeds_with_stake() {
                 description: b"description".to_vec(),
                 voting_results: VotingResults::default(),
                 exact_execution_block: None,
+                current_constitutionality_level: 0,
             }
         )
     });
@@ -1139,7 +1140,7 @@ fn finalize_expired_proposal_and_check_stake_removing_with_balance_checks_succee
         let expected_proposal = Proposal {
             parameters,
             proposer_id: 1,
-            created_at: starting_block,
+            activated_at: starting_block,
             status: ProposalStatus::Active(Some(ActiveStake {
                 source_account_id: 1,
             })),
@@ -1147,6 +1148,7 @@ fn finalize_expired_proposal_and_check_stake_removing_with_balance_checks_succee
             description: b"description".to_vec(),
             voting_results: VotingResults::default(),
             exact_execution_block: None,
+            current_constitutionality_level: 0,
         };
 
         assert_eq!(proposal, expected_proposal);
@@ -1214,7 +1216,7 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
         let expected_proposal = Proposal {
             parameters,
             proposer_id: 1,
-            created_at: starting_block,
+            activated_at: starting_block,
             status: ProposalStatus::Active(Some(ActiveStake {
                 source_account_id: 1,
             })),
@@ -1222,6 +1224,7 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
             description: b"description".to_vec(),
             voting_results: VotingResults::default(),
             exact_execution_block: None,
+            current_constitutionality_level: 0,
         };
 
         assert_eq!(proposal, expected_proposal);
@@ -1546,7 +1549,7 @@ fn proposal_execution_with_exact_execution_works() {
             Proposal {
                 parameters: parameters_fixture.params(),
                 proposer_id: 1,
-                created_at: 0,
+                activated_at: 0,
                 status: ProposalStatus::approved(ApprovedProposalStatus::PendingExecution, 0),
                 title: b"title".to_vec(),
                 description: b"description".to_vec(),
@@ -1557,6 +1560,7 @@ fn proposal_execution_with_exact_execution_works() {
                     slashes: 0,
                 },
                 exact_execution_block: Some(exact_block),
+                current_constitutionality_level: 1,
             }
         );
 
@@ -1577,3 +1581,69 @@ fn proposal_execution_with_exact_execution_works() {
         ));
     });
 }
+
+#[test]
+fn proposal_with_pending_constitutionality_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let starting_block = 1;
+        run_to_block_and_finalize(1);
+
+        let parameters_fixture = ProposalParametersFixture::default().with_constitutionality(2);
+        let dummy_proposal =
+            DummyProposalFixture::default().with_parameters(parameters_fixture.params());
+        let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
+
+        // internal active proposal counter check
+        assert_eq!(<ActiveProposalCount>::get(), 1);
+
+        let mut vote_generator = VoteGenerator::new(proposal_id);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+
+        run_to_block_and_finalize(2);
+
+        EventFixture::assert_last_crate_event(RawEvent::ProposalStatusUpdated(
+            proposal_id,
+            ProposalStatus::approved(
+                ApprovedProposalStatus::PendingConstitutionality,
+                starting_block,
+            ),
+        ));
+
+        let proposal = <Proposals<Test>>::get(proposal_id);
+
+        assert_eq!(
+            proposal,
+            Proposal {
+                parameters: parameters_fixture.params(),
+                proposer_id: 1,
+                activated_at: starting_block,
+                status: ProposalStatus::approved(
+                    ApprovedProposalStatus::PendingConstitutionality,
+                    starting_block
+                ),
+                title: b"title".to_vec(),
+                description: b"description".to_vec(),
+                voting_results: VotingResults {
+                    abstentions: 0,
+                    approvals: 4,
+                    rejections: 0,
+                    slashes: 0,
+                },
+                exact_execution_block: None,
+                current_constitutionality_level: 1,
+            }
+        );
+        // internal active proposal counter check
+        assert_eq!(<ActiveProposalCount>::get(), 0);
+        assert!(!<PendingExecutionProposalIds<Test>>::contains_key(
+            proposal_id
+        ));
+
+        assert!(<PendingConstitutionalityProposalIds<Test>>::contains_key(
+            proposal_id
+        ));
+    });
+}

+ 29 - 15
runtime-modules/proposals/engine/src/types/mod.rs

@@ -137,8 +137,8 @@ pub struct Proposal<BlockNumber, ProposerId, Balance, AccountId> {
     /// Proposal body
     pub description: Vec<u8>,
 
-    /// When it was created.
-    pub created_at: BlockNumber,
+    /// When it was created or reactivated to get constitutionality.
+    pub activated_at: BlockNumber,
 
     /// Current proposal status
     pub status: ProposalStatus<BlockNumber, AccountId>,
@@ -148,6 +148,10 @@ pub struct Proposal<BlockNumber, ProposerId, Balance, AccountId> {
 
     /// Optional exact block height which triggers the execution.
     pub exact_execution_block: Option<BlockNumber>,
+
+    /// The number of councils in that must approve the proposal in a row before it has its
+    /// intended effect.
+    pub current_constitutionality_level: u32,
 }
 
 impl<BlockNumber, ProposerId, Balance, AccountId>
@@ -158,7 +162,7 @@ where
 {
     /// Returns whether voting period expired by now
     pub fn is_voting_period_expired(&self, now: BlockNumber) -> bool {
-        now >= self.created_at + self.parameters.voting_period
+        now >= self.activated_at + self.parameters.voting_period
     }
 
     /// Returns whether grace period expired by now.
@@ -211,9 +215,13 @@ where
         if proposal_status_resolution.is_approval_quorum_reached()
             && proposal_status_resolution.is_approval_threshold_reached()
         {
-            Some(ProposalDecisionStatus::Approved(
-                ApprovedProposalStatus::PendingExecution,
-            ))
+            let approved_status = if proposal_status_resolution.is_constitutionality_reached() {
+                ApprovedProposalStatus::PendingExecution
+            } else {
+                ApprovedProposalStatus::PendingConstitutionality
+            };
+
+            Some(ProposalDecisionStatus::Approved(approved_status))
         } else if proposal_status_resolution.is_slashing_quorum_reached()
             && proposal_status_resolution.is_slashing_threshold_reached()
         {
@@ -311,6 +319,12 @@ where
     pub fn is_voting_completed(&self) -> bool {
         self.votes_count == self.total_voters_count
     }
+
+    // Council approved the proposal enough times.
+    pub fn is_constitutionality_reached(&self) -> bool {
+        self.proposal.current_constitutionality_level + 1
+            >= self.proposal.parameters.constitutionality
+    }
 }
 
 /// Proposal executable code wrapper
@@ -435,7 +449,7 @@ mod tests {
     fn proposal_voting_period_expired() {
         let mut proposal = ProposalObject::default();
 
-        proposal.created_at = 1;
+        proposal.activated_at = 1;
         proposal.parameters.voting_period = 3;
 
         assert!(proposal.is_voting_period_expired(4));
@@ -445,7 +459,7 @@ mod tests {
     fn proposal_voting_period_not_expired() {
         let mut proposal = ProposalObject::default();
 
-        proposal.created_at = 1;
+        proposal.activated_at = 1;
         proposal.parameters.voting_period = 3;
 
         assert!(!proposal.is_voting_period_expired(3));
@@ -499,7 +513,7 @@ mod tests {
     fn define_proposal_decision_status_returns_expired() {
         let mut proposal = ProposalObject::default();
         let now = 5;
-        proposal.created_at = 1;
+        proposal.activated_at = 1;
         proposal.parameters.voting_period = 3;
         proposal.parameters.approval_quorum_percentage = 80;
         proposal.parameters.approval_threshold_percentage = 40;
@@ -531,7 +545,7 @@ mod tests {
     fn define_proposal_decision_status_returns_approved() {
         let now = 2;
         let mut proposal = ProposalObject::default();
-        proposal.created_at = 1;
+        proposal.activated_at = 1;
         proposal.parameters.voting_period = 3;
         proposal.parameters.approval_quorum_percentage = 60;
         proposal.parameters.slashing_quorum_percentage = 50;
@@ -566,7 +580,7 @@ mod tests {
         let mut proposal = ProposalObject::default();
         let now = 2;
 
-        proposal.created_at = 1;
+        proposal.activated_at = 1;
         proposal.parameters.voting_period = 3;
         proposal.parameters.approval_quorum_percentage = 50;
         proposal.parameters.approval_threshold_percentage = 51;
@@ -600,7 +614,7 @@ mod tests {
         let mut proposal = ProposalObject::default();
         let now = 2;
 
-        proposal.created_at = 1;
+        proposal.activated_at = 1;
         proposal.parameters.voting_period = 3;
         proposal.parameters.approval_quorum_percentage = 50;
         proposal.parameters.approval_threshold_percentage = 50;
@@ -634,7 +648,7 @@ mod tests {
         let mut proposal = ProposalObject::default();
         let now = 2;
 
-        proposal.created_at = 1;
+        proposal.activated_at = 1;
         proposal.parameters.voting_period = 3;
         proposal.parameters.approval_quorum_percentage = 60;
         proposal.parameters.slashing_quorum_percentage = 50;
@@ -659,7 +673,7 @@ mod tests {
         let mut proposal = ProposalObject::default();
         let now = 2;
 
-        proposal.created_at = 1;
+        proposal.activated_at = 1;
         proposal.parameters.voting_period = 3;
         proposal.parameters.approval_quorum_percentage = 50;
         proposal.parameters.approval_threshold_percentage = 30;
@@ -698,7 +712,7 @@ mod tests {
         let mut proposal = ProposalObject::default();
         let now = 2;
 
-        proposal.created_at = 1;
+        proposal.activated_at = 1;
         proposal.parameters.voting_period = 3;
         proposal.parameters.approval_quorum_percentage = 50;
         proposal.parameters.approval_threshold_percentage = 30;

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

@@ -66,6 +66,9 @@ pub enum ApprovedProposalStatus {
     /// A proposal was approved and grace period is in effect
     PendingExecution,
 
+    /// The proposal needs more than one council approval.
+    PendingConstitutionality,
+
     /// Proposal was successfully executed
     Executed,
 
@@ -107,6 +110,9 @@ pub enum ProposalDecisionStatus {
     /// 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(ApprovedProposalStatus),
+
+    /// The proposal needs more than one council approval.
+    PendingConstitutionality,
 }
 
 #[cfg(test)]

+ 2 - 1
runtime/src/tests/proposals_integration/mod.rs

@@ -314,7 +314,7 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
         let expected_proposal = Proposal {
             parameters,
             proposer_id: member_id,
-            created_at: 0,
+            activated_at: 0,
             status: ProposalStatus::Active(Some(ActiveStake {
                 source_account_id: account_id.clone(),
             })),
@@ -322,6 +322,7 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
             description: b"description".to_vec(),
             voting_results: VotingResults::default(),
             exact_execution_block: None,
+            current_constitutionality_level: 0,
         };
 
         assert_eq!(proposal, expected_proposal);