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
@@ -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);
});