|
@@ -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(())
|
|
|
+ }
|
|
|
}
|