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.
This commit is contained in:
Gavin Wood
2020-01-18 18:35:25 +01:00
committed by GitHub
parent 1b9baad04f
commit 26fa5193cd
3 changed files with 86 additions and 15 deletions
+47 -5
View File
@@ -228,6 +228,7 @@
//! * `defender_vote` - A member can vote to approve or reject a defender's continued membership //! * `defender_vote` - A member can vote to approve or reject a defender's continued membership
//! to the society. //! to the society.
//! * `payout` - A member can claim their first matured payment. //! * `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 //! #### For Super Users
//! //!
@@ -254,7 +255,7 @@ use sp_std::prelude::*;
use codec::{Encode, Decode}; use codec::{Encode, Decode};
use sp_runtime::{Percent, ModuleId, RuntimeDebug, use sp_runtime::{Percent, ModuleId, RuntimeDebug,
traits::{ traits::{
StaticLookup, AccountIdConversion, Saturating, Zero, IntegerSquareRoot, StaticLookup, AccountIdConversion, Saturating, Zero, IntegerSquareRoot, Hash,
TrailingZeroInput, CheckedSub, EnsureOrigin TrailingZeroInput, CheckedSub, EnsureOrigin
} }
}; };
@@ -404,6 +405,10 @@ decl_storage! {
pub Founder get(founder) build(|config: &GenesisConfig<T, I>| config.members.first().cloned()): pub Founder get(founder) build(|config: &GenesisConfig<T, I>| config.members.first().cloned()):
Option<T::AccountId>; Option<T::AccountId>;
/// 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<T::Hash>;
/// The current set of candidates; bidders that are attempting to become members. /// The current set of candidates; bidders that are attempting to become members.
pub Candidates get(candidates): Vec<Bid<T::AccountId, BalanceOf<T, I>>>; pub Candidates get(candidates): Vec<Bid<T::AccountId, BalanceOf<T, I>>>;
@@ -805,6 +810,7 @@ decl_module! {
/// Parameters: /// Parameters:
/// - `founder` - The first member and head of the newly founded society. /// - `founder` - The first member and head of the newly founded society.
/// - `max_members` - The initial max number of members for the society. /// - `max_members` - The initial max number of members for the society.
/// - `rules` - The rules of this society concerning membership.
/// ///
/// # <weight> /// # <weight>
/// - Two storage mutates to set `Head` and `Founder`. O(1) /// - Two storage mutates to set `Head` and `Founder`. O(1)
@@ -814,7 +820,7 @@ decl_module! {
/// Total Complexity: O(1) /// Total Complexity: O(1)
/// # </weight> /// # </weight>
#[weight = SimpleDispatchInfo::FixedNormal(10_000)] #[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<u8>) {
T::FounderSetOrigin::ensure_origin(origin)?; T::FounderSetOrigin::ensure_origin(origin)?;
ensure!(!<Head<T, I>>::exists(), Error::<T, I>::AlreadyFounded); ensure!(!<Head<T, I>>::exists(), Error::<T, I>::AlreadyFounded);
ensure!(max_members > 1, Error::<T, I>::MaxMembers); ensure!(max_members > 1, Error::<T, I>::MaxMembers);
@@ -823,8 +829,38 @@ decl_module! {
Self::add_member(&founder)?; Self::add_member(&founder)?;
<Head<T, I>>::put(&founder); <Head<T, I>>::put(&founder);
<Founder<T, I>>::put(&founder); <Founder<T, I>>::put(&founder);
Rules::<T, I>::put(T::Hashing::hash(&rules));
Self::deposit_event(RawEvent::Founded(founder)); 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.
///
/// # <weight>
/// - Two storage reads O(1).
/// - Four storage removals O(1).
/// - One event.
///
/// Total Complexity: O(1)
/// # </weight>
#[weight = SimpleDispatchInfo::FixedNormal(20_000)]
fn unfound(origin) {
let founder = ensure_signed(origin)?;
ensure!(Founder::<T, I>::get() == Some(founder.clone()), Error::<T, I>::NotFounder);
ensure!(Head::<T, I>::get() == Some(founder.clone()), Error::<T, I>::NotHead);
Members::<T, I>::kill();
Head::<T, I>::kill();
Founder::<T, I>::kill();
Rules::<T, I>::kill();
Candidates::<T, I>::kill();
SuspendedCandidates::<T, I>::remove_all();
Self::deposit_event(RawEvent::Unfounded(founder));
}
/// Allow suspension judgement origin to make judgement on a suspended member. /// 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 /// If a suspended member is forgiven, we simply add them back as a member, not affecting
@@ -1047,6 +1083,10 @@ decl_error! {
NotCandidate, NotCandidate,
/// Too many members in the society. /// Too many members in the society.
MaxMembers, MaxMembers,
/// The caller is not the founder.
NotFounder,
/// The caller is not the head.
NotHead,
} }
} }
@@ -1087,6 +1127,8 @@ decl_event! {
DefenderVote(AccountId, bool), DefenderVote(AccountId, bool),
/// A new max member count has been set /// A new max member count has been set
NewMaxMembers(u32), NewMaxMembers(u32),
/// Society is unfounded.
Unfounded(AccountId),
} }
} }
@@ -1224,16 +1266,16 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
ensure!(Self::head() != Some(m.clone()), Error::<T, I>::Head); ensure!(Self::head() != Some(m.clone()), Error::<T, I>::Head);
ensure!(Self::founder() != Some(m.clone()), Error::<T, I>::Founder); ensure!(Self::founder() != Some(m.clone()), Error::<T, I>::Founder);
<Members<T, I>>::mutate(|members| let mut members = <Members<T, I>>::get();
match members.binary_search(&m) { match members.binary_search(&m) {
Err(_) => Err(Error::<T, I>::NotMember)?, Err(_) => Err(Error::<T, I>::NotMember)?,
Ok(i) => { Ok(i) => {
members.remove(i); members.remove(i);
T::MembershipChanged::change_members_sorted(&[], &[m.clone()], members); T::MembershipChanged::change_members_sorted(&[], &[m.clone()], &members[..]);
<Members<T, I>>::put(members);
Ok(()) Ok(())
} }
} }
)
} }
/// End the current period and begin a new one. /// End the current period and begin a new one.
+32 -3
View File
@@ -21,6 +21,7 @@ use mock::*;
use frame_support::{assert_ok, assert_noop}; use frame_support::{assert_ok, assert_noop};
use sp_runtime::traits::BadOrigin; use sp_runtime::traits::BadOrigin;
use sp_core::blake2_256;
#[test] #[test]
fn founding_works() { fn founding_works() {
@@ -31,9 +32,9 @@ fn founding_works() {
assert_eq!(Society::pot(), 0); assert_eq!(Society::pot(), 0);
// Account 1 is set as the founder origin // Account 1 is set as the founder origin
// Account 5 cannot start a society // 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 // 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 // Society members only include 10
assert_eq!(Society::members(), vec![10]); assert_eq!(Society::members(), vec![10]);
// 10 is the head of the society // 10 is the head of the society
@@ -42,11 +43,39 @@ fn founding_works() {
assert_eq!(Society::founder(), Some(10)); assert_eq!(Society::founder(), Some(10));
// 100 members max // 100 members max
assert_eq!(Society::max_members(), 100); 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 // Pot grows after first rotation period
run_to_block(4); run_to_block(4);
assert_eq!(Society::pot(), 1000); assert_eq!(Society::pot(), 1000);
// Cannot start another society // Cannot start another society
assert_noop!(Society::found(Origin::signed(1), 20, 100), Error::<Test, _>::AlreadyFounded); assert_noop!(
Society::found(Origin::signed(1), 20, 100, vec![]),
Error::<Test, _>::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::<Test, _>::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::<Test, _>::NotHead);
}); });
} }