Browse Source

council - candidacy withdrawal fix

ondratra 3 years ago
parent
commit
7c4fdc63c7
2 changed files with 87 additions and 10 deletions
  1. 35 9
      runtime-modules/council/src/lib.rs
  2. 52 1
      runtime-modules/council/src/tests.rs

+ 35 - 9
runtime-modules/council/src/lib.rs

@@ -631,7 +631,7 @@ decl_module! {
         /// # </weight>
         #[weight = CouncilWeightInfo::<T>::withdraw_candidacy()]
         pub fn withdraw_candidacy(origin, membership_id: T::MemberId) -> Result<(), Error<T>> {
-            let staking_account_id =
+            let (stage_data, candidate) =
                 EnsureChecks::<T>::can_withdraw_candidacy(origin, &membership_id)?;
 
             //
@@ -639,7 +639,7 @@ decl_module! {
             //
 
             // update state
-            Mutations::<T>::release_candidacy_stake(&membership_id, &staking_account_id);
+            Mutations::<T>::withdraw_candidacy(&stage_data, &membership_id, &candidate);
 
             // emit event
             Self::deposit_event(RawEvent::CandidacyWithdraw(membership_id));
@@ -902,10 +902,10 @@ impl<T: Trait> Module<T> {
 
     // Finish voting and start ravealing.
     fn end_announcement_period(stage_data: CouncilStageAnnouncing) {
-        let candidate_count = T::CouncilSize::get() + T::MinNumberOfExtraCandidates::get();
+        let min_candidate_count = T::CouncilSize::get() + T::MinNumberOfExtraCandidates::get();
 
         // reset announcing period when not enough candidates registered
-        if stage_data.candidates_count < candidate_count {
+        if stage_data.candidates_count < min_candidate_count {
             Mutations::<T>::start_announcing_period();
 
             // emit event
@@ -1322,7 +1322,7 @@ impl<T: Trait> Mutations<T> {
             candidates_count: stage_data.candidates_count + 1,
         };
 
-        // store new candidacy list
+        // store new stage
         Stage::<T>::mutate(|value| {
             *value = CouncilStageUpdate {
                 stage: CouncilStage::Announcing(new_stage_data),
@@ -1336,6 +1336,30 @@ impl<T: Trait> Mutations<T> {
         T::CandidacyLock::lock(&candidate.staking_account_id, *stake);
     }
 
+    fn withdraw_candidacy(
+        stage_data: &CouncilStageAnnouncing,
+        membership_id: &T::MemberId,
+        candidate: &CandidateOf<T>,
+    ) {
+        // release candidacy stake
+        Self::release_candidacy_stake(&membership_id, &candidate.staking_account_id);
+
+        // prepare new stage
+        let new_stage_data = CouncilStageAnnouncing {
+            candidates_count: stage_data.candidates_count - 1,
+        };
+
+        // store new stage
+        Stage::<T>::mutate(|value| {
+            *value = CouncilStageUpdate {
+                stage: CouncilStage::Announcing(new_stage_data),
+
+                // keep changed_at (and other values) - stage phase haven't changed
+                ..*value
+            }
+        });
+    }
+
     // Release user's stake that was used for candidacy.
     fn release_candidacy_stake(membership_id: &T::MemberId, account_id: &T::AccountId) {
         // release stake amount
@@ -1525,7 +1549,7 @@ impl<T: Trait> EnsureChecks<T> {
     fn can_withdraw_candidacy(
         origin: T::Origin,
         membership_id: &T::MemberId,
-    ) -> Result<T::AccountId, Error<T>> {
+    ) -> Result<(CouncilStageAnnouncing, CandidateOf<T>), Error<T>> {
         // ensure user's membership
         Self::ensure_user_membership(origin, membership_id)?;
 
@@ -1537,17 +1561,19 @@ impl<T: Trait> EnsureChecks<T> {
         let candidate = Candidates::<T>::get(membership_id);
 
         // ensure candidacy announcing period is running now
-        match Stage::<T>::get().stage {
-            CouncilStage::Announcing(_) => {
+        let stage_data = match Stage::<T>::get().stage {
+            CouncilStage::Announcing(stage_data) => {
                 // ensure candidacy was announced in current election cycle
                 if candidate.cycle_id != AnnouncementPeriodNr::get() {
                     return Err(Error::NotCandidatingNow);
                 }
+
+                stage_data
             }
             _ => return Err(Error::CantWithdrawCandidacyNow),
         };
 
-        Ok(candidate.staking_account_id)
+        Ok((stage_data, candidate))
     }
 
     // Ensures there is no problem in setting new note for the candidacy.

+ 52 - 1
runtime-modules/council/src/tests.rs

@@ -244,7 +244,8 @@ fn council_candidacy_release_candidate_stake() {
     });
 }
 
-// Test that only valid members can candidate.
+// Test that the announcement period is reset in case that not enough candidates
+// to fill the council has announced their candidacy.
 #[test]
 fn council_announcement_reset_on_insufficient_candidates() {
     let config = default_genesis_config();
@@ -288,6 +289,56 @@ fn council_announcement_reset_on_insufficient_candidates() {
     });
 }
 
+// Test that the announcement period is reset in case that not enough candidates
+// to fill the council has announced and not withdrawn their candidacy.
+#[test]
+fn council_announcement_reset_on_insufficient_candidates_after_candidacy_withdrawal() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0..council_settings.min_candidate_count)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: council_settings.clone(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: vec![], // not needed in this scenario
+            candidates_announcing: candidates.clone(),
+            expected_candidates: vec![], // not needed in this scenario
+            voters: vec![],              // not needed in this scenario
+
+            // escape before voting
+            interrupt_point: Some(CouncilCycleInterrupt::AfterCandidatesAnnounce),
+        };
+
+        Mocks::simulate_council_cycle(params.clone());
+
+        Mocks::withdraw_candidacy(
+            candidates[0].origin.clone(),
+            candidates[0].account_id.clone(),
+            Ok(()),
+        );
+
+        // forward to election-voting period
+        MockUtils::increase_block_number(council_settings.announcing_stage_duration + 1);
+
+        // check announcements were reset
+        Mocks::check_announcing_period(
+            params.cycle_start_block_number + council_settings.announcing_stage_duration,
+            CouncilStageAnnouncing {
+                candidates_count: 0,
+            },
+        );
+    });
+}
+
 // Test that announcement phase is reset when not enough candidates to fill council recieved votes
 #[test]
 fn council_announcement_reset_on_not_enough_winners() {