Initial draft of stakeholder democracy.

This commit is contained in:
Gav
2018-02-25 18:37:07 +01:00
parent c43d49084f
commit 2ffcf8ff26
10 changed files with 259 additions and 30 deletions
+7
View File
@@ -241,6 +241,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"demo-primitives 0.1.0", "demo-primitives 0.1.0",
"hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"integer-sqrt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0", "substrate-codec 0.1.0",
@@ -653,6 +654,11 @@ dependencies = [
"xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "integer-sqrt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "iovec" name = "iovec"
version = "0.1.2" version = "0.1.2"
@@ -2066,6 +2072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum hyper 0.11.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4de6edd503089841ebfa88341e1c00fb19b6bf93d820d908db15960fd31226" "checksum hyper 0.11.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4de6edd503089841ebfa88341e1c00fb19b6bf93d820d908db15960fd31226"
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
"checksum igd 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "356a0dc23a4fa0f8ce4777258085d00a01ea4923b2efd93538fc44bf5e1bda76" "checksum igd 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "356a0dc23a4fa0f8ce4777258085d00a01ea4923b2efd93538fc44bf5e1bda76"
"checksum integer-sqrt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8833702c315192502093b244e29c6ab9c55454adfe21b879a87a039ea8fe8520"
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
"checksum ipnetwork 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)" = "2134e210e2a024b5684f90e1556d5f71a1ce7f8b12e9ac9924c67fb36f63b336" "checksum ipnetwork 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)" = "2134e210e2a024b5684f90e1556d5f71a1ce7f8b12e9ac9924c67fb36f63b336"
"checksum isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f2a233726c7bb76995cec749d59582e5664823b7245d4970354408f1d79a7a2" "checksum isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f2a233726c7bb76995cec749d59582e5664823b7245d4970354408f1d79a7a2"
+1
View File
@@ -13,6 +13,7 @@ substrate-runtime-io = { path = "../../substrate/runtime-io" }
substrate-runtime-support = { path = "../../substrate/runtime-support" } substrate-runtime-support = { path = "../../substrate/runtime-support" }
substrate-primitives = { path = "../../substrate/primitives" } substrate-primitives = { path = "../../substrate/primitives" }
demo-primitives = { path = "../primitives" } demo-primitives = { path = "../primitives" }
integer-sqrt = "0.1.0"
[dev-dependencies] [dev-dependencies]
substrate-keyring = { path = "../../substrate/keyring" } substrate-keyring = { path = "../../substrate/keyring" }
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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.
// Substrate Demo 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 Substrate Demo. If not, see <http://www.gnu.org/licenses/>.
//! Democratic system: Handles administration of general stakeholder voting.
use demo_primitives::Proposal;
use runtime::{staking, system, session, governance};
pub fn enact_proposal(proposal: Proposal) {
match proposal {
Proposal::SystemSetCode(code) => {
system::privileged::set_code(&code);
}
Proposal::SessionSetLength(value) => {
session::privileged::set_length(value);
}
Proposal::SessionForceNewSession => {
session::privileged::force_new_session();
}
Proposal::StakingSetSessionsPerEra(value) => {
staking::privileged::set_sessions_per_era(value);
}
Proposal::StakingSetBondingDuration(value) => {
staking::privileged::set_bonding_duration(value);
}
Proposal::StakingSetValidatorCount(value) => {
staking::privileged::set_validator_count(value);
}
Proposal::StakingForceNewEra => {
staking::privileged::force_new_era()
}
Proposal::GovernanceSetApprovalPpmRequired(value) => {
governance::privileged::set_approval_ppm_required(value);
}
}
}
+3
View File
@@ -31,9 +31,12 @@ extern crate demo_primitives;
#[cfg(test)] #[macro_use] extern crate hex_literal; #[cfg(test)] #[macro_use] extern crate hex_literal;
extern crate integer_sqrt;
pub mod environment; pub mod environment;
pub mod runtime; pub mod runtime;
pub mod api; pub mod api;
pub mod dispatch;
#[cfg(feature = "std")] pub mod genesismap; #[cfg(feature = "std")] pub mod genesismap;
@@ -0,0 +1,185 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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.
// Substrate Demo 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 Substrate Demo. If not, see <http://www.gnu.org/licenses/>.
//! Democratic system: Handles administration of general stakeholder voting.
use rstd::prelude::*;
use integer_sqrt::IntegerSquareRoot;
use codec::KeyedVec;
use runtime_support::storage;
use demo_primitives::{Proposal, AccountId, Hash, BlockNumber};
use runtime::{staking, system, session};
const CURRENT_PROPOSAL: &[u8] = b"gov:pro";
const VOTE_OF: &[u8] = b"gov:vot:";
const VOTERS: &[u8] = b"gov:vtr:";
pub mod public {
use super::*;
/// Get the voters for the current proposal.
pub fn voters() -> Vec<AccountId> {
storage::get_or_default(VOTERS)
}
/// Get the vote, if Some, of `who`.
pub fn vote_of(who: &AccountId) -> Option<bool> {
storage::get(&who.to_keyed_vec(VOTE_OF))
}
/// Get the voters for the current proposal.
pub fn tally() -> (staking::Balance, staking::Balance) {
voters().iter()
.map(|a| (staking::balance(a), vote_of(a).expect("all items come from `voters`; for an item to be in `voters` there must be a vote registered; qed")))
.map(|(bal, vote)| if vote { (bal, 0) } else { (0, bal) })
.fold((0, 0), |(a, b), (c, d)| (a + c, b + d))
}
/// Propose a sensitive action to be taken. Any action that is enactable by `Proposal` is valid.
/// Proposal is by the `transactor` and will automatically count as an approval. Transactor must
/// be a current validator. It is illegal to propose when there is already a proposal in effect.
pub fn propose(validator: &AccountId, proposal: &Proposal) {
if storage::exists(CURRENT_PROPOSAL) {
panic!("there may only be one proposal per era.");
}
storage::put(CURRENT_PROPOSAL, proposal);
}
/// Approve the current era's proposal. Transactor must be a validator. This may not be done more
/// than once for any validator in an era.
pub fn vote(who: &AccountId, era_index: BlockNumber, way: bool) {
if era_index != staking::current_era() {
panic!("approval vote applied on non-current era.")
}
if !storage::exists(CURRENT_PROPOSAL) {
panic!("there must be a proposal in order to approve.");
}
if staking::balance(who) == 0 {
panic!("transactor must have balance to signal approval.");
}
let key = who.to_keyed_vec(VOTE_OF);
if !storage::exists(&key) {
let mut voters = voters();
voters.push(who.clone());
storage::put(VOTERS, &voters);
}
storage::put(&key, &way);
}
}
pub mod privileged {
use super::*;
pub fn clear_proposal() {
for v in public::voters() {
storage::kill(&v.to_keyed_vec(VOTE_OF));
}
storage::kill(VOTERS);
}
}
pub mod internal {
use super::*;
use demo_primitives::Proposal;
use dispatch::enact_proposal;
/// Current era is ending; we should finish up any proposals.
pub fn end_of_an_era() {
// tally up votes for the current proposal, if any. enact if there are sufficient approvals.
if let Some(proposal) = storage::take::<Proposal>(CURRENT_PROPOSAL) {
let tally = public::tally();
let total_stake = staking::total_stake();
privileged::clear_proposal();
// TODO: protect against overflows.
let threshold = (tally.0 + tally.1).integer_sqrt() * tally.0 / total_stake.integer_sqrt();
if tally.1 < threshold {
enact_proposal(proposal);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use runtime_io::{with_externalities, twox_128, TestExternalities};
use codec::{KeyedVec, Joiner};
use keyring::Keyring;
use environment::with_env;
use demo_primitives::{AccountId, Proposal};
use runtime::{staking, session, democracy};
fn new_test_ext() -> TestExternalities {
let alice = Keyring::Alice.to_raw_public();
let bob = Keyring::Bob.to_raw_public();
let charlie = Keyring::Charlie.to_raw_public();
let dave = Keyring::Dave.to_raw_public();
let eve = Keyring::Eve.to_raw_public();
let ferdie = Keyring::Ferdie.to_raw_public();
map![
twox_128(b"ses:len").to_vec() => vec![].and(&1u64),
twox_128(b"ses:val:len").to_vec() => vec![].and(&3u32),
twox_128(&0u32.to_keyed_vec(b"ses:val:")).to_vec() => alice.to_vec(),
twox_128(&1u32.to_keyed_vec(b"ses:val:")).to_vec() => bob.to_vec(),
twox_128(&2u32.to_keyed_vec(b"ses:val:")).to_vec() => charlie.to_vec(),
twox_128(b"sta:wil:len").to_vec() => vec![].and(&3u32),
twox_128(&0u32.to_keyed_vec(b"sta:wil:")).to_vec() => alice.to_vec(),
twox_128(&1u32.to_keyed_vec(b"sta:wil:")).to_vec() => bob.to_vec(),
twox_128(&2u32.to_keyed_vec(b"sta:wil:")).to_vec() => charlie.to_vec(),
twox_128(&alice.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&10u64),
twox_128(&bob.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&20u64),
twox_128(&charlie.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&30u64),
twox_128(&dave.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&40u64),
twox_128(&eve.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&50u64),
twox_128(&ferdie.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&60u64),
twox_128(b"sta:tot").to_vec() => vec![].and(&210u64),
twox_128(b"sta:spe").to_vec() => vec![].and(&1u64),
twox_128(b"sta:vac").to_vec() => vec![].and(&3u64),
twox_128(b"sta:era").to_vec() => vec![].and(&1u64)
]
}
#[test]
fn majority_voting_should_work() {
let alice = Keyring::Alice.to_raw_public();
let bob = Keyring::Bob.to_raw_public();
let charlie = Keyring::Charlie.to_raw_public();
let dave = Keyring::Dave.to_raw_public();
let eve = Keyring::Eve.to_raw_public();
let ferdie = Keyring::Ferdie.to_raw_public();
let mut t = new_test_ext();
with_externalities(&mut t, || {
assert_eq!(staking::era_length(), 1u64);
assert_eq!(staking::total_stake(), 210u64);
// Block 1: Make proposal. Approve it. Era length changes.
with_env(|e| e.block_number = 1);
public::propose(&alice, &Proposal::StakingSetSessionsPerEra(2));
public::vote(&ferdie, 1, true);
assert_eq!(public::tally(), (60, 0));
democracy::internal::end_of_an_era();
staking::internal::check_new_era();
assert_eq!(staking::era_length(), 2u64);
});
}
}
@@ -30,6 +30,7 @@ use codec::KeyedVec;
use runtime_support::storage; use runtime_support::storage;
use demo_primitives::{Proposal, AccountId, Hash, BlockNumber}; use demo_primitives::{Proposal, AccountId, Hash, BlockNumber};
use runtime::{staking, system, session}; use runtime::{staking, system, session};
use dispatch::enact_proposal;
const APPROVALS_REQUIRED: &[u8] = b"gov:apr"; const APPROVALS_REQUIRED: &[u8] = b"gov:apr";
const CURRENT_PROPOSAL: &[u8] = b"gov:pro"; const CURRENT_PROPOSAL: &[u8] = b"gov:pro";
@@ -110,36 +111,6 @@ pub mod internal {
} }
} }
} }
fn enact_proposal(proposal: Proposal) {
match proposal {
Proposal::SystemSetCode(code) => {
system::privileged::set_code(&code);
}
Proposal::SessionSetLength(value) => {
session::privileged::set_length(value);
}
Proposal::SessionForceNewSession => {
session::privileged::force_new_session();
}
Proposal::StakingSetSessionsPerEra(value) => {
staking::privileged::set_sessions_per_era(value);
}
Proposal::StakingSetBondingDuration(value) => {
staking::privileged::set_bonding_duration(value);
}
Proposal::StakingSetValidatorCount(value) => {
staking::privileged::set_validator_count(value);
}
Proposal::StakingForceNewEra => {
staking::privileged::force_new_era()
}
Proposal::GovernanceSetApprovalPpmRequired(value) => {
self::privileged::set_approval_ppm_required(value);
}
}
}
} }
#[cfg(test)] #[cfg(test)]
@@ -28,5 +28,8 @@ pub mod timestamp;
pub mod session; pub mod session;
#[allow(unused)] #[allow(unused)]
pub mod governance; pub mod governance;
#[allow(unused)]
pub mod democracy;
// TODO: polkadao // TODO: polkadao
@@ -43,6 +43,7 @@ const SESSIONS_PER_ERA: &[u8] = b"sta:spe";
const NEXT_SESSIONS_PER_ERA: &[u8] = b"sta:nse"; const NEXT_SESSIONS_PER_ERA: &[u8] = b"sta:nse";
const CURRENT_ERA: &[u8] = b"sta:era"; const CURRENT_ERA: &[u8] = b"sta:era";
const LAST_ERA_LENGTH_CHANGE: &[u8] = b"sta:lec"; const LAST_ERA_LENGTH_CHANGE: &[u8] = b"sta:lec";
const TOTAL_STAKE: &[u8] = b"sta:tot";
const BALANCE_OF: &[u8] = b"sta:bal:"; const BALANCE_OF: &[u8] = b"sta:bal:";
const BONDAGE_OF: &[u8] = b"sta:bon:"; const BONDAGE_OF: &[u8] = b"sta:bon:";
@@ -89,6 +90,11 @@ pub fn bondage(who: &AccountId) -> Bondage {
storage::get_or_default(&who.to_keyed_vec(BONDAGE_OF)) storage::get_or_default(&who.to_keyed_vec(BONDAGE_OF))
} }
/// The total amount of stake on the system.
pub fn total_stake() -> Balance {
storage::get_or(TOTAL_STAKE, 0)
}
// Each identity's stake may be in one of three bondage states, given by an integer: // Each identity's stake may be in one of three bondage states, given by an integer:
// - n | n <= current_era(): inactive: free to be transferred. // - n | n <= current_era(): inactive: free to be transferred.
// - ~0: active: currently representing a validator. // - ~0: active: currently representing a validator.
@@ -434,6 +440,7 @@ mod tests {
twox_128(SESSIONS_PER_ERA).to_vec() => vec![].and(&2u64), twox_128(SESSIONS_PER_ERA).to_vec() => vec![].and(&2u64),
twox_128(VALIDATOR_COUNT).to_vec() => vec![].and(&2u32), twox_128(VALIDATOR_COUNT).to_vec() => vec![].and(&2u32),
twox_128(BONDING_DURATION).to_vec() => vec![].and(&3u64), twox_128(BONDING_DURATION).to_vec() => vec![].and(&3u64),
twox_128(TOTAL_STAKE).to_vec() => vec![].and(&100u64),
twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&10u64), twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&10u64),
twox_128(&two.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&20u64), twox_128(&two.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&20u64),
twox_128(&three.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&30u64), twox_128(&three.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&30u64),
+1
View File
@@ -13,6 +13,7 @@ substrate-runtime-io = { path = "../../../substrate/runtime-io", default-feature
substrate-runtime-support = { path = "../../../substrate/runtime-support", default-features = false } substrate-runtime-support = { path = "../../../substrate/runtime-support", default-features = false }
substrate-primitives = { path = "../../../substrate/primitives", default-features = false } substrate-primitives = { path = "../../../substrate/primitives", default-features = false }
demo-primitives = { path = "../../primitives", default-features = false } demo-primitives = { path = "../../primitives", default-features = false }
integer-sqrt = "0.1.0"
[features] [features]
default = [] default = []
@@ -42,6 +42,7 @@ const SESSIONS_PER_ERA: &[u8] = b"sta:spe";
const NEXT_SESSIONS_PER_ERA: &[u8] = b"sta:nse"; const NEXT_SESSIONS_PER_ERA: &[u8] = b"sta:nse";
const CURRENT_ERA: &[u8] = b"sta:era"; const CURRENT_ERA: &[u8] = b"sta:era";
const LAST_ERA_LENGTH_CHANGE: &[u8] = b"sta:lec"; const LAST_ERA_LENGTH_CHANGE: &[u8] = b"sta:lec";
const TOTAL_STAKE: &[u8] = b"sta:tot";
const BALANCE_OF: &[u8] = b"sta:bal:"; const BALANCE_OF: &[u8] = b"sta:bal:";
const BONDAGE_OF: &[u8] = b"sta:bon:"; const BONDAGE_OF: &[u8] = b"sta:bon:";
@@ -235,6 +236,7 @@ mod tests {
twox_128(SESSIONS_PER_ERA).to_vec() => vec![].and(&2u64), twox_128(SESSIONS_PER_ERA).to_vec() => vec![].and(&2u64),
twox_128(VALIDATOR_COUNT).to_vec() => vec![].and(&2u32), twox_128(VALIDATOR_COUNT).to_vec() => vec![].and(&2u32),
twox_128(BONDING_DURATION).to_vec() => vec![].and(&3u64), twox_128(BONDING_DURATION).to_vec() => vec![].and(&3u64),
twox_128(TOTAL_STAKE).to_vec() => vec![].and(&100u64),
twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&10u64), twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&10u64),
twox_128(&two.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&20u64), twox_128(&two.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&20u64),
twox_128(&three.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&30u64), twox_128(&three.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&30u64),