Browse Source

Merge branch 'council_spending_budget2' into council_constants_exposure

ondratra 4 years ago
parent
commit
46cec757f3

+ 322 - 45
runtime-modules/council/src/lib.rs

@@ -30,6 +30,7 @@
 //! - [announce_candidacy](./struct.Module.html#method.announce_candidacy)
 //! - [release_candidacy_stake](./struct.Module.html#method.release_candidacy_stake)
 //! - [set_candidacy_note](./struct.Module.html#method.set_candidacy_note)
+//! - [set_budget](./struct.Module.html#method.set_budget)
 //!
 //! ## Important functions
 //! These functions have to be called by the runtime for the council to work properly.
@@ -55,9 +56,9 @@ use frame_support::{
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 use sp_arithmetic::traits::BaseArithmetic;
-use sp_runtime::traits::{Hash, MaybeSerialize, Member};
+use sp_runtime::traits::{Hash, MaybeSerialize, Member, SaturatedConversion, Saturating};
 use std::marker::PhantomData;
-use system::ensure_signed;
+use system::{ensure_root, ensure_signed};
 
 use referendum::{CastVote, OptionResult, ReferendumManager};
 
@@ -113,6 +114,7 @@ pub struct CouncilStageElection {
 #[derive(Encode, Decode, PartialEq, Eq, Debug, Default, Clone)]
 pub struct Candidate<AccountId, Balance, Hash> {
     staking_account_id: AccountId,
+    reward_account_id: AccountId,
     cycle_id: u64,
     stake: Balance,
     note_hash: Option<Hash>,
@@ -121,21 +123,40 @@ pub struct Candidate<AccountId, Balance, Hash> {
 /// Council member representation.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, PartialEq, Eq, Debug, Default, Clone)]
-pub struct CouncilMember<AccountId, MembershipId, Balance> {
+pub struct CouncilMember<AccountId, MembershipId, Balance, BlockNumber> {
     staking_account_id: AccountId,
+    reward_account_id: AccountId,
     membership_id: MembershipId,
     stake: Balance,
+
+    last_payment_block: BlockNumber,
+    unpaid_reward: Balance,
 }
 
-impl<AccountId, MembershipId, Balance, Hash>
-    From<(Candidate<AccountId, Balance, Hash>, MembershipId)>
-    for CouncilMember<AccountId, MembershipId, Balance>
+impl<AccountId, MembershipId, Balance, Hash, BlockNumber>
+    From<(
+        Candidate<AccountId, Balance, Hash>,
+        MembershipId,
+        BlockNumber,
+        Balance,
+    )> for CouncilMember<AccountId, MembershipId, Balance, BlockNumber>
 {
-    fn from(candidate_and_user_id: (Candidate<AccountId, Balance, Hash>, MembershipId)) -> Self {
+    fn from(
+        from: (
+            Candidate<AccountId, Balance, Hash>,
+            MembershipId,
+            BlockNumber,
+            Balance,
+        ),
+    ) -> Self {
         Self {
-            staking_account_id: candidate_and_user_id.0.staking_account_id,
-            membership_id: candidate_and_user_id.1,
-            stake: candidate_and_user_id.0.stake,
+            staking_account_id: from.0.staking_account_id,
+            reward_account_id: from.0.reward_account_id,
+            membership_id: from.1,
+            stake: from.0.stake,
+
+            last_payment_block: from.2,
+            unpaid_reward: from.3,
         }
     }
 }
@@ -154,8 +175,12 @@ pub type VotePowerOf<T> = <<T as Trait>::Referendum as ReferendumManager<
 >>::VotePower;
 pub type CastVoteOf<T> = CastVote<<T as system::Trait>::Hash, Balance<T>>;
 
-pub type CouncilMemberOf<T> =
-    CouncilMember<<T as system::Trait>::AccountId, <T as Trait>::MembershipId, Balance<T>>;
+pub type CouncilMemberOf<T> = CouncilMember<
+    <T as system::Trait>::AccountId,
+    <T as Trait>::MembershipId,
+    Balance<T>,
+    <T as system::Trait>::BlockNumber,
+>;
 pub type CandidateOf<T> =
     Candidate<<T as system::Trait>::AccountId, Balance<T>, <T as system::Trait>::Hash>;
 pub type CouncilStageUpdateOf<T> = CouncilStageUpdate<<T as system::Trait>::BlockNumber>;
@@ -200,6 +225,16 @@ pub trait Trait: system::Trait {
     /// Duration of idle period
     type IdlePeriodDuration: Get<Self::BlockNumber>;
 
+    /// The value elected members will be awarded each block of their reign.
+    type ElectedMemberRewardPerBlock: Get<Balance<Self>>;
+    /// Interval for automatic reward payments.
+    type ElectedMemberRewardPeriod: Get<Self::BlockNumber>;
+
+    /// Amount that will be added to the budget balance on every refill.
+    type BudgetRefillAmount: Get<Balance<Self>>;
+    /// Interval between automatic budget refills.
+    type BudgetRefillPeriod: Get<Self::BlockNumber>;
+
     /// Checks that the user account is indeed associated with the member.
     fn is_council_member_account(
         membership_id: &Self::MembershipId,
@@ -210,8 +245,7 @@ pub trait Trait: system::Trait {
 /// Trait with functions that MUST be called by the runtime with values received from the referendum module.
 pub trait ReferendumConnection<T: Trait> {
     /// Process referendum results. This function MUST be called in runtime's implementation of referendum's `process_results()`.
-    fn recieve_referendum_results(winners: &[OptionResult<VotePowerOf<T>>])
-        -> Result<(), Error<T>>;
+    fn recieve_referendum_results(winners: &[OptionResult<VotePowerOf<T>>]);
 
     /// Process referendum results. This function MUST be called in runtime's implementation of referendum's `can_release_voting_stake()`.
     fn can_release_vote_stake(
@@ -236,6 +270,15 @@ decl_storage! {
 
         /// Index of the current candidacy period. It is incremented everytime announcement period starts.
         pub AnnouncementPeriodNr get(fn announcement_period_nr) config(): u64;
+
+        /// Budget for the council's elected members rewards.
+        pub Budget get(fn budget): Balance<T>;
+
+        /// The next block in which the elected council member rewards will be payed.
+        pub NextRewardPayments get(fn next_reward_payments): T::BlockNumber;
+
+        /// The next block in which the budget will be increased.
+        pub NextBudgetRefill get(fn next_budget_refill): T::BlockNumber;
     }
 }
 
@@ -244,6 +287,7 @@ decl_event! {
     where
         Balance = Balance::<T>,
         <T as Trait>::MembershipId,
+        <T as system::Trait>::AccountId,
     {
         /// New council was elected
         AnnouncingPeriodStarted(),
@@ -271,6 +315,12 @@ decl_event! {
 
         /// The candidate has set a new note for their candidacy
         CandidacyNoteSet(MembershipId, Vec<u8>),
+
+        /// The whole reward was paid to the council member.
+        RewardPayment(MembershipId, AccountId, Balance, Balance),
+
+        /// Budget balance was changed by the root.
+        BudgetBalanceSet(Balance),
     }
 }
 
@@ -348,17 +398,23 @@ decl_module! {
 
         // No origin so this is a priviledged call
         fn on_finalize(now: T::BlockNumber) {
+            // council stage progress
             Self::try_progress_stage(now);
+
+            // budget reward payment + budget refill
+            Self::try_process_budget(now);
         }
 
+        /////////////////// Election-related ///////////////////////////////////
+
         /// Subscribe candidate
         #[weight = 10_000_000]
-        pub fn announce_candidacy(origin, membership_id: T::MembershipId, staking_account_id: T::AccountId, stake: Balance<T>) -> Result<(), Error<T>> {
+        pub fn announce_candidacy(origin, membership_id: T::MembershipId, staking_account_id: T::AccountId, reward_account_id: T::AccountId, stake: Balance<T>) -> Result<(), Error<T>> {
             // ensure action can be started
-            let (stage_data, previous_staking_account_id) = EnsureChecks::<T>::can_announce_candidacy(origin, &membership_id, &staking_account_id, &stake)?;
+            let (stage_data, previous_staking_account_id) = EnsureChecks::<T>::can_announce_candidacy(origin, &membership_id, &staking_account_id, &reward_account_id, &stake)?;
 
             // prepare candidate
-            let candidate = Self::prepare_new_candidate(staking_account_id, stake);
+            let candidate = Self::prepare_new_candidate(staking_account_id, reward_account_id, stake);
 
             //
             // == MUTATION SAFE ==
@@ -433,14 +489,36 @@ decl_module! {
 
             Ok(())
         }
+
+        /// Sets the budget balance.
+        #[weight = 10_000_000]
+        pub fn set_budget(origin, balance: Balance<T>) -> Result<(), Error<T>> {
+            // ensure action can be started
+            EnsureChecks::<T>::can_set_budget(origin)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // update state
+            Mutations::<T>::set_budget(&balance);
+
+            // emit event
+            Self::deposit_event(RawEvent::BudgetBalanceSet(balance));
+
+            Ok(())
+        }
     }
 }
 
 /////////////////// Inner logic ////////////////////////////////////////////////
 
 impl<T: Trait> Module<T> {
+    /////////////////// Lifetime ///////////////////////////////////////////
+
     /// Checkout expire of referendum stage.
     fn try_progress_stage(now: T::BlockNumber) {
+        // election progress
         match Stage::<T>::get().stage {
             CouncilStage::Announcing(stage_data) => {
                 if now
@@ -458,6 +536,24 @@ impl<T: Trait> Module<T> {
         }
     }
 
+    /// Checkout elected council members reward payments.
+    fn try_process_budget(now: T::BlockNumber) {
+        // budget autorefill
+        let next_refill = NextBudgetRefill::<T>::get();
+        if next_refill == 0.into() {
+            // budget automatic refill initialization (this condition will be true only first time autorefill is planned)
+            Mutations::<T>::plan_budget_refill(now);
+        } else if next_refill == now {
+            // budget automatic refill
+            Mutations::<T>::refill_budget(now);
+        }
+
+        // council members rewards
+        if now == NextRewardPayments::<T>::get() {
+            Self::pay_elected_member_rewards(now);
+        }
+    }
+
     /// Finish voting and start ravealing.
     fn end_announcement_period(stage_data: CouncilStageAnnouncing) {
         let candidate_count = T::CouncilSize::get() + T::MinNumberOfExtraCandidates::get();
@@ -492,6 +588,8 @@ impl<T: Trait> Module<T> {
             return;
         }
 
+        let now: T::BlockNumber = <system::Module<T>>::block_number();
+
         // prepare candidates that got elected
         let elected_members: Vec<CouncilMemberOf<T>> = winners
             .iter()
@@ -499,10 +597,10 @@ impl<T: Trait> Module<T> {
                 let membership_id = item.option_id.into();
                 let candidate = Candidates::<T>::get(membership_id);
 
-                // clear candidate record
-                Candidates::<T>::remove(membership_id);
+                // clear candidate record and unlock their candidacy stake
+                Mutations::<T>::clear_candidate(&membership_id, &candidate);
 
-                (candidate, membership_id).into()
+                (candidate, membership_id, now, 0.into()).into()
             })
             .collect();
         // prepare council users for event
@@ -512,7 +610,7 @@ impl<T: Trait> Module<T> {
             .collect();
 
         // update state
-        Mutations::<T>::elect_new_council(elected_members.as_slice());
+        Mutations::<T>::elect_new_council(elected_members.as_slice(), now);
 
         // emit event
         Self::deposit_event(RawEvent::NewCouncilElected(elected_council_users));
@@ -527,13 +625,84 @@ impl<T: Trait> Module<T> {
         Self::deposit_event(RawEvent::AnnouncingPeriodStarted());
     }
 
+    /////////////////// Budget-related /////////////////////////////////////
+
+    /// Pay rewards to elected council members.
+    fn pay_elected_member_rewards(now: T::BlockNumber) {
+        let reward_per_block = T::ElectedMemberRewardPerBlock::get();
+        let starting_balance = Budget::<T>::get();
+
+        // pay reward to all council members
+        let new_balance = CouncilMembers::<T>::get().iter().enumerate().fold(
+            starting_balance,
+            |balance, (member_index, council_member)| {
+                // calculate unpaid reward
+                let unpaid_reward =
+                    Calculations::<T>::get_current_reward(&council_member, reward_per_block, now);
+
+                if unpaid_reward == 0.into() {
+                    Self::deposit_event(RawEvent::RewardPayment(
+                        council_member.membership_id,
+                        council_member.reward_account_id.clone(),
+                        0.into(),
+                        0.into(),
+                    ));
+                    return balance;
+                }
+
+                // stop iterating if budget is completely depleted
+                if balance == 0.into() {
+                    // emit event
+                    Self::deposit_event(RawEvent::RewardPayment(
+                        council_member.membership_id,
+                        council_member.reward_account_id.clone(),
+                        0.into(),
+                        unpaid_reward,
+                    ));
+                    return balance;
+                }
+
+                // calculate withdrawable balance
+                let (available_balance, missing_balance) =
+                    Calculations::<T>::payable_reward(&balance, &unpaid_reward);
+
+                // pay reward
+                Mutations::<T>::pay_reward(
+                    member_index,
+                    &council_member.reward_account_id,
+                    &available_balance,
+                    &missing_balance,
+                    &now,
+                );
+
+                // emit event
+                Self::deposit_event(RawEvent::RewardPayment(
+                    council_member.membership_id,
+                    council_member.reward_account_id.clone(),
+                    available_balance,
+                    missing_balance,
+                ));
+
+                // return new balance
+                balance - available_balance
+            },
+        );
+
+        // update state
+        Mutations::<T>::finish_reward_payments(new_balance, now);
+    }
+
+    /////////////////// Utils //////////////////////////////////////////////////
+
     /// Construct a new candidate for council election.
     fn prepare_new_candidate(
         staking_account_id: T::AccountId,
+        reward_account_id: T::AccountId,
         stake: Balance<T>,
     ) -> CandidateOf<T> {
         Candidate {
             staking_account_id,
+            reward_account_id,
             cycle_id: AnnouncementPeriodNr::get(),
             stake,
             note_hash: None,
@@ -543,17 +712,13 @@ impl<T: Trait> Module<T> {
 
 impl<T: Trait> ReferendumConnection<T> for Module<T> {
     /// Process candidates' results recieved from the referendum.
-    fn recieve_referendum_results(
-        winners: &[OptionResult<VotePowerOf<T>>],
-    ) -> Result<(), Error<T>> {
+    fn recieve_referendum_results(winners: &[OptionResult<VotePowerOf<T>>]) {
         //
         // == MUTATION SAFE ==
         //
 
         // conclude election
         Self::end_election_period(winners);
-
-        Ok(())
     }
 
     /// Check that it is a proper time to release stake.
@@ -602,6 +767,46 @@ impl<T: Trait> ReferendumConnection<T> for Module<T> {
     }
 }
 
+/////////////////// Calculations ///////////////////////////////////////////////
+
+struct Calculations<T: Trait> {
+    _dummy: PhantomData<T>, // 0-sized data meant only to bound generic parameters
+}
+
+impl<T: Trait> Calculations<T> {
+    /// Calculate current reward for the recipient.
+    fn get_current_reward(
+        council_member: &CouncilMemberOf<T>,
+        reward_per_block: Balance<T>,
+        now: T::BlockNumber,
+    ) -> Balance<T> {
+        // calculate currently unpaid reward for elected council member
+        // previously_unpaid_reward + (current_block_number - last_payment_block_number) * reward_per_block
+        council_member.unpaid_reward.saturating_add(
+            now.saturating_sub(council_member.last_payment_block)
+                .saturated_into()
+                .saturating_mul(reward_per_block.saturated_into())
+                .saturated_into(),
+        )
+    }
+
+    /// Retrieve current budget's balance and calculate missing balance for reward payment.
+    fn payable_reward(
+        budget_balance: &Balance<T>,
+        reward_amount: &Balance<T>,
+    ) -> (Balance<T>, Balance<T>) {
+        // check if reward has enough balance
+        if reward_amount <= budget_balance {
+            return (*reward_amount, 0.into());
+        }
+
+        // calculate missing balance
+        let missing_balance = reward_amount.saturating_sub(*budget_balance);
+
+        (*budget_balance, missing_balance)
+    }
+}
+
 /////////////////// Mutations //////////////////////////////////////////////////
 
 struct Mutations<T: Trait> {
@@ -609,6 +814,8 @@ struct Mutations<T: Trait> {
 }
 
 impl<T: Trait> Mutations<T> {
+    /////////////////// Election-related ///////////////////////////////////
+
     /// Change the council stage to candidacy announcing stage.
     fn start_announcing_period() {
         let stage_data = CouncilStageAnnouncing {
@@ -646,7 +853,7 @@ impl<T: Trait> Mutations<T> {
     }
 
     /// Elect new council after successful election.
-    fn elect_new_council(elected_members: &[CouncilMemberOf<T>]) {
+    fn elect_new_council(elected_members: &[CouncilMemberOf<T>], now: T::BlockNumber) {
         let block_number = <system::Module<T>>::block_number();
 
         // change council state
@@ -657,29 +864,22 @@ impl<T: Trait> Mutations<T> {
             }
         });
 
+        // try to pay any unpaid rewards (any unpaid rewards after this will be discarded call)
+        Module::<T>::pay_elected_member_rewards(now);
+
         // release stakes for previous council members
-        CouncilMembers::<T>::get()
-            .iter()
-            .for_each(|council_member| {
-                T::ElectedMemberLock::unlock(&council_member.staking_account_id);
-            });
+        for council_member in CouncilMembers::<T>::get() {
+            T::ElectedMemberLock::unlock(&council_member.staking_account_id);
+        }
 
         // set new council
         CouncilMembers::<T>::put(elected_members.to_vec());
 
-        // setup elected member lock to new council's members
-        CouncilMembers::<T>::get()
-            .iter()
-            .for_each(|council_member| {
-                // unlock candidacy stake
-                T::CandidacyLock::unlock(&council_member.staking_account_id);
-
-                // lock council member stake
-                T::ElectedMemberLock::lock(
-                    &council_member.staking_account_id,
-                    council_member.stake,
-                );
-            });
+        // setup elected member lock for new council's members
+        for council_member in CouncilMembers::<T>::get() {
+            // lock council member stake
+            T::ElectedMemberLock::lock(&council_member.staking_account_id, council_member.stake);
+        }
     }
 
     /// Announce user's candidacy.
@@ -724,6 +924,71 @@ impl<T: Trait> Mutations<T> {
     fn set_candidacy_note(membership_id: &T::MembershipId, note_hash: &T::Hash) {
         Candidates::<T>::mutate(membership_id, |value| value.note_hash = Some(*note_hash));
     }
+
+    /// Removes member's candidacy record.
+    fn clear_candidate(membership_id: &T::MembershipId, candidate: &CandidateOf<T>) {
+        // unlock candidacy stake
+        T::CandidacyLock::unlock(&candidate.staking_account_id);
+
+        // clear candidate record
+        Candidates::<T>::remove(membership_id);
+    }
+
+    /////////////////// Budget-related /////////////////////////////////////////
+
+    /// Set budget balance
+    fn set_budget(balance: &Balance<T>) {
+        Budget::<T>::put(balance);
+    }
+
+    /// Refill budget's balance.
+    fn refill_budget(now: T::BlockNumber) {
+        let refill_amount = T::BudgetRefillAmount::get();
+
+        Budget::<T>::mutate(|balance| *balance += refill_amount);
+        Self::plan_budget_refill(now);
+    }
+
+    // Plan next budget refill.
+    fn plan_budget_refill(now: T::BlockNumber) {
+        let refill_period = T::BudgetRefillPeriod::get();
+
+        NextBudgetRefill::<T>::put(now + refill_period);
+    }
+
+    /// Pay reward to a single elected council member.
+    fn pay_reward(
+        member_index: usize,
+        account_id: &T::AccountId,
+        amount: &Balance<T>,
+        missing_balance: &Balance<T>,
+        now: &T::BlockNumber,
+    ) {
+        // mint tokens into reward account
+        <<<T as Trait>::Referendum as ReferendumManager<
+            <T as system::Trait>::Origin,
+            <T as system::Trait>::AccountId,
+            <T as system::Trait>::Hash,
+        >>::Currency as Currency<<T as system::Trait>::AccountId>>::deposit_creating(
+            account_id, *amount,
+        );
+
+        // update elected council member
+        CouncilMembers::<T>::mutate(|members| {
+            members[member_index].last_payment_block = *now;
+            members[member_index].unpaid_reward = *missing_balance;
+        });
+    }
+
+    /// Save reward-payments-related changes and plan the next reward payout.
+    fn finish_reward_payments(new_balance: Balance<T>, now: T::BlockNumber) {
+        // update budget's balance
+        Budget::<T>::put(new_balance);
+
+        // plan next rewards payment
+        let next_reward_block = now + T::ElectedMemberRewardPeriod::get();
+        NextRewardPayments::<T>::put(next_reward_block);
+    }
 }
 
 /////////////////// Ensure checks //////////////////////////////////////////////
@@ -756,6 +1021,7 @@ impl<T: Trait> EnsureChecks<T> {
         origin: T::Origin,
         membership_id: &T::MembershipId,
         staking_account_id: &T::AccountId,
+        reward_account_id: &T::AccountId,
         stake: &Balance<T>,
     ) -> Result<(CouncilStageAnnouncing, Option<T::AccountId>), Error<T>> {
         // ensure user's membership
@@ -771,6 +1037,10 @@ impl<T: Trait> EnsureChecks<T> {
             return Err(Error::ConflictingStake);
         }
 
+        if !T::is_council_member_account(&membership_id, &reward_account_id) {
+            return Err(Error::MembershipIdNotMatchAccount);
+        }
+
         let stage_data = match Stage::<T>::get().stage {
             CouncilStage::Announcing(stage_data) => stage_data,
             _ => return Err(Error::CantCandidateNow),
@@ -884,4 +1154,11 @@ impl<T: Trait> EnsureChecks<T> {
 
         Ok(())
     }
+
+    // Ensures there is no problem in setting the budget balance.
+    fn can_set_budget(origin: T::Origin) -> Result<(), Error<T>> {
+        ensure_root(origin)?;
+
+        Ok(())
+    }
 }

+ 65 - 11
runtime-modules/council/src/mock.rs

@@ -2,8 +2,8 @@
 
 /////////////////// Configuration //////////////////////////////////////////////
 use crate::{
-    AnnouncementPeriodNr, Balance, CandidateOf, Candidates, CouncilMemberOf, CouncilMembers,
-    CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate,
+    AnnouncementPeriodNr, Balance, Budget, CandidateOf, Candidates, CouncilMemberOf,
+    CouncilMembers, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate,
     CouncilStageUpdateOf, Error, GenesisConfig, Module, ReferendumConnection, Stage, Trait,
 };
 
@@ -51,11 +51,15 @@ pub struct Runtime;
 parameter_types! {
     pub const MinNumberOfExtraCandidates: u64 = 1;
     pub const AnnouncingPeriodDuration: u64 = 15;
-    pub const IdlePeriodDuration: u64 = 17;
+    pub const IdlePeriodDuration: u64 = 27;
     pub const CouncilSize: u64 = 3;
     pub const MinCandidateStake: u64 = 11000;
     pub const CandidacyLockId: LockIdentifier = *b"council1";
     pub const ElectedMemberLockId: LockIdentifier = *b"council2";
+    pub const ElectedMemberRewardPerBlock: u64 = 100;
+    pub const ElectedMemberRewardPeriod: u64 = 10;
+    pub const BudgetRefillAmount: u64 = 1000;
+    pub const BudgetRefillPeriod: u64 = 1000; // intentionally high number that prevents side-effecting tests other than  budget refill tests
 }
 
 impl Trait for Runtime {
@@ -73,6 +77,12 @@ impl Trait for Runtime {
     type CandidacyLock = Lock1;
     type ElectedMemberLock = Lock2;
 
+    type ElectedMemberRewardPerBlock = ElectedMemberRewardPerBlock;
+    type ElectedMemberRewardPeriod = ElectedMemberRewardPeriod;
+
+    type BudgetRefillAmount = BudgetRefillAmount;
+    type BudgetRefillPeriod = BudgetRefillPeriod;
+
     fn is_council_member_account(
         membership_id: &Self::MembershipId,
         account_id: &<Self as system::Trait>::AccountId,
@@ -190,7 +200,7 @@ impl referendum::Trait<ReferendumInstance> for RuntimeReferendum {
 
     type MinimumStake = MinimumVotingStake;
 
-    fn caclulate_vote_power(
+    fn calculate_vote_power(
         account_id: &<Self as system::Trait>::AccountId,
         stake: &BalanceReferendum<Self, ReferendumInstance>,
     ) -> Self::VotePower {
@@ -228,8 +238,7 @@ impl referendum::Trait<ReferendumInstance> for RuntimeReferendum {
             .collect();
         <Module<Runtime> as ReferendumConnection<Runtime>>::recieve_referendum_results(
             tmp_winners.as_slice(),
-        )
-        .unwrap();
+        );
 
         INTERMEDIATE_RESULTS.with(|value| value.replace(BTreeMap::new()));
     }
@@ -342,9 +351,10 @@ pub struct CouncilSettings<T: Trait> {
     pub voting_stage_duration: T::BlockNumber,
     pub reveal_stage_duration: T::BlockNumber,
     pub idle_stage_duration: T::BlockNumber,
-
     pub election_duration: T::BlockNumber,
     pub cycle_duration: T::BlockNumber,
+    pub budget_refill_amount: Balance<T>,
+    pub budget_refill_period: T::BlockNumber,
 }
 
 impl<T: Trait> CouncilSettings<T>
@@ -380,6 +390,9 @@ where
                 + announcing_stage_duration
                 + voting_stage_duration
                 + idle_stage_duration,
+
+            budget_refill_amount: <T as Trait>::BudgetRefillAmount::get(),
+            budget_refill_period: <T as Trait>::BudgetRefillPeriod::get(),
         }
     }
 }
@@ -491,6 +504,7 @@ where
         let origin = OriginType::Signed(account_id.into());
         let candidate = CandidateOf::<T> {
             staking_account_id: account_id.into(),
+            reward_account_id: account_id.into(),
             cycle_id: AnnouncementPeriodNr::get(),
             stake,
             note_hash: None,
@@ -700,7 +714,8 @@ where
             Module::<T>::announce_candidacy(
                 InstanceMockUtils::<T>::mock_origin(origin),
                 member_id,
-                member_id.into(),
+                member_id.into(), // use member id as staking account
+                member_id.into(), // use member id as reward account
                 stake
             ),
             expected_result,
@@ -758,6 +773,24 @@ where
         );
     }
 
+    pub fn set_budget(
+        origin: OriginType<T::AccountId>,
+        amount: Balance<T>,
+        expected_result: Result<(), ()>,
+    ) {
+        // check method returns expected result
+        assert_eq!(
+            Module::<T>::set_budget(InstanceMockUtils::<T>::mock_origin(origin), amount,).is_ok(),
+            expected_result.is_ok(),
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+
+        assert_eq!(Budget::<T>::get(), amount,);
+    }
+
     /// simulate one council's election cycle
     pub fn simulate_council_cycle(params: CouncilCycleParams<T>) {
         let settings = params.council_settings;
@@ -868,6 +901,9 @@ where
                 + settings.voting_stage_duration,
         );
         Self::check_council_members(params.expected_final_council_members.clone());
+
+        // finish idle period
+        InstanceMockUtils::<T>::increase_block_number(settings.idle_stage_duration.into() + 1);
     }
 
     /// Simulate one full round of council lifecycle (announcing, election, idle). Use it to quickly test behavior in 2nd, 3rd, etc. cycle.
@@ -898,9 +934,27 @@ where
             .collect();
 
         let expected_final_council_members: Vec<CouncilMemberOf<T>> = vec![
-            (candidates[3].candidate.clone(), candidates[3].membership_id).into(),
-            (candidates[0].candidate.clone(), candidates[0].membership_id).into(),
-            (candidates[1].candidate.clone(), candidates[1].membership_id).into(),
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                start_block_number + council_settings.election_duration - 1.into(),
+                0.into(),
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                start_block_number + council_settings.election_duration - 1.into(),
+                0.into(),
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                start_block_number + council_settings.election_duration - 1.into(),
+                0.into(),
+            )
+                .into(),
         ];
 
         // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A, and 2 vote for option B, and 1 for option C

+ 329 - 32
runtime-modules/council/src/tests.rs

@@ -1,9 +1,12 @@
 #![cfg(test)]
 
-use super::{CouncilMemberOf, CouncilStageAnnouncing, Error, Module, Trait};
+use super::{
+    Budget, CouncilMemberOf, CouncilMembers, CouncilStageAnnouncing, Error, Module, Trait,
+};
 use crate::mock::*;
 use crate::staking_handler::mocks::{CANDIDATE_BASE_ID, VOTER_CANDIDATE_OFFSET};
 use crate::staking_handler::StakingHandler2;
+use frame_support::StorageValue;
 
 type Mocks = InstanceMocks<Runtime>;
 type MockUtils = InstanceMockUtils<Runtime>;
@@ -11,14 +14,14 @@ type MockUtils = InstanceMockUtils<Runtime>;
 type CandidacyLock = <Runtime as Trait>::CandidacyLock;
 type ElectedMemberLock = <Runtime as Trait>::ElectedMemberLock;
 
-/////////////////// Lifetime - election cycle start ////////////////////////////
+/////////////////// Election-related ///////////////////////////////////////////
 /// Test one referendum cycle with succesfull council election
 #[test]
 fn council_lifecycle() {
     let config = default_genesis_config();
 
     build_test_externalities(config).execute_with(|| {
-        Mocks::run_full_council_cycle(0, &vec![], 0);
+        Mocks::run_full_council_cycle(0, &[], 0);
     });
 }
 
@@ -169,9 +172,6 @@ fn council_vote_for_winner_stakes_longer() {
         Mocks::release_vote_stake(voter_for_winner.origin.clone(), Err(()));
         Mocks::release_vote_stake(voter_for_looser.origin.clone(), Ok(()));
 
-        // forward past idle stage
-        MockUtils::increase_block_number(council_settings.idle_stage_duration + 1);
-
         // try to release vote stake
         Mocks::release_vote_stake(voter_for_winner.origin.clone(), Err(()));
 
@@ -343,9 +343,27 @@ fn council_two_consecutive_rounds() {
             .collect();
 
         let expected_final_council_members: Vec<CouncilMemberOf<Runtime>> = vec![
-            (candidates[3].candidate.clone(), candidates[3].membership_id).into(),
-            (candidates[0].candidate.clone(), candidates[0].membership_id).into(),
-            (candidates[1].candidate.clone(), candidates[1].membership_id).into(),
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
         ];
 
         // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A, and 2 vote for option B, and 1 for option C
@@ -374,9 +392,6 @@ fn council_two_consecutive_rounds() {
 
         Mocks::simulate_council_cycle(params.clone());
 
-        // forward to idle period
-        MockUtils::increase_block_number(council_settings.idle_stage_duration + 1);
-
         let votes_map2: Vec<u64> = vec![3, 3, 3, 3, 1, 1, 2];
         let voters2 = (0..votes_map2.len())
             .map(|index| {
@@ -389,9 +404,27 @@ fn council_two_consecutive_rounds() {
             .collect();
 
         let expected_final_council_members2: Vec<CouncilMemberOf<Runtime>> = vec![
-            (candidates[3].candidate.clone(), candidates[3].membership_id).into(),
-            (candidates[1].candidate.clone(), candidates[1].membership_id).into(),
-            (candidates[2].candidate.clone(), candidates[2].membership_id).into(),
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[2].candidate.clone(),
+                candidates[2].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
         ];
 
         let params2 = CouncilCycleParams {
@@ -499,9 +532,27 @@ fn council_candidate_stake_can_be_unlocked() {
             .collect();
 
         let expected_final_council_members: Vec<CouncilMemberOf<Runtime>> = vec![
-            (candidates[3].candidate.clone(), candidates[3].membership_id).into(),
-            (candidates[0].candidate.clone(), candidates[0].membership_id).into(),
-            (candidates[1].candidate.clone(), candidates[1].membership_id).into(),
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
         ];
 
         // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A, and 2 vote for option B, and 1 for option C
@@ -587,9 +638,27 @@ fn council_candidate_stake_automaticly_converted() {
             .collect();
 
         let expected_final_council_members: Vec<CouncilMemberOf<Runtime>> = vec![
-            (candidates[3].candidate.clone(), candidates[3].membership_id).into(),
-            (candidates[0].candidate.clone(), candidates[0].membership_id).into(),
-            (candidates[1].candidate.clone(), candidates[1].membership_id).into(),
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
         ];
 
         // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A, and 2 vote for option B, and 1 for option C
@@ -659,9 +728,27 @@ fn council_member_stake_is_locked() {
             .collect();
 
         let expected_final_council_members: Vec<CouncilMemberOf<Runtime>> = vec![
-            (candidates[3].candidate.clone(), candidates[3].membership_id).into(),
-            (candidates[0].candidate.clone(), candidates[0].membership_id).into(),
-            (candidates[1].candidate.clone(), candidates[1].membership_id).into(),
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
         ];
 
         // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A, and 2 vote for option B, and 1 for option C
@@ -712,7 +799,7 @@ fn council_member_stake_automaticly_unlocked() {
             <RuntimeReferendum as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
         let not_reelected_candidate_index = 0;
 
-        let params = Mocks::run_full_council_cycle(0, &vec![], 0);
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
 
         let candidates = params.candidates_announcing.clone();
 
@@ -722,9 +809,6 @@ fn council_member_stake_automaticly_unlocked() {
             council_settings.min_candidate_stake,
         );
 
-        // forward to idle period
-        MockUtils::increase_block_number(council_settings.idle_stage_duration + 1);
-
         let votes_map2: Vec<u64> = vec![3, 3, 3, 3, 1, 1, 2];
         let voters2 = (0..votes_map2.len())
             .map(|index| {
@@ -737,9 +821,27 @@ fn council_member_stake_automaticly_unlocked() {
             .collect();
 
         let expected_final_council_members2: Vec<CouncilMemberOf<Runtime>> = vec![
-            (candidates[3].candidate.clone(), candidates[3].membership_id).into(),
-            (candidates[1].candidate.clone(), candidates[1].membership_id).into(),
-            (candidates[2].candidate.clone(), candidates[2].membership_id).into(),
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[2].candidate.clone(),
+                candidates[2].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
         ];
 
         let params2 = CouncilCycleParams {
@@ -888,7 +990,7 @@ fn council_repeated_candidacy_unstakes() {
         let not_elected_candidate_index = 2;
 
         // run one council cycle
-        let params = Mocks::run_full_council_cycle(0, &vec![], 0);
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
 
         // forward to next candidacy announcing period
         MockUtils::increase_block_number(council_settings.idle_stage_duration + 1);
@@ -913,3 +1015,198 @@ fn council_repeated_candidacy_unstakes() {
         Mocks::check_announcing_stake(&candidate.membership_id, new_stake);
     });
 }
+
+/////////////////// Budget-related /////////////////////////////////////////////
+
+/// Test that budget balance can be set from external source.
+#[test]
+fn council_budget_can_be_set() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let balances = [100, 500, 300];
+        let origin = OriginType::Root;
+
+        for balance in &balances {
+            Mocks::set_budget(origin.clone(), *balance, Ok(()));
+        }
+    })
+}
+
+/// Test that rewards for council members are paid.
+#[test]
+fn council_rewards_are_paid() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let origin = OriginType::Root;
+
+        let sufficient_balance = 10000000;
+
+        Mocks::set_budget(origin.clone(), sufficient_balance, Ok(()));
+
+        // run 1st council cycle
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
+
+        // calculate council member last reward block
+        let last_payment_block = council_settings.cycle_duration
+            + (<Runtime as Trait>::ElectedMemberRewardPeriod::get()
+                - (council_settings.idle_stage_duration
+                    % <Runtime as Trait>::ElectedMemberRewardPeriod::get()))
+            - 1; // -1 because current block is not finalized yet
+        let tmp_council_members: Vec<CouncilMemberOf<Runtime>> = params
+            .expected_final_council_members
+            .iter()
+            .map(|council_member| CouncilMemberOf::<Runtime> {
+                last_payment_block,
+                ..*council_member
+            })
+            .collect();
+
+        // run 2nd council cycle
+        Mocks::run_full_council_cycle(
+            council_settings.cycle_duration,
+            &tmp_council_members.as_slice(),
+            0,
+        );
+    });
+}
+
+/// Test that any rewards missed due to insufficient budget balance will be paid off eventually.
+#[test]
+fn council_missed_rewards_are_paid_later() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let origin = OriginType::Root;
+        let reward_period = <Runtime as Trait>::ElectedMemberRewardPeriod::get();
+
+        let insufficient_balance = 0;
+        let sufficient_balance = 10000000;
+
+        // set empty budget
+        Mocks::set_budget(origin.clone(), insufficient_balance, Ok(()));
+
+        // run 1st council cycle
+        Mocks::run_full_council_cycle(0, &[], 0);
+
+        // forward to block after first reward payment
+        MockUtils::increase_block_number(<Runtime as Trait>::ElectedMemberRewardPeriod::get());
+
+        let last_payment_block = council_settings.cycle_duration
+            + (reward_period
+                - (council_settings.idle_stage_duration
+                    % <Runtime as Trait>::ElectedMemberRewardPeriod::get()))
+            - 1; // -1 because current block is not finalized yet
+
+        // check unpaid rewards were discarded
+        for council_member in CouncilMembers::<Runtime>::get() {
+            assert_eq!(council_member.unpaid_reward, 0,);
+            assert_eq!(
+                council_member.last_payment_block,
+                //last_payment_block + reward_period,
+                council_settings.election_duration - 1, // -1 because council was elected (last_payment_block set) in on_finalize
+            );
+        }
+
+        // set sufficitent budget
+        Mocks::set_budget(origin.clone(), sufficient_balance, Ok(()));
+
+        // forward to block after second reward payment
+        MockUtils::increase_block_number(<Runtime as Trait>::ElectedMemberRewardPeriod::get());
+
+        // check unpaid rewards were discarded
+        for council_member in CouncilMembers::<Runtime>::get() {
+            assert_eq!(council_member.unpaid_reward, 0,);
+            assert_eq!(
+                council_member.last_payment_block,
+                last_payment_block + 2 * reward_period,
+            );
+        }
+    });
+}
+
+/// Test that any unpaid rewards will be discarded on council depose if budget is still insufficient.
+#[test]
+fn council_discard_remaining_rewards_on_depose() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let origin = OriginType::Root;
+
+        let sufficient_balance = 10000000;
+        let second_cycle_user_offset = 10;
+
+        Mocks::set_budget(origin.clone(), sufficient_balance, Ok(()));
+
+        // run 1st council cycle
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
+
+        // calculate council member last reward block
+        let last_payment_block = council_settings.cycle_duration
+            + (<Runtime as Trait>::ElectedMemberRewardPeriod::get()
+                - (council_settings.idle_stage_duration
+                    % <Runtime as Trait>::ElectedMemberRewardPeriod::get()))
+            - 1; // -1 because current block is not finalized yet
+        let tmp_council_members: Vec<CouncilMemberOf<Runtime>> = params
+            .expected_final_council_members
+            .iter()
+            .map(|council_member| CouncilMemberOf::<Runtime> {
+                last_payment_block,
+                ..*council_member
+            })
+            .collect();
+
+        // check unpaid rewards were discarded
+        for council_member in CouncilMembers::<Runtime>::get() {
+            assert_eq!(council_member.unpaid_reward, 0,);
+        }
+
+        // run 2nd council cycle
+        Mocks::run_full_council_cycle(
+            council_settings.cycle_duration,
+            &tmp_council_members.as_slice(),
+            second_cycle_user_offset,
+        );
+
+        // check unpaid rewards were discarded
+        for council_member in CouncilMembers::<Runtime>::get() {
+            assert_eq!(council_member.unpaid_reward, 0,);
+        }
+    });
+}
+
+/// Test that budget is periodicly refilled.
+#[test]
+fn council_budget_auto_refill() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let start_balance = Budget::<Runtime>::get();
+
+        // forward before next refill
+        MockUtils::increase_block_number(council_settings.budget_refill_period - 1);
+
+        assert_eq!(Budget::<Runtime>::get(), start_balance,);
+
+        // forward to next filling
+        MockUtils::increase_block_number(1);
+
+        assert_eq!(
+            Budget::<Runtime>::get(),
+            start_balance + council_settings.budget_refill_amount,
+        );
+
+        // forward to next filling
+        MockUtils::increase_block_number(council_settings.budget_refill_period);
+
+        assert_eq!(
+            Budget::<Runtime>::get(),
+            start_balance + 2 * council_settings.budget_refill_amount,
+        );
+    });
+}

+ 2 - 2
runtime-modules/referendum/src/lib.rs

@@ -187,7 +187,7 @@ pub trait Trait<I: Instance>: system::Trait {
     type MinimumStake: Get<Balance<Self, I>>;
 
     /// Calculate the vote's power for user and his stake.
-    fn caclulate_vote_power(
+    fn calculate_vote_power(
         account_id: &<Self as system::Trait>::AccountId,
         stake: &Balance<Self, I>,
     ) -> <Self as Trait<I>>::VotePower;
@@ -581,7 +581,7 @@ impl<T: Trait<I>, I: Instance> Mutations<T, I> {
         cast_vote: CastVoteOf<T, I>,
     ) -> Result<(), Error<T, I>> {
         // prepare new values
-        let vote_power = T::caclulate_vote_power(&account_id, &cast_vote.stake);
+        let vote_power = T::calculate_vote_power(&account_id, &cast_vote.stake);
         let option_result = OptionResult {
             option_id: *option_id,
             vote_power,

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

@@ -79,7 +79,7 @@ impl Trait<Instance0> for Runtime {
 
     type MinimumStake = MinimumStake;
 
-    fn caclulate_vote_power(
+    fn calculate_vote_power(
         account_id: &<Self as system::Trait>::AccountId,
         stake: &Balance<Self, Instance0>,
     ) -> <Self as Trait<Instance0>>::VotePower {