Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Add trivial EnsureFounder verifier to society #4615

Merged
merged 7 commits into from
Jan 14, 2020
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 69 additions & 57 deletions frame/society/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,49 +15,49 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.

//! # Society Module
//!
//!
//! - [`society::Trait`](./trait.Trait.html)
//! - [`Call`](./enum.Call.html)
//!
//!
//! ## Overview
//!
//!
//! The Society module is an economic game which incentivizes users to participate
//! and maintain a membership society.
//!
//! and maintain a membership society.
//!
//! ### User Types
//!
//!
//! At any point, a user in the society can be one of a:
//! * Bidder - A user who has submitted intention of joining the society.
//! * Candidate - A user who will be voted on to join the society.
//! * Suspended Candidate - A user who failed to win a vote.
//! * Member - A user who is a member of the society.
//! * Suspended Member - A member of the society who has accumulated too many strikes
//! or failed their membership challenge.
//!
//!
//! Of the non-suspended members, there is always a:
//! * Head - A member who is exempt from suspension.
//! * Defender - A member whose membership is under question and voted on again.
//!
//!
//! Of the non-suspended members of the society, a random set of them are chosen as
//! "skeptics". The mechanics of skeptics is explained in the
//! [member phase](#member-phase) below.
//!
//!
//! ### Mechanics
//!
//!
//! #### Rewards
//!
//!
//! Members are incentivized to participate in the society through rewards paid
//! by the Society treasury. These payments have a maturity period that the user
//! must wait before they are able to access the funds.
//!
//!
//! #### Punishments
//!
//!
//! Members can be punished by slashing the reward payouts that have not been
//! collected. Additionally, members can accumulate "strikes", and when they
//! reach a max strike limit, they become suspended.
//!
//!
//! #### Skeptics
//!
//!
//! During the voting period, a random set of members are selected as "skeptics".
//! These skeptics are expected to vote on the current candidates. If they do not vote,
//! their skeptic status is treated as a rejection vote, the member is deemed
Expand All @@ -72,25 +72,25 @@
//! assuming no one else votes, the defender always get a free vote on their
//! own challenge keeping them in the society. The Head member is exempt from the
//! negative outcome of a membership challenge.
//!
//!
//! #### Society Treasury
//!
//! The membership society is independently funded by a treasury managed by this
//! module. Some subset of this treasury is placed in a Society Pot, which is used
//! to determine the number of accepted bids.
//!
//! #### Rate of Growth
//!
//!
//! The membership society can grow at a rate of 10 accepted candidates per rotation period up
//! to the max membership threshold. Once this threshold is met, candidate selections
//! are stalled until there is space for new members to join. This can be resolved by
//! voting out existing members through the random challenges or by using governance
//! to increase the maximum membership count.
//!
//!
//! ### User Life Cycle
//!
//!
//! A user can go through the following phases:
//!
//!
//! ```ignore
//! +-------> User <----------+
//! | + |
Expand All @@ -115,40 +115,40 @@
//! | |
//! +------------------Society---------------------+
//! ```
//!
//!
//! #### Initialization
//!
//!
//! The society is initialized with a single member who is automatically chosen as the Head.
//!
//!
//! #### Bid Phase
//!
//!
//! New users must have a bid to join the society.
//!
//!
//! A user can make a bid by reserving a deposit. Alternatively, an already existing member
//! can create a bid on a user's behalf by "vouching" for them.
//!
//!
//! A bid includes reward information that the user would like to receive for joining
//! the society. A vouching bid can additionally request some portion of that reward as a tip
//! to the voucher for vouching for the prospective candidate.
//!
//!
//! Every rotation period, Bids are ordered by reward amount, and the module
//! selects as many bids the Society Pot can support for that period.
//!
//!
//! These selected bids become candidates and move on to the Candidate phase.
//! Bids that were not selected stay in the bidder pool until they are selected or
//! a user chooses to "unbid".
//!
//!
//! #### Candidate Phase
//!
//!
//! Once a bidder becomes a candidate, members vote whether to approve or reject
//! that candidate into society. This voting process also happens during a rotation period.
//!
//!
//! The approval and rejection criteria for candidates are not set on chain,
//! and may change for different societies.
//!
//!
//! At the end of the rotation period, we collect the votes for a candidate
//! and randomly select a vote as the final outcome.
//!
//!
//! ```ignore
//! [ a-accept, r-reject, s-skeptic ]
//! +----------------------------------+
Expand All @@ -163,63 +163,63 @@
//!
//! Result: Rejected
//! ```
//!
//!
//! Each member that voted opposite to this randomly selected vote is punished by
//! slashing their unclaimed payouts and increasing the number of strikes they have.
//!
//!
//! These slashed funds are given to a random user who voted the same as the
//! selected vote as a reward for participating in the vote.
//!
//!
//! If the candidate wins the vote, they receive their bid reward as a future payout.
//! If the bid was placed by a voucher, they will receive their portion of the reward,
//! before the rest is paid to the winning candidate.
//!
//! One winning candidate is selected as the Head of the members. This is randomly
//! chosen, weighted by the number of approvals the winning candidates accumulated.
//!
//!
//! If the candidate loses the vote, they are suspended and it is up to the Suspension
//! Judgement origin to determine if the candidate should go through the bidding process
//! again, should be accepted into the membership society, or rejected and their deposit
//! slashed.
//!
//!
//! #### Member Phase
//!
//!
//! Once a candidate becomes a member, their role is to participate in society.
//!
//!
//! Regular participation involves voting on candidates who want to join the membership
//! society, and by voting in the right way, a member will accumulate future payouts.
//! When a payout matures, members are able to claim those payouts.
//!
//!
//! Members can also vouch for users to join the society, and request a "tip" from
//! the fees the new member would collect by joining the society. This vouching
//! process is useful in situations where a user may not have enough balance to
//! satisfy the bid deposit. A member can only vouch one user at a time.
//!
//!
//! During rotation periods, a random group of members are selected as "skeptics".
//! These skeptics are expected to vote on the current candidates. If they do not vote,
//! their skeptic status is treated as a rejection vote, the member is deemed
//! "lazy", and are given a strike per missing vote.
//!
//!
//! There is a challenge period in parallel to the rotation period. During a challenge period,
//! a random member is selected to defend their membership to the society. Other members
//! make a traditional majority-wins vote to determine if the member should stay in the society.
//! Ties are treated as a failure of the challenge.
//!
//!
//! If a member accumulates too many strikes or fails their membership challenge,
//! they will become suspended. While a member is suspended, they are unable to
//! claim matured payouts. It is up to the Suspension Judgement origin to determine
//! if the member should re-enter society or be removed from society with all their
//! future payouts slashed.
//!
//!
//! ## Interface
//!
//!
//! ### Dispatchable Functions
//!
//!
//! #### For General Users
//!
//!
//! * `bid` - A user can make a bid to join the membership society by reserving a deposit.
//! * `unbid` - A user can withdraw their bid for entry, the deposit is returned.
//!
//!
//! #### For Members
//!
//! * `vouch` - A member can place a bid on behalf of a user to join the membership society.
Expand All @@ -228,9 +228,9 @@
//! * `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.
//!
//!
//! #### For Super Users
//!
//!
//! * `found` - The founder origin can initiate this society. Useful for bootstrapping the Society
//! pallet on an already running chain.
//! * `judge_suspended_member` - The suspension judgement origin is able to make
Expand Down Expand Up @@ -444,7 +444,7 @@ decl_storage! {

/// The defending member currently being challenged.
Defender get(fn defender): Option<T::AccountId>;

/// Votes for the defender.
DefenderVotes: map hasher(twox_64_concat) T::AccountId => Option<Vote>;

Expand Down Expand Up @@ -849,7 +849,7 @@ decl_module! {
fn judge_suspended_member(origin, who: T::AccountId, forgive: bool) {
T::SuspensionJudgementOrigin::ensure_origin(origin)?;
ensure!(<SuspendedMembers<T, I>>::exists(&who), Error::<T, I>::NotSuspended);

if forgive {
// Try to add member back to society. Can fail with `MaxMembers` limit.
Self::add_member(&who)?;
Expand Down Expand Up @@ -1081,6 +1081,18 @@ decl_event! {
}
}

/// Simple ensure origin struct to filter for the founder account.
pub struct EnsureFounder<T>(sp_std::marker::PhantomData<T>);
impl<T: Trait> EnsureOrigin<T::Origin> for EnsureFounder<T> {
type Success = T::AccountId;
fn try_origin(o: T::Origin) -> Result<Self::Success, T::Origin> {
o.into().and_then(|o| match (o, Members::<T>::get().get(0)) {
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
(system::RawOrigin::Signed(ref who), Some(f)) if who == f => Ok(who.clone()),
(r, _) => Err(T::Origin::from(r)),
})
}
}

/// Pick an item at pseudo-random from the slice, given the `rng`. `None` iff the slice is empty.
fn pick_item<'a, R: RngCore, T>(rng: &mut R, items: &'a [T]) -> Option<&'a T> {
if items.is_empty() {
Expand Down Expand Up @@ -1251,7 +1263,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
.filter_map(|m| <Votes<T, I>>::take(&candidate, m).map(|v| (v, m)))
.inspect(|&(v, _)| if v == Vote::Approve { approval_count += 1 })
.collect::<Vec<_>>();

// Select one of the votes at random.
// Note that `Vote::Skeptical` and `Vote::Reject` both reject the candidate.
let is_accepted = pick_item(&mut rng, &votes).map(|x| x.0) == Some(Vote::Approve);
Expand Down Expand Up @@ -1325,15 +1337,15 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {

// if at least one candidate was accepted...
if !accepted.is_empty() {
// select one as primary, randomly chosen from the accepted, weighted by approvals.
// select one as primary, randomly chosen from the accepted, weighted by approvals.
// Choose a random number between 0 and `total_approvals`
let primary_point = pick_usize(&mut rng, total_approvals - 1);
// Find the zero bid or the user who falls on that point
let primary = accepted.iter().find(|e| e.2.is_zero() || e.1 > primary_point)
.expect("e.1 of final item == total_approvals; \
worst case find will always return that item; qed")
.0.clone();

let accounts = accepted.into_iter().map(|x| x.0).collect::<Vec<_>>();

// Then write everything back out, signal the changed membership and leave an event.
Expand Down Expand Up @@ -1521,7 +1533,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {

// The list of selected candidates
let mut selected = Vec::new();

if bids.len() > 0 {
// Can only select at most the length of bids
max_selections = max_selections.min(bids.len());
Expand Down