-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Communities] Governance V0: Enqueue and Execute calls (#313)
* feat(pallets/communities): define MemberRank trait, for MembershipPassport * feat(pallets/communities): add structures and storage for handling governance * feat(pallets/communities): add governance (polls/proposals) handling functions * feat(pallets/communities): define open_proposal/execute_call * change(pallets/communities): explictly state call origin when creating proposal * fix(pallets/communities): lint * change(communities:functions/origin): use CheckedConversion to replace as_origin!
- Loading branch information
Showing
24 changed files
with
925 additions
and
57 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pub(self) use super::*; | ||
|
||
mod poll; | ||
mod proposals; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
use super::*; | ||
use sp_runtime::Permill; | ||
|
||
impl<T: Config> Pallet<T> { | ||
pub(crate) fn do_initiate_poll(community_id: &CommunityIdOf<T>) -> DispatchResult { | ||
Poll::<T>::insert(community_id, CommunityPoll::default()); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) fn do_vote_in_poll( | ||
who: &AccountIdOf<T>, | ||
community_id: &CommunityIdOf<T>, | ||
vote: CommunityPollVote<T>, | ||
) -> DispatchResult { | ||
Poll::<T>::try_mutate(community_id, |value| { | ||
let Some(mut poll) = value.clone() else { | ||
return Err(Error::<T>::PollAlreadyClosed)?; | ||
}; | ||
|
||
match vote { | ||
CommunityPollVote::Aye(weight) => { | ||
poll.ayes = poll | ||
.ayes | ||
.saturating_add(Self::get_vote_weight(who, community_id, weight)?) | ||
} | ||
CommunityPollVote::Nay(weight) => { | ||
poll.ayes = poll | ||
.nays | ||
.saturating_add(Self::get_vote_weight(who, community_id, weight)?) | ||
} | ||
} | ||
|
||
*value = Some(poll); | ||
|
||
Ok(()) | ||
}) | ||
} | ||
|
||
#[allow(dead_code)] | ||
pub(crate) fn do_close_poll(community_id: &CommunityIdOf<T>) -> DispatchResult { | ||
let poll_outcome = Self::do_calculate_poll_outcome(community_id)?; | ||
let proposal = Self::do_deequeue_proposal(community_id)?; | ||
|
||
match poll_outcome { | ||
// Schedule the approved proposal | ||
PollOutcome::Approved => Self::do_execute_proposal(proposal), | ||
// Do nothing | ||
PollOutcome::Rejected => Ok(()), | ||
}?; | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
impl<T: Config> Pallet<T> { | ||
fn get_vote_weight( | ||
who: &AccountIdOf<T>, | ||
community_id: &CommunityIdOf<T>, | ||
_input_weight: VoteWeightFor<T>, | ||
) -> Result<VoteWeightFor<T>, DispatchError> { | ||
let governance_strategy = Self::governance_strategy(community_id).ok_or(Error::<T>::CommunityDoesNotExist)?; | ||
|
||
match governance_strategy { | ||
CommunityGovernanceStrategy::AdminBased(admin) => { | ||
if *who == admin { | ||
Ok(1u32.into()) | ||
} else { | ||
Ok(0u32.into()) | ||
} | ||
} | ||
CommunityGovernanceStrategy::MemberCountPoll { min: _ } => Ok(1u32.into()), | ||
CommunityGovernanceStrategy::AssetWeighedPoll { | ||
asset_id: _, | ||
num: _, | ||
denum: _, | ||
} => todo!(), | ||
CommunityGovernanceStrategy::RankedWeighedPoll { num: _, denum: _ } => { | ||
// use crate::traits::rank::GetRank; | ||
|
||
// let membership = Self::ensure_member(community_id, who)?; | ||
// let rank = <MembershipPassportOf<T> as | ||
// GetRank<T::MembershipRank>>::rank_of(&membership); | ||
|
||
// Ok(input_weight.max(rank.into())) | ||
todo!() | ||
} | ||
} | ||
} | ||
|
||
fn do_calculate_poll_outcome(community_id: &CommunityIdOf<T>) -> Result<PollOutcome, DispatchError> { | ||
let governance_strategy = Self::governance_strategy(community_id).ok_or(Error::<T>::CommunityDoesNotExist)?; | ||
let poll = Self::poll(community_id).ok_or(Error::<T>::PollAlreadyClosed)?; | ||
|
||
let (ayes, nays) = (poll.ayes, poll.nays); | ||
|
||
match governance_strategy { | ||
CommunityGovernanceStrategy::AdminBased(_) => Ok(PollOutcome::Approved), | ||
CommunityGovernanceStrategy::MemberCountPoll { min } => { | ||
if ayes.saturating_add(nays) >= min { | ||
if ayes > nays { | ||
Ok(PollOutcome::Approved) | ||
} else { | ||
Ok(PollOutcome::Rejected) | ||
} | ||
} else { | ||
Err(Error::<T>::CannotClosePoll.into()) | ||
} | ||
} | ||
CommunityGovernanceStrategy::AssetWeighedPoll { | ||
asset_id: _, | ||
num, | ||
denum, | ||
} => { | ||
let criteria_fraction = Permill::from_rational(num, denum); | ||
let poll_fraction = Permill::from_rational(ayes, ayes.saturating_add(nays)); | ||
|
||
if poll_fraction >= criteria_fraction { | ||
Ok(PollOutcome::Approved) | ||
} else { | ||
Ok(PollOutcome::Rejected) | ||
} | ||
} | ||
CommunityGovernanceStrategy::RankedWeighedPoll { num, denum } => { | ||
let criteria_fraction = Permill::from_rational(num, denum); | ||
let poll_fraction = Permill::from_rational(ayes, ayes.saturating_add(nays)); | ||
|
||
if poll_fraction >= criteria_fraction { | ||
Ok(PollOutcome::Approved) | ||
} else { | ||
Ok(PollOutcome::Rejected) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
use super::*; | ||
pub(self) use frame_support::traits::{schedule::v3::Anon, StorePreimage}; | ||
pub(self) use sp_runtime::traits::Zero; | ||
|
||
impl<T: Config> Pallet<T> { | ||
pub(crate) fn do_create_proposal( | ||
proposer: &AccountIdOf<T>, | ||
community_id: &CommunityIdOf<T>, | ||
call_origin: PalletsOriginOf<T>, | ||
call: RuntimeCallOf<T>, | ||
) -> DispatchResult { | ||
Self::ensure_proposal_origin(community_id, call_origin.clone())?; | ||
|
||
let bounded_call = T::Preimage::bound(call).map_err(|_| Error::<T>::CannotEncodeCall)?; | ||
|
||
Self::do_enqueue_proposal( | ||
community_id, | ||
CommunityProposal { | ||
proposer: proposer.clone(), | ||
call: bounded_call, | ||
origin: call_origin, | ||
}, | ||
)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
#[allow(dead_code)] | ||
pub(crate) fn do_execute_proposal(proposal: CommunityProposal<T>) -> DispatchResult { | ||
T::Scheduler::schedule( | ||
frame_support::traits::schedule::DispatchTime::After(Zero::zero()), | ||
None, | ||
Default::default(), | ||
proposal.origin, | ||
proposal.call, | ||
) | ||
.map(|_| ()) | ||
} | ||
|
||
fn do_enqueue_proposal(community_id: &CommunityIdOf<T>, proposal: CommunityProposal<T>) -> DispatchResult { | ||
if Proposals::<T>::decode_len(community_id).unwrap_or_default() >= T::MaxProposals::get() as usize { | ||
Err(Error::<T>::ExceededMaxProposals)?; | ||
} | ||
|
||
Proposals::<T>::try_append(community_id, proposal).map_err(|_| Error::<T>::CannotEnqueueProposal)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) fn do_deequeue_proposal(community_id: &CommunityIdOf<T>) -> Result<CommunityProposal<T>, DispatchError> { | ||
Proposals::<T>::try_mutate(community_id, |proposals| { | ||
let first_proposal = proposals.first().ok_or(Error::<T>::CannotDequeueProposal)?.clone(); | ||
proposals.remove(0); | ||
|
||
Ok(first_proposal) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use sp_runtime::traits::CheckedConversion; | ||
|
||
use super::*; | ||
|
||
impl<T: Config> Pallet<T> { | ||
pub(crate) fn ensure_proposal_origin( | ||
community_id: &CommunityIdOf<T>, | ||
origin: PalletsOriginOf<T>, | ||
) -> DispatchResult { | ||
let community_account_id = Self::get_community_account_id(community_id); | ||
|
||
if let Some(o) = origin.clone().checked_into::<frame_system::Origin<T>>() { | ||
match o { | ||
frame_system::Origin::<T>::Signed(account) if account == community_account_id => Ok(()), | ||
_ => Err(Error::<T>::InvalidProposalOrigin.into()), | ||
} | ||
} else { | ||
match origin.checked_into::<pallet::Origin<T>>() { | ||
Some(_) => Ok(()), | ||
None => Err(Error::<T>::InvalidProposalOrigin.into()), | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.