diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock
index 2af889e5c9..4f4a414d7a 100644
--- a/substrate/Cargo.lock
+++ b/substrate/Cargo.lock
@@ -241,6 +241,7 @@ version = "0.1.0"
dependencies = [
"demo-primitives 0.1.0",
"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)",
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0",
@@ -653,6 +654,11 @@ dependencies = [
"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]]
name = "iovec"
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 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 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 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"
diff --git a/substrate/demo/runtime/Cargo.toml b/substrate/demo/runtime/Cargo.toml
index 8c175b818c..f98345cae6 100644
--- a/substrate/demo/runtime/Cargo.toml
+++ b/substrate/demo/runtime/Cargo.toml
@@ -13,6 +13,7 @@ substrate-runtime-io = { path = "../../substrate/runtime-io" }
substrate-runtime-support = { path = "../../substrate/runtime-support" }
substrate-primitives = { path = "../../substrate/primitives" }
demo-primitives = { path = "../primitives" }
+integer-sqrt = "0.1.0"
[dev-dependencies]
substrate-keyring = { path = "../../substrate/keyring" }
diff --git a/substrate/demo/runtime/src/dispatch.rs b/substrate/demo/runtime/src/dispatch.rs
new file mode 100644
index 0000000000..6b03667ff9
--- /dev/null
+++ b/substrate/demo/runtime/src/dispatch.rs
@@ -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 .
+
+//! 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);
+ }
+ }
+}
diff --git a/substrate/demo/runtime/src/lib.rs b/substrate/demo/runtime/src/lib.rs
index 4bb5dc4169..f42d16016a 100644
--- a/substrate/demo/runtime/src/lib.rs
+++ b/substrate/demo/runtime/src/lib.rs
@@ -31,9 +31,12 @@ extern crate demo_primitives;
#[cfg(test)] #[macro_use] extern crate hex_literal;
+extern crate integer_sqrt;
+
pub mod environment;
pub mod runtime;
pub mod api;
+pub mod dispatch;
#[cfg(feature = "std")] pub mod genesismap;
diff --git a/substrate/demo/runtime/src/runtime/democracy.rs b/substrate/demo/runtime/src/runtime/democracy.rs
new file mode 100644
index 0000000000..cad53ead18
--- /dev/null
+++ b/substrate/demo/runtime/src/runtime/democracy.rs
@@ -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 .
+
+//! 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 {
+ storage::get_or_default(VOTERS)
+ }
+
+ /// Get the vote, if Some, of `who`.
+ pub fn vote_of(who: &AccountId) -> Option {
+ 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::(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);
+ });
+ }
+}
diff --git a/substrate/demo/runtime/src/runtime/governance.rs b/substrate/demo/runtime/src/runtime/governance.rs
index b7e2fca386..0db372c481 100644
--- a/substrate/demo/runtime/src/runtime/governance.rs
+++ b/substrate/demo/runtime/src/runtime/governance.rs
@@ -30,6 +30,7 @@ use codec::KeyedVec;
use runtime_support::storage;
use demo_primitives::{Proposal, AccountId, Hash, BlockNumber};
use runtime::{staking, system, session};
+use dispatch::enact_proposal;
const APPROVALS_REQUIRED: &[u8] = b"gov:apr";
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)]
diff --git a/substrate/demo/runtime/src/runtime/mod.rs b/substrate/demo/runtime/src/runtime/mod.rs
index cfb45f3c8f..2dab848343 100644
--- a/substrate/demo/runtime/src/runtime/mod.rs
+++ b/substrate/demo/runtime/src/runtime/mod.rs
@@ -28,5 +28,8 @@ pub mod timestamp;
pub mod session;
#[allow(unused)]
pub mod governance;
+#[allow(unused)]
+pub mod democracy;
+
// TODO: polkadao
diff --git a/substrate/demo/runtime/src/runtime/staking.rs b/substrate/demo/runtime/src/runtime/staking.rs
index c9ca26dd24..e5369be6f9 100644
--- a/substrate/demo/runtime/src/runtime/staking.rs
+++ b/substrate/demo/runtime/src/runtime/staking.rs
@@ -43,6 +43,7 @@ const SESSIONS_PER_ERA: &[u8] = b"sta:spe";
const NEXT_SESSIONS_PER_ERA: &[u8] = b"sta:nse";
const CURRENT_ERA: &[u8] = b"sta:era";
const LAST_ERA_LENGTH_CHANGE: &[u8] = b"sta:lec";
+const TOTAL_STAKE: &[u8] = b"sta:tot";
const BALANCE_OF: &[u8] = b"sta:bal:";
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))
}
+/// 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:
// - n | n <= current_era(): inactive: free to be transferred.
// - ~0: active: currently representing a validator.
@@ -434,6 +440,7 @@ mod tests {
twox_128(SESSIONS_PER_ERA).to_vec() => vec![].and(&2u64),
twox_128(VALIDATOR_COUNT).to_vec() => vec![].and(&2u32),
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(&two.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&20u64),
twox_128(&three.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&30u64),
diff --git a/substrate/demo/runtime/wasm/Cargo.toml b/substrate/demo/runtime/wasm/Cargo.toml
index 2c6f440187..04d8999e27 100644
--- a/substrate/demo/runtime/wasm/Cargo.toml
+++ b/substrate/demo/runtime/wasm/Cargo.toml
@@ -13,6 +13,7 @@ substrate-runtime-io = { path = "../../../substrate/runtime-io", default-feature
substrate-runtime-support = { path = "../../../substrate/runtime-support", default-features = false }
substrate-primitives = { path = "../../../substrate/primitives", default-features = false }
demo-primitives = { path = "../../primitives", default-features = false }
+integer-sqrt = "0.1.0"
[features]
default = []
diff --git a/substrate/polkadot/runtime/src/runtime/staking.rs b/substrate/polkadot/runtime/src/runtime/staking.rs
index 0bb4372c6d..1b69698265 100644
--- a/substrate/polkadot/runtime/src/runtime/staking.rs
+++ b/substrate/polkadot/runtime/src/runtime/staking.rs
@@ -42,6 +42,7 @@ const SESSIONS_PER_ERA: &[u8] = b"sta:spe";
const NEXT_SESSIONS_PER_ERA: &[u8] = b"sta:nse";
const CURRENT_ERA: &[u8] = b"sta:era";
const LAST_ERA_LENGTH_CHANGE: &[u8] = b"sta:lec";
+const TOTAL_STAKE: &[u8] = b"sta:tot";
const BALANCE_OF: &[u8] = b"sta:bal:";
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(VALIDATOR_COUNT).to_vec() => vec![].and(&2u32),
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(&two.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&20u64),
twox_128(&three.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&30u64),