Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Communities] Governance V0: Enqueue and Execute calls #313

2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pallets/communities/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkado

pallet-assets = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
pallet-preimage = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
pallet-scheduler = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }

[features]
default = ["std"]
Expand Down
9 changes: 5 additions & 4 deletions pallets/communities/src/functions/getters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ impl<T: Config> Pallet<T> {
T::PalletId::get().into_sub_account_truncating(community_id)
}

pub(crate) fn get_community_admin(community_id: &CommunityIdOf<T>) -> Result<AccountIdOf<T>, DispatchError> {
let community = Self::community(community_id).ok_or(Error::<T>::CommunityDoesNotExist)?;

Ok(community.admin)
pub(crate) fn get_community_admin(community_id: &CommunityIdOf<T>) -> Option<AccountIdOf<T>> {
match GovernanceStrategy::<T>::get(community_id) {
Some(CommunityGovernanceStrategy::AdminBased(admin)) => Some(admin),
_ => None,
}
}
}
4 changes: 4 additions & 0 deletions pallets/communities/src/functions/governance/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub(self) use super::*;

mod poll;
mod proposals;
136 changes: 136 additions & 0 deletions pallets/communities/src/functions/governance/poll.rs
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)
}
}
}
}
}
58 changes: 58 additions & 0 deletions pallets/communities/src/functions/governance/proposals.rs
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)
})
}
}
32 changes: 18 additions & 14 deletions pallets/communities/src/functions/membership.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,35 @@ impl<T: Config> Pallet<T> {
pub(crate) fn ensure_origin_member(
origin: OriginFor<T>,
community_id: &CommunityIdOf<T>,
) -> Result<(), DispatchError> {
) -> Result<AccountIdOf<T>, DispatchError> {
let caller = ensure_signed(origin)?;

if Self::membership(community_id, caller).is_none() {
return Err(DispatchError::BadOrigin);
}
Self::membership(community_id, &caller)
.ok_or(DispatchError::BadOrigin)
.map(|_| caller)
}

Ok(())
#[allow(dead_code)]
pub(crate) fn ensure_member(
community_id: &CommunityIdOf<T>,
who: &AccountIdOf<T>,
) -> Result<MembershipPassportOf<T>, DispatchError> {
Self::membership(community_id, who).ok_or(Error::<T>::NotAMember.into())
}

pub(crate) fn ensure_origin_privileged(
origin: OriginFor<T>,
community_id: &CommunityIdOf<T>,
) -> Result<(), DispatchError> {
) -> Result<Option<AccountIdOf<T>>, DispatchError> {
if let Some(caller) = ensure_signed_or_root(origin)? {
if caller != Self::get_community_admin(community_id)? {
return Err(DispatchError::BadOrigin);
if let Some(admin) = Self::get_community_admin(community_id) && admin == caller {
return Ok(Some(admin))
} else {
return Err(DispatchError::BadOrigin)
}
}

Ok(())
Ok(None)
}

pub(crate) fn do_insert_member(community_id: &CommunityIdOf<T>, who: &AccountIdOf<T>) -> DispatchResult {
Expand All @@ -50,11 +58,7 @@ impl<T: Config> Pallet<T> {
return Err(Error::<T>::NotAMember.into());
}

let Some(community_info) = Self::community(community_id) else {
return Err(Error::<T>::CommunityDoesNotExist.into());
};

if community_info.admin == *who {
if let Some(community_admin) = Self::get_community_admin(community_id) && community_admin == *who {
return Err(Error::<T>::CannotRemoveAdmin.into());
}

Expand Down
3 changes: 3 additions & 0 deletions pallets/communities/src/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ pub(self) use frame_support::traits::tokens::{
fungibles::Mutate as MutateFuns,
};
pub(self) use frame_system::pallet_prelude::*;
pub(self) use sp_runtime::Saturating;
pub(self) use types::*;

mod challenges;
mod getters;
mod governance;
mod membership;
mod origin;
mod registry;
mod treasury;
24 changes: 24 additions & 0 deletions pallets/communities/src/functions/origin.rs
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()),
}
}
}
}
5 changes: 2 additions & 3 deletions pallets/communities/src/functions/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ impl<T: Config> Pallet<T> {
}

Info::<T>::insert(
community_id.clone(),
community_id,
CommunityInfo {
admin: who.clone(),
state: Default::default(),
sufficient_asset_id: None,
},
);
GovernanceStrategy::<T>::insert(community_id, CommunityGovernanceStrategy::AdminBased(who.clone()));

Self::do_insert_member(community_id, who)?;

Expand Down
Loading