Council motions for approving treasury proposals (#694)

* Treasury in runtime, generic approve/reject

* Add logic for council origin

* Add tests.

* Configurable number of members in EnsureMembers

* Fix grumbles

* Fix spelling

* Comment
This commit is contained in:
Gav Wood
2018-09-10 16:02:59 +02:00
committed by GitHub
parent 7b112dee7c
commit bcc26dd30a
26 changed files with 678 additions and 47 deletions
+3
View File
@@ -446,6 +446,7 @@ dependencies = [
"substrate-runtime-staking 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-treasury 0.1.0",
"substrate-state-machine 0.1.0",
"triehash 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -492,6 +493,7 @@ dependencies = [
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
"substrate-runtime-treasury 0.1.0",
"substrate-runtime-version 0.1.0",
]
@@ -2708,6 +2710,7 @@ dependencies = [
"serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0",
"substrate-codec-derive 0.1.0",
"substrate-keyring 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-balances 0.1.0",
+7 -1
View File
@@ -51,7 +51,7 @@ use std::sync::Arc;
use demo_primitives::{AccountId, Hash};
use demo_runtime::{Block, BlockId, GenesisConfig,
BalancesConfig, ConsensusConfig, CouncilConfig, DemocracyConfig, SessionConfig,
StakingConfig, TimestampConfig};
StakingConfig, TimestampConfig, TreasuryConfig, Permill};
use futures::{Future, Sink, Stream};
use tokio::runtime::Runtime;
use demo_executor::NativeExecutor;
@@ -211,6 +211,12 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
timestamp: Some(TimestampConfig {
period: 5, // 5 second block time.
}),
treasury: Some(TreasuryConfig {
proposal_bond: Permill::from_percent(5),
proposal_bond_minimum: 1_000_000,
spend_period: 12 * 60 * 24,
burn: Permill::from_percent(50),
}),
};
let client = Arc::new(client::new_in_mem::<NativeExecutor<demo_executor::Executor>, Block, _>(executor, genesis_config)?);
+1
View File
@@ -25,3 +25,4 @@ substrate-runtime-session = { path = "../../substrate/runtime/session" }
substrate-runtime-staking = { path = "../../substrate/runtime/staking" }
substrate-runtime-system = { path = "../../substrate/runtime/system" }
substrate-runtime-consensus = { path = "../../substrate/runtime/consensus" }
substrate-runtime-treasury = { path = "../../substrate/runtime/treasury" }
+30 -4
View File
@@ -35,6 +35,7 @@ extern crate triehash;
#[cfg(test)] extern crate substrate_runtime_staking as staking;
#[cfg(test)] extern crate substrate_runtime_system as system;
#[cfg(test)] extern crate substrate_runtime_consensus as consensus;
#[cfg(test)] extern crate substrate_runtime_treasury as treasury;
#[cfg(test)] #[macro_use] extern crate hex_literal;
pub use substrate_executor::NativeExecutor;
@@ -53,7 +54,7 @@ mod tests {
use demo_primitives::{Hash, BlockNumber, AccountId};
use runtime_primitives::traits::Header as HeaderT;
use runtime_primitives::{ApplyOutcome, ApplyError, ApplyResult};
use {balances, staking, session, system, consensus};
use {balances, staking, session, system, consensus, treasury};
use system::{EventRecord, Phase};
use demo_runtime::{Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances,
BuildStorage, GenesisConfig, BalancesConfig, SessionConfig, StakingConfig, System, Event};
@@ -234,6 +235,7 @@ mod tests {
democracy: Some(Default::default()),
council: Some(Default::default()),
timestamp: Some(Default::default()),
treasury: Some(Default::default()),
}.build_storage().unwrap().into()
}
@@ -260,7 +262,7 @@ mod tests {
construct_block(
1,
[69u8; 32].into(),
hex!("ddfc4d60889b25215f4fe6ead4e38b7522fa20809a793476eae3ad5ab2d9c399").into(),
hex!("1e930ccf2a39029931fcb9173637ab99a7de9d0364e7bf57cfbcb3eb4619e0d4").into(),
vec![CheckedExtrinsic {
signed: Some(alice()),
index: 0,
@@ -273,7 +275,7 @@ mod tests {
construct_block(
2,
block1().1,
hex!("2b4464c7e0d51325505663ae2ebd2246fcefc5cb998e9c29d8030c559cbbf27f").into(),
hex!("80e45869c9a9f513695b94baf479913ddf0bc9653f1be63baa383be8553a9e13").into(),
vec![
CheckedExtrinsic {
signed: Some(bob()),
@@ -293,7 +295,7 @@ mod tests {
construct_block(
1,
[69u8; 32].into(),
hex!("e45221804da3a3609454d4e09debe6364cc6af63c2ff067d802d1af62fea32ae").into(),
hex!("58bf7cd5a908de7296bfc0524d89086384df3e8010ab83c8599be036445d6c79").into(),
vec![CheckedExtrinsic {
signed: Some(alice()),
index: 0,
@@ -328,6 +330,18 @@ mod tests {
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: Event::system(system::Event::ExtrinsicSuccess)
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Spending(0))
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Burnt(0))
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Rollover(0))
}
]);
});
@@ -368,6 +382,18 @@ mod tests {
phase: Phase::ApplyExtrinsic(1),
event: Event::system(system::Event::ExtrinsicSuccess)
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Spending(0))
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Burnt(0))
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Rollover(0))
},
EventRecord {
phase: Phase::Finalization,
event: Event::session(session::RawEvent::NewSession(1))
+2
View File
@@ -27,6 +27,7 @@ substrate-runtime-session = { path = "../../substrate/runtime/session" }
substrate-runtime-staking = { path = "../../substrate/runtime/staking" }
substrate-runtime-system = { path = "../../substrate/runtime/system" }
substrate-runtime-timestamp = { path = "../../substrate/runtime/timestamp" }
substrate-runtime-treasury = { path = "../../substrate/runtime/treasury" }
substrate-runtime-version = { path = "../../substrate/runtime/version" }
demo-primitives = { path = "../primitives" }
@@ -48,6 +49,7 @@ std = [
"substrate-runtime-staking/std",
"substrate-runtime-system/std",
"substrate-runtime-timestamp/std",
"substrate-runtime-treasury/std",
"substrate-runtime-version/std",
"demo-primitives/std",
"serde_derive",
+29 -3
View File
@@ -35,6 +35,7 @@ extern crate serde_derive;
extern crate serde;
extern crate substrate_codec as codec;
extern crate substrate_primitives;
#[macro_use]
extern crate substrate_codec_derive;
@@ -49,6 +50,7 @@ extern crate substrate_runtime_session as session;
extern crate substrate_runtime_staking as staking;
extern crate substrate_runtime_system as system;
extern crate substrate_runtime_timestamp as timestamp;
extern crate substrate_runtime_treasury as treasury;
#[macro_use]
extern crate substrate_runtime_version as version;
extern crate demo_primitives;
@@ -57,9 +59,11 @@ use demo_primitives::{AccountId, AccountIndex, Balance, BlockNumber, Hash, Index
use runtime_primitives::generic;
use runtime_primitives::traits::{Convert, BlakeTwo256, DigestItem};
use version::RuntimeVersion;
use council::motions as council_motions;
use substrate_primitives::u32_trait::{_2, _4};
#[cfg(any(feature = "std", test))]
pub use runtime_primitives::BuildStorage;
pub use runtime_primitives::{BuildStorage, Permill};
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, Copy, PartialEq, Eq)]
@@ -161,9 +165,27 @@ pub type Council = council::Module<Runtime>;
/// Council voting module for this concrete runtime.
pub type CouncilVoting = council::voting::Module<Runtime>;
impl council::motions::Trait for Runtime {
type Origin = Origin;
type Proposal = Call;
type Event = Event;
}
/// Council motions module for this concrete runtime.
pub type CouncilMotions = council_motions::Module<Runtime>;
impl treasury::Trait for Runtime {
type ApproveOrigin = council_motions::EnsureMembers<_4>;
type RejectOrigin = council_motions::EnsureMembers<_2>;
type Event = Event;
}
/// Treasury module for this concrete runtime.
pub type Treasury = treasury::Module<Runtime>;
impl_outer_event! {
pub enum Event for Runtime {
balances, session, staking, democracy
balances, session, staking, democracy, treasury, council_motions
}
}
@@ -175,6 +197,7 @@ impl_outer_log! {
impl_outer_origin! {
pub enum Origin for Runtime {
council_motions
}
}
@@ -188,6 +211,8 @@ impl_outer_dispatch! {
Democracy,
Council,
CouncilVoting,
CouncilMotions,
Treasury,
}
}
@@ -201,6 +226,7 @@ impl_outer_config! {
DemocracyConfig => democracy,
CouncilConfig => council,
TimestampConfig => timestamp,
TreasuryConfig => treasury,
}
}
@@ -228,7 +254,7 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Index, Call,
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Index, Call>;
/// Executive: handles dispatch to the various modules.
pub type Executive = executive::Executive<Runtime, Block, Balances, Balances,
(((((), Council), Democracy), Staking), Session)>;
((((((), Treasury), Council), Democracy), Staking), Session)>;
pub mod api {
impl_stubs!(
+20
View File
@@ -111,6 +111,7 @@ dependencies = [
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
"substrate-runtime-treasury 0.1.0",
"substrate-runtime-version 0.1.0",
]
@@ -648,6 +649,7 @@ dependencies = [
"serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0",
"substrate-codec-derive 0.1.0",
"substrate-keyring 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-balances 0.1.0",
@@ -842,6 +844,24 @@ dependencies = [
"substrate-runtime-system 0.1.0",
]
[[package]]
name = "substrate-runtime-treasury"
version = "0.1.0"
dependencies = [
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0",
"substrate-codec-derive 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-balances 0.1.0",
"substrate-runtime-io 0.1.0",
"substrate-runtime-primitives 0.1.0",
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
]
[[package]]
name = "substrate-runtime-version"
version = "0.1.0"
+2
View File
@@ -25,6 +25,7 @@ substrate-runtime-session = { path = "../../../substrate/runtime/session", defau
substrate-runtime-staking = { path = "../../../substrate/runtime/staking", default-features = false }
substrate-runtime-system = { path = "../../../substrate/runtime/system", default-features = false }
substrate-runtime-timestamp = { path = "../../../substrate/runtime/timestamp", default-features = false }
substrate-runtime-treasury = { path = "../../../substrate/runtime/treasury", default-features = false }
substrate-runtime-version = { path = "../../../substrate/runtime/version", default-features = false }
demo-primitives = { path = "../../primitives", default-features = false }
@@ -47,6 +48,7 @@ std = [
"substrate-runtime-staking/std",
"substrate-runtime-system/std",
"substrate-runtime-timestamp/std",
"substrate-runtime-treasury/std",
"substrate-runtime-version/std",
"demo-primitives/std",
]
@@ -92,6 +92,8 @@ pub use hashing::{blake2_256, twox_128, twox_256};
#[cfg(feature = "std")]
pub mod hexdisplay;
pub mod u32_trait;
pub mod hash;
mod hasher;
pub mod sandbox;
@@ -0,0 +1,89 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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 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. If not, see <http://www.gnu.org/licenses/>.
//! An u32 trait with "values" as impl'd types.
/// A u32 value, wrapped in a trait because we don't yet have const generics.
pub trait Value {
/// The actual value represented by the impl'ing type.
const VALUE: u32;
}
/// Type representing the value 0 for the `Value` trait.
pub struct _0; impl Value for _0 { const VALUE: u32 = 0; }
/// Type representing the value 1 for the `Value` trait.
pub struct _1; impl Value for _1 { const VALUE: u32 = 1; }
/// Type representing the value 2 for the `Value` trait.
pub struct _2; impl Value for _2 { const VALUE: u32 = 2; }
/// Type representing the value 3 for the `Value` trait.
pub struct _3; impl Value for _3 { const VALUE: u32 = 3; }
/// Type representing the value 4 for the `Value` trait.
pub struct _4; impl Value for _4 { const VALUE: u32 = 4; }
/// Type representing the value 5 for the `Value` trait.
pub struct _5; impl Value for _5 { const VALUE: u32 = 5; }
/// Type representing the value 6 for the `Value` trait.
pub struct _6; impl Value for _6 { const VALUE: u32 = 6; }
/// Type representing the value 7 for the `Value` trait.
pub struct _7; impl Value for _7 { const VALUE: u32 = 7; }
/// Type representing the value 8 for the `Value` trait.
pub struct _8; impl Value for _8 { const VALUE: u32 = 8; }
/// Type representing the value 9 for the `Value` trait.
pub struct _9; impl Value for _9 { const VALUE: u32 = 9; }
/// Type representing the value 10 for the `Value` trait.
pub struct _10; impl Value for _10 { const VALUE: u32 = 10; }
/// Type representing the value 11 for the `Value` trait.
pub struct _11; impl Value for _11 { const VALUE: u32 = 11; }
/// Type representing the value 12 for the `Value` trait.
pub struct _12; impl Value for _12 { const VALUE: u32 = 12; }
/// Type representing the value 13 for the `Value` trait.
pub struct _13; impl Value for _13 { const VALUE: u32 = 13; }
/// Type representing the value 14 for the `Value` trait.
pub struct _14; impl Value for _14 { const VALUE: u32 = 14; }
/// Type representing the value 15 for the `Value` trait.
pub struct _15; impl Value for _15 { const VALUE: u32 = 15; }
/// Type representing the value 16 for the `Value` trait.
pub struct _16; impl Value for _16 { const VALUE: u32 = 16; }
/// Type representing the value 24 for the `Value` trait.
pub struct _24; impl Value for _24 { const VALUE: u32 = 24; }
/// Type representing the value 32 for the `Value` trait.
pub struct _32; impl Value for _32 { const VALUE: u32 = 32; }
/// Type representing the value 40 for the `Value` trait.
pub struct _40; impl Value for _40 { const VALUE: u32 = 40; }
/// Type representing the value 48 for the `Value` trait.
pub struct _48; impl Value for _48 { const VALUE: u32 = 48; }
/// Type representing the value 56 for the `Value` trait.
pub struct _56; impl Value for _56 { const VALUE: u32 = 56; }
/// Type representing the value 64 for the `Value` trait.
pub struct _64; impl Value for _64 { const VALUE: u32 = 64; }
/// Type representing the value 80 for the `Value` trait.
pub struct _80; impl Value for _80 { const VALUE: u32 = 80; }
/// Type representing the value 96 for the `Value` trait.
pub struct _96; impl Value for _96 { const VALUE: u32 = 96; }
/// Type representing the value 112 for the `Value` trait.
pub struct _112; impl Value for _112 { const VALUE: u32 = 112; }
/// Type representing the value 128 for the `Value` trait.
pub struct _128; impl Value for _128 { const VALUE: u32 = 128; }
/// Type representing the value 160 for the `Value` trait.
pub struct _160; impl Value for _160 { const VALUE: u32 = 160; }
/// Type representing the value 192 for the `Value` trait.
pub struct _192; impl Value for _192 { const VALUE: u32 = 192; }
/// Type representing the value 224 for the `Value` trait.
pub struct _224; impl Value for _224 { const VALUE: u32 = 224; }
/// Type representing the value 256 for the `Value` trait.
pub struct _256; impl Value for _256 { const VALUE: u32 = 256; }
/// Type representing the value 384 for the `Value` trait.
pub struct _384; impl Value for _384 { const VALUE: u32 = 384; }
/// Type representing the value 512 for the `Value` trait.
pub struct _512; impl Value for _512 { const VALUE: u32 = 512; }
@@ -37,6 +37,7 @@ macro_rules! __impl_outer_event_json_metadata {
$( $module:ident )*
) => {
impl $runtime {
#[allow(dead_code)]
pub fn outer_event_json_metadata() -> &'static str {
concat!(r#"{ "name": ""#, stringify!($event_name), r#"", "items": { "#,
r#""system": "system::Event""#,
@@ -116,7 +116,7 @@ macro_rules! impl_outer_origin {
pub enum $name {
system($system::Origin<$trait>),
$(
$module($module::Origin<$trait>),
$module($module::Origin),
)*
#[allow(dead_code)]
Void($crate::Void)
@@ -149,13 +149,13 @@ macro_rules! impl_outer_origin {
}
}
$(
impl From<$module::Origin<$trait>> for $name {
fn from(x: $module::Origin<$trait>) -> Self {
impl From<$module::Origin> for $name {
fn from(x: $module::Origin) -> Self {
$name::$module(x)
}
}
impl<T: Trait> Into<Option<$module::Origin<T>>> for $name<T> {
fn into(self) -> Option<$module::Origin<T>> {
impl Into<Option<$module::Origin>> for $name {
fn into(self) -> Option<$module::Origin> {
if let $name::$module(l) = self {
Some(l)
} else {
@@ -11,6 +11,7 @@ serde_derive = { version = "1.0", optional = true }
safe-mix = { version = "1.0", default_features = false}
substrate-keyring = { path = "../../keyring", optional = true }
substrate-codec = { path = "../../codec", default_features = false }
substrate-codec-derive = { path = "../../codec/derive", default_features = false }
substrate-primitives = { path = "../../primitives", default_features = false }
substrate-runtime-std = { path = "../../runtime-std", default_features = false }
substrate-runtime-io = { path = "../../runtime-io", default_features = false }
@@ -29,6 +30,7 @@ std = [
"safe-mix/std",
"substrate-keyring",
"substrate-codec/std",
"substrate-codec-derive/std",
"substrate-primitives/std",
"substrate-runtime-std/std",
"substrate-runtime-io/std",
+26 -5
View File
@@ -25,8 +25,13 @@ extern crate serde;
#[macro_use]
extern crate serde_derive;
#[cfg(test)]
#[macro_use]
extern crate hex_literal;
extern crate integer_sqrt;
extern crate substrate_codec as codec;
#[macro_use] extern crate substrate_codec_derive;
extern crate substrate_primitives;
#[cfg(feature = "std")] extern crate substrate_keyring as keyring;
#[macro_use] extern crate substrate_runtime_std as rstd;
@@ -47,6 +52,7 @@ use balances::address::Address;
use system::{ensure_signed, ensure_root};
pub mod voting;
pub mod motions;
// no polynomial attacks:
//
@@ -630,9 +636,18 @@ mod tests {
use primitives::traits::{BlakeTwo256};
use primitives::testing::{Digest, Header};
use substrate_primitives::KeccakHasher;
use motions as council_motions;
impl_outer_origin! {
pub enum Origin for Test {}
pub enum Origin for Test {
council_motions
}
}
impl_outer_event! {
pub enum Event for Test {
balances, democracy, council_motions
}
}
impl_outer_dispatch! {
@@ -654,20 +669,26 @@ mod tests {
type Digest = Digest;
type AccountId = u64;
type Header = Header;
type Event = ();
type Event = Event;
}
impl balances::Trait for Test {
type Balance = u64;
type AccountIndex = u64;
type OnFreeBalanceZero = ();
type EnsureAccountLiquid = ();
type Event = ();
type Event = Event;
}
impl democracy::Trait for Test {
type Proposal = Call;
type Event = ();
type Event = Event;
}
impl Trait for Test {
}
impl council_motions::Trait for Test {
type Origin = Origin;
type Proposal = Call;
type Event = Event;
}
impl Trait for Test {}
pub fn new_test_ext(with_council: bool) -> runtime_io::TestExternalities<KeccakHasher> {
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap();
@@ -0,0 +1,373 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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 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. If not, see <http://www.gnu.org/licenses/>.
//! Council voting system.
use rstd::prelude::*;
use rstd::result;
use substrate_primitives::u32_trait::Value as U32;
use primitives::traits::{Hash, EnsureOrigin, MaybeSerializeDebug};
use substrate_runtime_support::dispatch::{Result, Dispatchable, Parameter};
use substrate_runtime_support::{StorageValue, StorageMap};
use super::{Trait as CouncilTrait, Module as Council};
use system::{self, ensure_signed};
/// Simple index type for proposal counting.
pub type ProposalIndex = u32;
pub trait Trait: CouncilTrait + MaybeSerializeDebug {
/// The outer origin type.
type Origin: From<Origin>;
/// The outer call dispatch type.
type Proposal: Parameter + Dispatchable<Origin=<Self as Trait>::Origin> + MaybeSerializeDebug;
/// The outer event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
/// Origin for the council module.
#[derive(PartialEq, Eq, Clone)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum Origin {
/// It has been condoned by a given number of council members.
Members(u32),
}
/// Outwardly visible event.
pub type Event<T> = RawEvent<<T as system::Trait>::Hash, <T as system::Trait>::AccountId>;
/// Event for this module.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
pub enum RawEvent<Hash, AccountId> {
/// A motion (given hash) has been proposed (by given account) with a threshold (given u32).
Proposed(AccountId, ProposalIndex, Hash, u32),
/// A motion (given hash) has been voted on by given account, leaving
/// a tally (yes votes and no votes given as u32s respectively).
Voted(AccountId, Hash, bool, u32, u32),
/// A motion was approved by the required threshold.
Approved(Hash),
/// A motion was not approved by the required threshold.
Disapproved(Hash),
/// A motion was executed; `bool` is true if returned without error.
Executed(Hash, bool),
}
impl<H, A> From<RawEvent<H, A>> for () {
fn from(_: RawEvent<H, A>) -> () { () }
}
decl_module! {
#[cfg_attr(feature = "std", serde(bound(deserialize = "<T as Trait>::Proposal: ::serde::de::DeserializeOwned")))]
pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin {
fn propose(origin, threshold: u32, proposal: Box<<T as Trait>::Proposal>) -> Result;
fn vote(origin, proposal: T::Hash, index: ProposalIndex, approve: bool) -> Result;
}
}
decl_storage! {
trait Store for Module<T: Trait> as CouncilMotions {
/// The (hashes of) the active proposals.
pub Proposals get(proposals): default Vec<T::Hash>;
/// Actual proposal for a given hash, if it's current.
pub ProposalOf get(proposal_of): map [ T::Hash => <T as Trait>::Proposal ];
/// Votes for a given proposal: (required_yes_votes, yes_voters, no_voters).
pub Voting get(voting): map [ T::Hash => (ProposalIndex, u32, Vec<T::AccountId>, Vec<T::AccountId>) ];
/// Proposals so far.
pub ProposalCount get(proposal_count): default u32;
}
}
impl<T: Trait> Module<T> {
/// Deposit one of this module's events.
fn deposit_event(event: Event<T>) {
<system::Module<T>>::deposit_event(<T as Trait>::Event::from(event).into());
}
pub fn is_councillor(who: &T::AccountId) -> bool {
<Council<T>>::active_council().iter()
.any(|&(ref a, _)| a == who)
}
// Dispatch
fn propose(origin: <T as system::Trait>::Origin, threshold: u32, proposal: Box<<T as Trait>::Proposal>) -> Result {
let who = ensure_signed(origin)?;
ensure!(Self::is_councillor(&who), "proposer not on council");
let proposal_hash = T::Hashing::hash_of(&proposal);
ensure!(!<ProposalOf<T>>::exists(proposal_hash), "duplicate proposals not allowed");
if threshold < 2 {
let ok = proposal.dispatch(Origin::Members(1).into()).is_ok();
Self::deposit_event(RawEvent::Executed(proposal_hash, ok));
} else {
let index = Self::proposal_count();
<ProposalCount<T>>::mutate(|i| *i += 1);
<Proposals<T>>::mutate(|proposals| proposals.push(proposal_hash));
<ProposalOf<T>>::insert(proposal_hash, *proposal);
<Voting<T>>::insert(proposal_hash, (index, threshold, vec![who.clone()], vec![]));
Self::deposit_event(RawEvent::Proposed(who, index, proposal_hash, threshold));
}
Ok(())
}
fn vote(origin: <T as system::Trait>::Origin, proposal: T::Hash, index: ProposalIndex, approve: bool) -> Result {
let who = ensure_signed(origin)?;
ensure!(Self::is_councillor(&who), "voter not on council");
let mut voting = Self::voting(&proposal).ok_or("proposal must exist")?;
ensure!(voting.0 == index, "mismatched index");
let position_yes = voting.2.iter().position(|a| a == &who);
let position_no = voting.3.iter().position(|a| a == &who);
if approve {
if position_yes.is_none() {
voting.2.push(who.clone());
} else {
return Err("duplicate vote ignored")
}
if let Some(pos) = position_no {
voting.3.swap_remove(pos);
}
} else {
if position_no.is_none() {
voting.3.push(who.clone());
} else {
return Err("duplicate vote ignored")
}
if let Some(pos) = position_yes {
voting.2.swap_remove(pos);
}
}
let yes_votes = voting.2.len() as u32;
let no_votes = voting.3.len() as u32;
Self::deposit_event(RawEvent::Voted(who, proposal, approve, yes_votes, no_votes));
let threshold = voting.1;
let potential_votes = <Council<T>>::active_council().len() as u32;
let approved = yes_votes >= threshold;
let disapproved = potential_votes - no_votes < threshold;
if approved || disapproved {
if approved {
Self::deposit_event(RawEvent::Approved(proposal));
// execute motion, assuming it exists.
if let Some(p) = <ProposalOf<T>>::take(&proposal) {
let ok = p.dispatch(Origin::Members(threshold).into()).is_ok();
Self::deposit_event(RawEvent::Executed(proposal, ok));
}
} else {
// disapproved
Self::deposit_event(RawEvent::Disapproved(proposal));
}
// remove vote
<Voting<T>>::remove(&proposal);
<Proposals<T>>::mutate(|proposals| proposals.retain(|h| h != &proposal));
} else {
// update voting
<Voting<T>>::insert(&proposal, voting);
}
Ok(())
}
}
/// Ensure that the origin `o` represents at least `n` council members. Returns
/// `Ok` or an `Err` otherwise.
pub fn ensure_council_members<OuterOrigin>(o: OuterOrigin, n: u32) -> result::Result<u32, &'static str>
where OuterOrigin: Into<Option<Origin>>
{
match o.into() {
Some(Origin::Members(x)) if x >= n => Ok(n),
_ => Err("bad origin: expected to be a threshold number of council members"),
}
}
pub struct EnsureMembers<N: U32>(::rstd::marker::PhantomData<N>);
impl<O, N: U32> EnsureOrigin<O> for EnsureMembers<N>
where O: Into<Option<Origin>>
{
type Success = u32;
fn ensure_origin(o: O) -> result::Result<Self::Success, &'static str> {
ensure_council_members(o, N::VALUE)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ::tests::*;
use ::tests::{Call, Origin, Event as OuterEvent};
use substrate_runtime_support::Hashable;
use system::{EventRecord, Phase};
type CouncilMotions = super::Module<Test>;
#[test]
fn motions_basic_environment_works() {
with_externalities(&mut new_test_ext(true), || {
System::set_block_number(1);
assert_eq!(Balances::free_balance(&42), 0);
assert_eq!(CouncilMotions::proposals(), Vec::<H256>::new());
});
}
fn set_balance_proposal(value: u64) -> Call {
Call::Balances(balances::Call::set_balance(balances::address::Address::Id(42), value, 0))
}
#[test]
fn motions_propose_works() {
with_externalities(&mut new_test_ext(true), || {
System::set_block_number(1);
let proposal = set_balance_proposal(42);
let hash = proposal.blake2_256().into();
assert_ok!(CouncilMotions::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
assert_eq!(CouncilMotions::proposals(), vec![hash]);
assert_eq!(CouncilMotions::proposal_of(&hash), Some(proposal));
assert_eq!(CouncilMotions::voting(&hash), Some((0, 3, vec![1], Vec::<u64>::new())));
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Proposed(1, 0, hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into(), 3))
}
]);
});
}
#[test]
fn motions_ignoring_non_council_proposals_works() {
with_externalities(&mut new_test_ext(true), || {
System::set_block_number(1);
let proposal = set_balance_proposal(42);
assert_noop!(CouncilMotions::propose(Origin::signed(42), 3, Box::new(proposal.clone())), "proposer not on council");
});
}
#[test]
fn motions_ignoring_non_council_votes_works() {
with_externalities(&mut new_test_ext(true), || {
System::set_block_number(1);
let proposal = set_balance_proposal(42);
let hash: H256 = proposal.blake2_256().into();
assert_ok!(CouncilMotions::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
assert_noop!(CouncilMotions::vote(Origin::signed(42), hash.clone(), 0, true), "voter not on council");
});
}
#[test]
fn motions_ignoring_bad_index_council_vote_works() {
with_externalities(&mut new_test_ext(true), || {
System::set_block_number(3);
let proposal = set_balance_proposal(42);
let hash: H256 = proposal.blake2_256().into();
assert_ok!(CouncilMotions::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
assert_noop!(CouncilMotions::vote(Origin::signed(2), hash.clone(), 1, true), "mismatched index");
});
}
#[test]
fn motions_revoting_works() {
with_externalities(&mut new_test_ext(true), || {
System::set_block_number(1);
let proposal = set_balance_proposal(42);
let hash: H256 = proposal.blake2_256().into();
assert_ok!(CouncilMotions::propose(Origin::signed(1), 2, Box::new(proposal.clone())));
assert_eq!(CouncilMotions::voting(&hash), Some((0, 2, vec![1], Vec::<u64>::new())));
assert_noop!(CouncilMotions::vote(Origin::signed(1), hash.clone(), 0, true), "duplicate vote ignored");
assert_ok!(CouncilMotions::vote(Origin::signed(1), hash.clone(), 0, false));
assert_eq!(CouncilMotions::voting(&hash), Some((0, 2, Vec::<u64>::new(), vec![1])));
assert_noop!(CouncilMotions::vote(Origin::signed(1), hash.clone(), 0, false), "duplicate vote ignored");
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Proposed(1, 0, hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into(), 2))
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Voted(1, hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into(), false, 0, 1))
}
]);
});
}
#[test]
fn motions_disapproval_works() {
with_externalities(&mut new_test_ext(true), || {
System::set_block_number(1);
let proposal = set_balance_proposal(42);
let hash: H256 = proposal.blake2_256().into();
assert_ok!(CouncilMotions::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
assert_ok!(CouncilMotions::vote(Origin::signed(2), hash.clone(), 0, false));
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Proposed(1, 0, hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into(), 3))
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Voted(2, hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into(), false, 1, 1))
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Disapproved(hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into()))
}
]);
});
}
#[test]
fn motions_approval_works() {
with_externalities(&mut new_test_ext(true), || {
System::set_block_number(1);
let proposal = set_balance_proposal(42);
let hash: H256 = proposal.blake2_256().into();
assert_ok!(CouncilMotions::propose(Origin::signed(1), 2, Box::new(proposal.clone())));
assert_ok!(CouncilMotions::vote(Origin::signed(2), hash.clone(), 0, true));
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Proposed(1, 0, hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into(), 2))
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Voted(2, hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into(), true, 2, 0))
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Approved(hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into()))
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::council_motions(RawEvent::Executed(hex!["a900ca23832b1f42a5d4af5d0ece88da63fbb4049cc00bac3f741eabb5a79c45"].into(), false))
}
]);
});
}
}
@@ -98,6 +98,8 @@ impl<T: Trait> Module<T> {
fn vote(origin: T::Origin, proposal: T::Hash, approve: bool) -> Result {
let who = ensure_signed(origin)?;
ensure!(Self::is_councillor(&who), "only councillors may vote on council proposals");
if Self::vote_of((proposal, who.clone())).is_none() {
let mut voters = Self::proposal_voters(&proposal);
voters.push(who.clone());
@@ -220,7 +222,7 @@ impl<T: Trait> OnFinalise<T::BlockNumber> for Council<T> {
mod tests {
use super::*;
use ::tests::*;
use ::tests::Call;
use ::tests::{Call, Origin};
use substrate_runtime_support::Hashable;
use democracy::VoteThreshold;
@@ -471,4 +473,14 @@ mod tests {
assert_noop!(CouncilVoting::propose(Origin::signed(4), Box::new(proposal)), "proposer would not be on council");
});
}
#[test]
fn vote_by_public_should_not_work() {
with_externalities(&mut new_test_ext(true), || {
System::set_block_number(1);
let proposal = set_balance_proposal(42);
assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone())));
assert_noop!(CouncilVoting::vote(Origin::signed(4), proposal.blake2_256().into(), true), "only councillors may vote on council proposals");
});
}
}
@@ -87,6 +87,40 @@ impl BuildStorage for StorageMap {
}
}
/// Permill is parts-per-million (i.e. after multiplying by this, divide by 1000000).
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq)]
pub struct Permill(u32);
// TODO: impl Mul<Permill> for N where N: As<usize>
impl Permill {
pub fn times<N: traits::As<usize> + ::rstd::ops::Mul<N, Output=N> + ::rstd::ops::Div<N, Output=N>>(self, b: N) -> N {
// TODO: handle overflows
b * <N as traits::As<usize>>::sa(self.0 as usize) / <N as traits::As<usize>>::sa(1000000)
}
pub fn from_millionths(x: u32) -> Permill { Permill(x) }
pub fn from_percent(x: u32) -> Permill { Permill(x * 10_000) }
#[cfg(feature = "std")]
pub fn from_fraction(x: f64) -> Permill { Permill((x * 1_000_000.0) as u32) }
}
#[cfg(feature = "std")]
impl From<f64> for Permill {
fn from(x: f64) -> Permill {
Permill::from_fraction(x)
}
}
#[cfg(feature = "std")]
impl From<f32> for Permill {
fn from(x: f32) -> Permill {
Permill::from_fraction(x as f64)
}
}
/// Ed25519 signature verify.
#[derive(Eq, PartialEq, Clone, Default, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
@@ -47,6 +47,12 @@ pub trait Verify {
fn verify<L: Lazy<[u8]>>(&self, msg: L, signer: &Self::Signer) -> bool;
}
/// Some sort of check on the origin is performed by this object.
pub trait EnsureOrigin<OuterOrigin> {
type Success;
fn ensure_origin(o: OuterOrigin) -> Result<Self::Success, &'static str>;
}
/// Means of changing one type into another in a manner dependent on the source type.
pub trait Lookup {
/// Type to lookup from.
@@ -45,7 +45,7 @@ extern crate safe_mix;
use rstd::prelude::*;
use primitives::traits::{self, CheckEqual, SimpleArithmetic, SimpleBitOps, Zero, One, Bounded,
Hash, Member, MaybeDisplay};
Hash, Member, MaybeDisplay, EnsureOrigin};
use runtime_support::{StorageValue, StorageMap, Parameter};
use safe_mix::TripletMix;
@@ -168,6 +168,14 @@ decl_storage! {
}
}
pub struct EnsureRoot<AccountId>(::rstd::marker::PhantomData<AccountId>);
impl<O: Into<Option<RawOrigin<AccountId>>>, AccountId> EnsureOrigin<O> for EnsureRoot<AccountId> {
type Success = ();
fn ensure_origin(o: O) -> Result<Self::Success, &'static str> {
ensure_root(o)
}
}
/// Ensure that the origin `o` represents a signed extrinsic (i.e. transaction).
/// Returns `Ok` with the account that signed the extrinsic or an `Err` otherwise.
pub fn ensure_signed<OuterOrigin, AccountId>(o: OuterOrigin) -> Result<AccountId, &'static str>
+24 -27
View File
@@ -18,13 +18,13 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg_attr(any(feature = "std", test), macro_use)]
#[cfg_attr(feature = "std", macro_use)]
extern crate substrate_runtime_std as rstd;
#[macro_use]
extern crate substrate_runtime_support as runtime_support;
#[cfg(any(feature = "std", test))]
#[cfg(feature = "std")]
extern crate substrate_runtime_io as runtime_io;
#[cfg(feature = "std")]
@@ -41,10 +41,10 @@ extern crate substrate_runtime_primitives as runtime_primitives;
extern crate substrate_runtime_system as system;
extern crate substrate_runtime_balances as balances;
use rstd::ops::{Mul, Div};
use rstd::prelude::*;
use runtime_support::{StorageValue, StorageMap};
use runtime_support::dispatch::Result;
use runtime_primitives::traits::{As, OnFinalise, Zero};
use runtime_primitives::{Permill, traits::{OnFinalise, Zero, EnsureOrigin}};
use balances::OnMinted;
use system::{ensure_signed, ensure_root};
@@ -54,6 +54,12 @@ use system::{ensure_signed, ensure_root};
///
/// `system::Trait` should always be included in our implied traits.
pub trait Trait: balances::Trait {
/// Origin from which approvals must come.
type ApproveOrigin: EnsureOrigin<Self::Origin>;
/// Origin from which rejections must come.
type RejectOrigin: EnsureOrigin<Self::Origin>;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
@@ -95,19 +101,6 @@ pub struct Proposal<AccountId, Balance> {
bond: Balance,
}
/// Permill is parts-per-million (i.e. after multiplying by this, divide by `PERMILL`).
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq)]
pub struct Permill(u32);
// TODO: impl Mul<Permill> for N where N: As<usize>
impl Permill {
fn times<N: As<usize> + Mul<N, Output=N> + Div<N, Output=N>>(self, b: N) -> N {
// TODO: handle overflows
b * <N as As<usize>>::sa(self.0 as usize) / <N as As<usize>>::sa(1000000)
}
}
decl_storage! {
trait Store for Module<T: Trait> as Treasury {
// Config...
@@ -192,7 +185,8 @@ impl<T: Trait> Module<T> {
}
fn reject_proposal(origin: T::Origin, proposal_id: ProposalIndex) -> Result {
ensure_root(origin)?;
T::RejectOrigin::ensure_origin(origin)?;
let proposal = <Proposals<T>>::take(proposal_id).ok_or("No proposal at that index")?;
let value = proposal.bond;
@@ -202,7 +196,8 @@ impl<T: Trait> Module<T> {
}
fn approve_proposal(origin: T::Origin, proposal_id: ProposalIndex) -> Result {
ensure_root(origin)?;
T::ApproveOrigin::ensure_origin(origin)?;
ensure!(<Proposals<T>>::exists(proposal_id), "No proposal at that index");
<Approvals<T>>::mutate(|v| v.push(proposal_id));
@@ -298,7 +293,7 @@ impl<T: Trait> OnFinalise<T::BlockNumber> for Module<T> {
}
}
#[cfg(any(feature = "std", test))]
#[cfg(feature = "std")]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
@@ -311,19 +306,19 @@ pub struct GenesisConfig<T: Trait> {
pub burn: Permill,
}
#[cfg(any(feature = "std", test))]
#[cfg(feature = "std")]
impl<T: Trait> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig {
proposal_bond: Default::default(),
proposal_bond_minimum: Default::default(),
spend_period: Default::default(),
spend_period: runtime_primitives::traits::One::one(),
burn: Default::default(),
}
}
}
#[cfg(any(feature = "std", test))]
#[cfg(feature = "std")]
impl<T: Trait> runtime_primitives::BuildStorage for GenesisConfig<T>
{
fn build_storage(self) -> ::std::result::Result<runtime_primitives::StorageMap, String> {
@@ -372,6 +367,8 @@ mod tests {
type Event = ();
}
impl Trait for Test {
type ApproveOrigin = system::EnsureRoot<u64>;
type RejectOrigin = system::EnsureRoot<u64>;
type Event = ();
}
type Balances = balances::Module<Test>;
@@ -389,10 +386,10 @@ mod tests {
reclaim_rebate: 0,
}.build_storage().unwrap());
t.extend(GenesisConfig::<Test>{
proposal_bond: Permill(50_000), // 5%
proposal_bond: Permill::from_percent(5),
proposal_bond_minimum: 1,
spend_period: 2,
burn: Permill(500_000), // 50%
burn: Permill::from_percent(50),
}.build_storage().unwrap());
t.into()
}
@@ -400,10 +397,10 @@ mod tests {
#[test]
fn genesis_config_works() {
with_externalities(&mut new_test_ext(), || {
assert_eq!(Treasury::proposal_bond(), Permill(50_000));
assert_eq!(Treasury::proposal_bond(), Permill::from_percent(5));
assert_eq!(Treasury::proposal_bond_minimum(), 1);
assert_eq!(Treasury::spend_period(), 2);
assert_eq!(Treasury::burn(), Permill(500_000));
assert_eq!(Treasury::burn(), Permill::from_percent(50));
assert_eq!(Treasury::pot(), 0);
assert_eq!(Treasury::proposal_count(), 0);
});