Simple governance subsytem.

This commit is contained in:
Gav
2018-01-21 22:35:25 +01:00
parent 6303c7900d
commit 2789bc2b8f
15 changed files with 360 additions and 83 deletions
@@ -0,0 +1,97 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Governance system: Handles administration and dispatch of sensitive operations including
//! setting new code, minting new tokens and changing parameters.
use runtime_support::Vec;
use keyedvec::KeyedVec;
use storable::{Storable, StorageVec, kill};
use primitives::{AccountID, Hash, BlockNumber};
use proposal::Proposal;
use runtime::{staking, system, session};
// TRANSACTION API
pub fn propose(transactor: &AccountID, proposal: &Proposal) {
if Proposal::lookup(b"gov:pro").is_some() {
panic!("there may only be one proposal per era.");
}
proposal.store(b"gov:pro");
approve(transactor, staking::current_era());
}
pub fn approve(transactor: &AccountID, era_index: BlockNumber) {
if era_index != staking::current_era() {
panic!("approval vote applied on non-current era.")
}
if Proposal::lookup(b"gov:pro").is_none() {
panic!("there must be a proposal in order to approve.");
}
let key = transactor.to_keyed_vec(b"gov:app:");
if bool::lookup(&key).is_some() {
panic!("transactor may not approve a proposal twice in one era.");
}
true.store(&key);
(approval_count() + 1).store(b"gov:app");
}
// INSPECTION API
pub fn approval_count() -> u32 {
Storable::lookup_default(b"gov:app")
}
pub fn approval_ppm_required() -> u32 {
Storable::lookup(b"gov:apr").unwrap_or(1000)
}
pub fn approvals_required() -> u32 {
approval_ppm_required() * staking::validator_count() as u32 / 1000
}
// PUBLIC API
/// Current era is ending; we should finish up any proposals.
pub fn end_of_an_era() {
// TODO: tally up votes for the current proposal, if any. enact if there are sufficient
// approvals.
if let Some(proposal) = Proposal::lookup(b"gov:pro") {
let enact = approval_count() >= approvals_required();
// clear proposal
reset_proposal();
if enact {
proposal.enact();
}
}
}
// PRIVATE API
fn reset_proposal() {
session::validators().into_iter().for_each(|v| {
kill(&v.to_keyed_vec(b"gov:app:"));
});
kill(b"gov:pro");
kill(b"gov:app");
}
#[cfg(test)]
mod tests {
// TODO
}
@@ -26,5 +26,7 @@ pub mod staking;
pub mod timestamp;
#[allow(unused)]
pub mod session;
#[allow(unused)]
pub mod governance;
// TODO: governance, polkadao
// TODO: polkadao
@@ -16,11 +16,11 @@
//! Staking manager: Handles balances and periodically determines the best set of validators.
use runtime_support::Vec;
use runtime_support::{Vec, RefCell};
use keyedvec::KeyedVec;
use storable::{Storable, StorageVec};
use primitives::{BlockNumber, AccountID};
use runtime::{system, session};
use runtime::{system, session, governance};
/// The balance of an account.
pub type Balance = u64;
@@ -31,7 +31,7 @@ pub type Bondage = u64;
struct IntentionStorageVec {}
impl StorageVec for IntentionStorageVec {
type Item = AccountID;
const PREFIX: &'static[u8] = b"ses:wil:";
const PREFIX: &'static[u8] = b"sta:wil:";
}
// Each identity's stake may be in one of three bondage states, given by an integer:
@@ -40,6 +40,55 @@ impl StorageVec for IntentionStorageVec {
// - n | n > current_era(): deactivating: recently representing a validator and not yet
// ready for transfer.
// TRANSACTION API
/// Transfer some unlocked staking balance to another staker.
pub fn transfer(transactor: &AccountID, dest: &AccountID, value: Balance) {
let from_key = transactor.to_keyed_vec(b"sta:bal:");
let from_balance = Balance::lookup_default(&from_key);
assert!(from_balance >= value);
let to_key = dest.to_keyed_vec(b"sta:bal:");
let to_balance: Balance = Storable::lookup_default(&to_key);
assert!(bondage(transactor) <= bondage(dest));
assert!(to_balance + value > to_balance); // no overflow
(from_balance - value).store(&from_key);
(to_balance + value).store(&to_key);
}
/// Declare the desire to stake for the transactor.
///
/// Effects will be felt at the beginning of the next era.
pub fn stake(transactor: &AccountID) {
let mut intentions = IntentionStorageVec::items();
// can't be in the list twice.
assert!(intentions.iter().find(|t| *t == transactor).is_none(), "Cannot stake if already staked.");
intentions.push(transactor.clone());
IntentionStorageVec::set_items(&intentions);
u64::max_value().store(&transactor.to_keyed_vec(b"sta:bon:"));
}
/// Retract the desire to stake for the transactor.
///
/// Effects will be felt at the beginning of the next era.
pub fn unstake(transactor: &AccountID) {
let mut intentions = IntentionStorageVec::items();
if let Some(position) = intentions.iter().position(|t| t == transactor) {
intentions.swap_remove(position);
} else {
panic!("Cannot unstake if not already staked.");
}
IntentionStorageVec::set_items(&intentions);
(current_era() + bonding_duration()).store(&transactor.to_keyed_vec(b"sta:bon:"));
}
// PUBLIC API
pub fn set_sessions_per_era(new: BlockNumber) {
new.store(b"sta:nse");
}
// INSPECTION API
/// The length of the bonding duration in eras.
pub fn bonding_duration() -> BlockNumber {
Storable::lookup_default(b"sta:loc")
@@ -80,45 +129,6 @@ pub fn bondage(who: &AccountID) -> Bondage {
Storable::lookup_default(&who.to_keyed_vec(b"sta:bon:"))
}
/// Transfer some unlocked staking balance to another staker.
pub fn transfer(transactor: &AccountID, dest: &AccountID, value: Balance) {
let from_key = transactor.to_keyed_vec(b"sta:bal:");
let from_balance = Balance::lookup_default(&from_key);
assert!(from_balance >= value);
let to_key = dest.to_keyed_vec(b"sta:bal:");
let to_balance: Balance = Storable::lookup_default(&to_key);
assert!(bondage(transactor) <= bondage(dest));
assert!(to_balance + value > to_balance); // no overflow
(from_balance - value).store(&from_key);
(to_balance + value).store(&to_key);
}
/// Declare the desire to stake for the transactor.
///
/// Effects will be felt at the beginning of the next era.
pub fn stake(transactor: &AccountID) {
let mut intentions = IntentionStorageVec::items();
// can't be in the list twice.
assert!(intentions.iter().find(|t| *t == transactor).is_none(), "Cannot stake if already staked.");
intentions.push(transactor.clone());
IntentionStorageVec::set_items(&intentions);
u64::max_value().store(&transactor.to_keyed_vec(b"sta:bon:"));
}
/// Retract the desire to stake for the transactor.
///
/// Effects will be felt at the beginning of the next era.
pub fn unstake(transactor: &AccountID) {
let mut intentions = IntentionStorageVec::items();
if let Some(position) = intentions.iter().position(|t| t == transactor) {
intentions.swap_remove(position);
} else {
panic!("Cannot unstake if not already staked.");
}
IntentionStorageVec::set_items(&intentions);
(current_era() + bonding_duration()).store(&transactor.to_keyed_vec(b"sta:bon:"));
}
/// Hook to be called after to transaction processing.
pub fn check_new_era() {
// check block number and call new_era if necessary.
@@ -131,8 +141,12 @@ pub fn check_new_era() {
/// The era has changed - enact new staking set.
///
/// NOTE: This always happens on a session change.
/// NOTE: This always happens immediately before a session change to ensure that new validators
/// get a chance to set their session keys.
fn new_era() {
// Inform governance module that it's the end of an era
governance::end_of_an_era();
// Increment current era.
(current_era() + 1).store(b"sta:era");
@@ -143,9 +157,8 @@ fn new_era() {
system::block_number().store(b"sta:lec");
}
// TODO: evaluate desired staking amounts and nominations and optimise to find the best
// evaluate desired staking amounts and nominations and optimise to find the best
// combination of validators, then use session::set_validators().
// for now, this just orders would-be stakers by their balances and chooses the top-most
// validator_count() of them.
let mut intentions = IntentionStorageVec::items()
@@ -161,11 +174,6 @@ fn new_era() {
);
}
/// Set a new era length. Won't kick in until the next era change (at current length).
fn set_sessions_per_era(new: BlockNumber) {
new.store(b"sta:nse");
}
#[cfg(test)]
mod tests {
use runtime_support::{with_externalities, twox_128};