Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion runtime-modules/governance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,9 @@ rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
[dependencies.minting]
default_features = false
package = 'substrate-token-mint-module'
path = '../token-minting'
path = '../token-minting'

[dependencies.recurringrewards]
default_features = false
package = 'substrate-recurring-reward-module'
path = '../recurring-reward'
178 changes: 169 additions & 9 deletions runtime-modules/governance/src/council.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use rstd::prelude::*;
use sr_primitives::traits::Zero;
use srml_support::{decl_event, decl_module, decl_storage, ensure};
use sr_primitives::traits::{One, Zero};
use srml_support::{debug, decl_event, decl_module, decl_storage, ensure};
use system::{self, ensure_root};

pub use super::election::{self, CouncilElected, Seat, Seats};
Expand All @@ -21,7 +21,7 @@ impl<X: CouncilTermEnded> CouncilTermEnded for (X,) {
}
}

pub trait Trait: system::Trait + minting::Trait + GovernanceCurrency {
pub trait Trait: system::Trait + recurringrewards::Trait + GovernanceCurrency {
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;

type CouncilTermEnded: CouncilTermEnded;
Expand All @@ -37,6 +37,20 @@ decl_storage! {
/// because it was introduced in a runtime upgrade. It will be automatically created when
/// a successful call to set_council_mint_capacity() is made.
pub CouncilMint get(council_mint) : Option<<T as minting::Trait>::MintId>;

/// The reward relationships currently in place. There may not necessarily be a 1-1 correspondance with
/// the active council, since there are multiple ways of setting/adding/removing council members, some of which
/// do not involve creating a relationship.
pub RewardRelationships get(reward_relationships) : map T::AccountId => T::RewardRelationshipId;

/// Reward amount paid out at each PayoutInterval
pub AmountPerPayout get(amount_per_payout): minting::BalanceOf<T>;

/// Optional interval in blocks on which a reward payout will be made to each council member
pub PayoutInterval get(payout_interval): Option<T::BlockNumber>;

/// How many blocks after the reward is created, the first payout will be made
pub FirstPayoutAfterRewardCreated get(first_payout_after_reward_created): T::BlockNumber;
}
}

Expand All @@ -50,10 +64,23 @@ decl_event!(

impl<T: Trait> CouncilElected<Seats<T::AccountId, BalanceOf<T>>, T::BlockNumber> for Module<T> {
fn council_elected(seats: Seats<T::AccountId, BalanceOf<T>>, term: T::BlockNumber) {
<ActiveCouncil<T>>::put(seats);
<ActiveCouncil<T>>::put(seats.clone());

let next_term_ends_at = <system::Module<T>>::block_number() + term;

<TermEndsAt<T>>::put(next_term_ends_at);

if let Some(reward_source) = Self::council_mint() {
for seat in seats.iter() {
Self::add_reward_relationship(&seat.member, reward_source);
}
} else {
// Skip trying to create rewards since no mint has been created yet
debug::warn!(
"Not creating reward relationship for council seats because no mint exists"
);
}

Self::deposit_event(RawEvent::NewCouncilTermStarted(next_term_ends_at));
}
}
Expand All @@ -75,6 +102,51 @@ impl<T: Trait> Module<T> {
CouncilMint::<T>::put(mint_id);
Ok(mint_id)
}

fn add_reward_relationship(destination: &T::AccountId, reward_source: T::MintId) {
let recipient = <recurringrewards::Module<T>>::add_recipient();

// When calculating when first payout occurs, add minimum of one block interval to ensure rewards module
// has a chance to execute its on_finalize routine.
let next_payout_at = system::Module::<T>::block_number()
+ Self::first_payout_after_reward_created()
+ T::BlockNumber::one();

if let Ok(relationship_id) = <recurringrewards::Module<T>>::add_reward_relationship(
reward_source,
recipient,
destination.clone(),
Self::amount_per_payout(),
next_payout_at,
Self::payout_interval(),
) {
RewardRelationships::<T>::insert(destination, relationship_id);
} else {
debug::warn!("Failed to create a reward relationship for council seat");
}
}

fn remove_reward_relationships() {
for seat in Self::active_council().into_iter() {
if RewardRelationships::<T>::exists(&seat.member) {
let id = Self::reward_relationships(&seat.member);
<recurringrewards::Module<T>>::remove_reward_relationship(id);
}
}
}

fn on_term_ended(now: T::BlockNumber) {
// Stop paying out rewards when the term ends.
// Note: Is it not simpler to just do a single payout at end of term?
// During the term the recurring reward module could unfairly pay some but not all council members
// If there is insufficient mint capacity.. so doing it at this point offers more control
// and a potentially more fair outcome in such a case.
Self::remove_reward_relationships();

Self::deposit_event(RawEvent::CouncilTermEnded(now));

T::CouncilTermEnded::council_term_ended();
}
}

decl_module! {
Expand All @@ -83,30 +155,49 @@ decl_module! {

fn on_finalize(now: T::BlockNumber) {
if now == Self::term_ends_at() {
Self::deposit_event(RawEvent::CouncilTermEnded(now));
T::CouncilTermEnded::council_term_ended();
Self::on_term_ended(now);
}
}

// Privileged methods

/// Force set a zero staked council. Stakes in existing council will vanish into thin air!
/// Force set a zero staked council. Stakes in existing council seats are not returned.
/// Existing council rewards are removed and new council members do NOT get any rewards.
/// Avoid using this call if possible, will be deprecated. The term of the new council is
/// not extended.
pub fn set_council(origin, accounts: Vec<T::AccountId>) {
ensure_root(origin)?;

// Council is being replaced so remove existing reward relationships if they exist
Self::remove_reward_relationships();

if let Some(reward_source) = Self::council_mint() {
for account in accounts.clone() {
Self::add_reward_relationship(&account, reward_source);
}
}

let new_council: Seats<T::AccountId, BalanceOf<T>> = accounts.into_iter().map(|account| {
Seat {
member: account,
stake: BalanceOf::<T>::zero(),
backers: vec![]
}
}).collect();

<ActiveCouncil<T>>::put(new_council);
}

/// Adds a zero staked council member
/// Adds a zero staked council member. A member added in this way does not get a recurring reward.
fn add_council_member(origin, account: T::AccountId) {
ensure_root(origin)?;

ensure!(!Self::is_councilor(&account), "cannot add same account multiple times");

if let Some(reward_source) = Self::council_mint() {
Self::add_reward_relationship(&account, reward_source);
}

let seat = Seat {
member: account,
stake: BalanceOf::<T>::zero(),
Expand All @@ -117,13 +208,22 @@ decl_module! {
<ActiveCouncil<T>>::mutate(|council| council.push(seat));
}

/// Remove a single council member and their reward.
fn remove_council_member(origin, account_to_remove: T::AccountId) {
ensure_root(origin)?;

ensure!(Self::is_councilor(&account_to_remove), "account is not a councilor");

if RewardRelationships::<T>::exists(&account_to_remove) {
let relationship_id = Self::reward_relationships(&account_to_remove);
<recurringrewards::Module<T>>::remove_reward_relationship(relationship_id);
}

let filtered_council: Seats<T::AccountId, BalanceOf<T>> = Self::active_council()
.into_iter()
.filter(|c| c.member != account_to_remove)
.collect();

<ActiveCouncil<T>>::put(filtered_council);
}

Expand Down Expand Up @@ -153,14 +253,34 @@ decl_module! {
if let Some(mint_id) = Self::council_mint() {
minting::Module::<T>::transfer_tokens(mint_id, amount, &destination)?;
} else {
return Err("CouncilHashNoMint")
return Err("CouncilHasNoMint")
}
}

/// Sets the council rewards which is only applied on new council being elected.
fn set_council_rewards(
origin,
amount_per_payout: minting::BalanceOf<T>,
payout_interval: Option<T::BlockNumber>,
first_payout_after_reward_created: T::BlockNumber
) {
ensure_root(origin)?;

AmountPerPayout::<T>::put(amount_per_payout);
FirstPayoutAfterRewardCreated::<T>::put(first_payout_after_reward_created);

if let Some(payout_interval) = payout_interval {
PayoutInterval::<T>::put(payout_interval);
} else {
PayoutInterval::<T>::take();
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::mock::*;
use srml_support::*;

Expand Down Expand Up @@ -212,4 +332,44 @@ mod tests {
assert!(Council::is_councilor(&6));
});
}

#[test]
fn council_elected_test() {
initial_test_ext().execute_with(|| {
// Ensure a mint is created so we can create rewards
assert_ok!(Council::set_council_mint_capacity(
system::RawOrigin::Root.into(),
1000
));

Council::council_elected(
vec![
Seat {
member: 5,
stake: 0,
backers: vec![],
},
Seat {
member: 6,
stake: 0,
backers: vec![],
},
Seat {
member: 7,
stake: 0,
backers: vec![],
},
],
50 as u64, // <Test as system::Trait>::BlockNumber::from(50)
);

assert!(Council::is_councilor(&5));
assert!(Council::is_councilor(&6));
assert!(Council::is_councilor(&7));

assert!(RewardRelationships::<Test>::exists(&5));
assert!(RewardRelationships::<Test>::exists(&6));
assert!(RewardRelationships::<Test>::exists(&7));
});
}
}
5 changes: 5 additions & 0 deletions runtime-modules/governance/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ impl minting::Trait for Test {
type Currency = Balances;
type MintId = u64;
}
impl recurringrewards::Trait for Test {
type PayoutStatusHandler = ();
type RecipientId = u64;
type RewardRelationshipId = u64;
}
parameter_types! {
pub const ExistentialDeposit: u32 = 0;
pub const TransferFee: u32 = 0;
Expand Down
5 changes: 0 additions & 5 deletions runtime/src/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ impl<T: Trait> Module<T> {

// Initialise the proposal system various periods
proposals_codex::Module::<T>::set_default_config_values();

Self::deposit_event(RawEvent::Migrated(
<system::Module<T>>::block_number(),
VERSION.spec_version,
));
}
}

Expand Down