From f06aa9a0b49097b5446d217bc665c43b8b8d17e1 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Sat, 18 Jan 2020 18:35:25 +0100 Subject: [PATCH] Add rules and unfounding to society. (#4671) * Add rules and unfounding to society. * Docs and event * Extra bit of docs. * Cunningly reduce complexity * Remove candidates when unfounding. * Remove suspended candidates when unfounding, too. --- frame/democracy/src/lib.rs | 2 +- frame/society/src/lib.rs | 64 +++++++++++++++++++++++++++++++------- frame/society/src/tests.rs | 35 +++++++++++++++++++-- 3 files changed, 86 insertions(+), 15 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 354e93cc292ee..d7790be8f7065 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -497,7 +497,7 @@ decl_module! { } /// Vote in a referendum on behalf of a stash. If `vote.is_aye()`, the vote is to enact - /// the proposal; otherwise it is a vote to keep the status quo. + /// the proposal; otherwise it is a vote to keep the status quo. /// /// # /// - O(1). diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 348607d196c86..f4e5904ea42d7 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -228,6 +228,7 @@ //! * `defender_vote` - A member can vote to approve or reject a defender's continued membership //! to the society. //! * `payout` - A member can claim their first matured payment. +//! * `unfound` - Allow the founder to unfound the society when they are the only member. //! //! #### For Super Users //! @@ -254,7 +255,7 @@ use sp_std::prelude::*; use codec::{Encode, Decode}; use sp_runtime::{Percent, ModuleId, RuntimeDebug, traits::{ - StaticLookup, AccountIdConversion, Saturating, Zero, IntegerSquareRoot, + StaticLookup, AccountIdConversion, Saturating, Zero, IntegerSquareRoot, Hash, TrailingZeroInput, CheckedSub, EnsureOrigin } }; @@ -404,6 +405,10 @@ decl_storage! { pub Founder get(founder) build(|config: &GenesisConfig| config.members.first().cloned()): Option; + /// A hash of the rules of this society concerning membership. Can only be set once and + /// only by the founder. + pub Rules get(rules): Option; + /// The current set of candidates; bidders that are attempting to become members. pub Candidates get(candidates): Vec>>; @@ -805,6 +810,7 @@ decl_module! { /// Parameters: /// - `founder` - The first member and head of the newly founded society. /// - `max_members` - The initial max number of members for the society. + /// - `rules` - The rules of this society concerning membership. /// /// # /// - Two storage mutates to set `Head` and `Founder`. O(1) @@ -814,7 +820,7 @@ decl_module! { /// Total Complexity: O(1) /// # #[weight = SimpleDispatchInfo::FixedNormal(10_000)] - fn found(origin, founder: T::AccountId, max_members: u32) { + fn found(origin, founder: T::AccountId, max_members: u32, rules: Vec) { T::FounderSetOrigin::ensure_origin(origin)?; ensure!(!>::exists(), Error::::AlreadyFounded); ensure!(max_members > 1, Error::::MaxMembers); @@ -823,8 +829,38 @@ decl_module! { Self::add_member(&founder)?; >::put(&founder); >::put(&founder); + Rules::::put(T::Hashing::hash(&rules)); Self::deposit_event(RawEvent::Founded(founder)); } + + /// Anull the founding of the society. + /// + /// The dispatch origin for this call must be Signed, and the signing account must be both + /// the `Founder` and the `Head`. This implies that it may only be done when there is one + /// member. + /// + /// # + /// - Two storage reads O(1). + /// - Four storage removals O(1). + /// - One event. + /// + /// Total Complexity: O(1) + /// # + #[weight = SimpleDispatchInfo::FixedNormal(20_000)] + fn unfound(origin) { + let founder = ensure_signed(origin)?; + ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); + ensure!(Head::::get() == Some(founder.clone()), Error::::NotHead); + + Members::::kill(); + Head::::kill(); + Founder::::kill(); + Rules::::kill(); + Candidates::::kill(); + SuspendedCandidates::::remove_all(); + Self::deposit_event(RawEvent::Unfounded(founder)); + } + /// Allow suspension judgement origin to make judgement on a suspended member. /// /// If a suspended member is forgiven, we simply add them back as a member, not affecting @@ -1047,6 +1083,10 @@ decl_error! { NotCandidate, /// Too many members in the society. MaxMembers, + /// The caller is not the founder. + NotFounder, + /// The caller is not the head. + NotHead, } } @@ -1087,6 +1127,8 @@ decl_event! { DefenderVote(AccountId, bool), /// A new max member count has been set NewMaxMembers(u32), + /// Society is unfounded. + Unfounded(AccountId), } } @@ -1224,16 +1266,16 @@ impl, I: Instance> Module { ensure!(Self::head() != Some(m.clone()), Error::::Head); ensure!(Self::founder() != Some(m.clone()), Error::::Founder); - >::mutate(|members| - match members.binary_search(&m) { - Err(_) => Err(Error::::NotMember)?, - Ok(i) => { - members.remove(i); - T::MembershipChanged::change_members_sorted(&[], &[m.clone()], members); - Ok(()) - } + let mut members = >::get(); + match members.binary_search(&m) { + Err(_) => Err(Error::::NotMember)?, + Ok(i) => { + members.remove(i); + T::MembershipChanged::change_members_sorted(&[], &[m.clone()], &members[..]); + >::put(members); + Ok(()) } - ) + } } /// End the current period and begin a new one. diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 580edc36431b8..3e5afc47f500a 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -21,6 +21,7 @@ use mock::*; use frame_support::{assert_ok, assert_noop}; use sp_runtime::traits::BadOrigin; +use sp_core::blake2_256; #[test] fn founding_works() { @@ -31,9 +32,9 @@ fn founding_works() { assert_eq!(Society::pot(), 0); // Account 1 is set as the founder origin // Account 5 cannot start a society - assert_noop!(Society::found(Origin::signed(5), 20, 100), BadOrigin); + assert_noop!(Society::found(Origin::signed(5), 20, 100, vec![]), BadOrigin); // Account 1 can start a society, where 10 is the founding member - assert_ok!(Society::found(Origin::signed(1), 10, 100)); + assert_ok!(Society::found(Origin::signed(1), 10, 100, b"be cool".to_vec())); // Society members only include 10 assert_eq!(Society::members(), vec![10]); // 10 is the head of the society @@ -42,11 +43,39 @@ fn founding_works() { assert_eq!(Society::founder(), Some(10)); // 100 members max assert_eq!(Society::max_members(), 100); + // rules are correct + assert_eq!(Society::rules(), Some(blake2_256(b"be cool").into())); // Pot grows after first rotation period run_to_block(4); assert_eq!(Society::pot(), 1000); // Cannot start another society - assert_noop!(Society::found(Origin::signed(1), 20, 100), Error::::AlreadyFounded); + assert_noop!( + Society::found(Origin::signed(1), 20, 100, vec![]), + Error::::AlreadyFounded + ); + }); +} + +#[test] +fn unfounding_works() { + EnvBuilder::new().with_max_members(0).with_members(vec![]).execute(|| { + // Account 1 sets the founder... + assert_ok!(Society::found(Origin::signed(1), 10, 100, vec![])); + // Account 2 cannot unfound it as it's not the founder. + assert_noop!(Society::unfound(Origin::signed(2)), Error::::NotFounder); + // Account 10 can, though. + assert_ok!(Society::unfound(Origin::signed(10))); + + // 1 sets the founder to 20 this time + assert_ok!(Society::found(Origin::signed(1), 20, 100, vec![])); + // Bring in a new member... + assert_ok!(Society::bid(Origin::signed(10), 0)); + run_to_block(4); + assert_ok!(Society::vote(Origin::signed(20), 10, true)); + run_to_block(8); + + // Unfounding won't work now, even though it's from 20. + assert_noop!(Society::unfound(Origin::signed(20)), Error::::NotHead); }); }