Skip to content

Commit

Permalink
feat(pallet-communities): implement voting for DecisionMethod::Rank
Browse files Browse the repository at this point in the history
  • Loading branch information
pandres95 committed Mar 4, 2024
1 parent 46c17f2 commit 03f50ed
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 40 deletions.
58 changes: 45 additions & 13 deletions pallets/communities/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,36 @@ impl<T: Config> Pallet<T> {

pub(crate) fn do_vote(
who: &AccountIdOf<T>,
community_id: &CommunityIdOf<T>,
membership_id: MembershipIdOf<T>,
poll_index: PollIndexOf<T>,
vote: VoteOf<T>,
) -> DispatchResult {
let info = T::MemberMgmt::get_membership(membership_id.clone(), &who).ok_or(Error::<T>::NotAMember)?;
let community_id = CommunityIdOf::<T>::from(membership_id.clone());

if VoteWeight::from(vote.clone()) == 0 {
return Err(TokenError::BelowMinimum.into());
}

T::Polls::try_access_poll(poll_index, |poll_status| {
let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::<T>::NotOngoing)?;
ensure!(community_id == &class, Error::<T>::InvalidTrack);
ensure!(community_id == class, Error::<T>::InvalidTrack);

let decision_method = CommunityDecisionMethod::<T>::get(community_id);

let maybe_vote = Self::community_vote_of(who, poll_index);
if let Some(vote) = maybe_vote {
Self::do_unlock_for_vote(who, &poll_index, &vote)?;
tally.remove_vote(vote.clone().into(), vote.into());

let multiplied_vote = match CommunityDecisionMethod::<T>::get(community_id) {
DecisionMethod::Rank => Into::<u8>::into(info.rank()) as u32,
_ => 1,
};
tally.remove_vote(
vote.clone().into(),
multiplied_vote * Into::<VoteWeight>::into(vote.clone()),
vote.into(),
);
}

let say = match vote.clone() {
Expand Down Expand Up @@ -127,7 +139,16 @@ impl<T: Config> Pallet<T> {
};

Self::do_lock_for_vote(who, &poll_index, &vote)?;
tally.add_vote(say, vote.clone().into());

let multiplied_vote = match CommunityDecisionMethod::<T>::get(community_id) {
DecisionMethod::Rank => Into::<u8>::into(info.rank()) as u32,
_ => 1,
};
tally.add_vote(
say,
multiplied_vote * Into::<VoteWeight>::into(vote.clone()),
vote.clone().into(),
);

Self::deposit_event(Event::<T>::VoteCasted {
who: who.clone(),
Expand All @@ -141,15 +162,26 @@ impl<T: Config> Pallet<T> {

pub(crate) fn do_remove_vote(
who: &AccountIdOf<T>,
community_id: &CommunityIdOf<T>,
membership_id: MembershipIdOf<T>,
poll_index: PollIndexOf<T>,
) -> DispatchResult {
let info = T::MemberMgmt::get_membership(membership_id.clone(), &who).ok_or(Error::<T>::NotAMember)?;
let community_id = CommunityIdOf::<T>::from(membership_id.clone());

T::Polls::try_access_poll(poll_index, |poll_status| {
if let Some((tally, class)) = poll_status.ensure_ongoing() {
ensure!(community_id == &class, Error::<T>::InvalidTrack);
ensure!(community_id == class, Error::<T>::InvalidTrack);
let vote = Self::community_vote_of(who, poll_index).ok_or(Error::<T>::NoVoteCasted)?;

tally.remove_vote(vote.clone().into(), vote.clone().into());
let multiplied_vote = match CommunityDecisionMethod::<T>::get(community_id) {
DecisionMethod::Rank => Into::<u8>::into(info.rank()) as u32,
_ => 1,
};
tally.remove_vote(
vote.clone().into(),
multiplied_vote * Into::<VoteWeight>::into(vote.clone()),
vote.clone().into(),
);

let reason = HoldReason::VoteCasted(poll_index).into();
CommunityVotes::<T>::remove(who, poll_index);
Expand Down Expand Up @@ -199,26 +231,26 @@ impl<T: Config> Pallet<T> {
}

impl<T: Config> Tally<T> {
pub(self) fn add_vote(&mut self, say: bool, weight: VoteWeight) {
pub(self) fn add_vote(&mut self, say: bool, multiplied_weight: VoteWeight, weight: VoteWeight) {
match say {
true => {
self.ayes = self.ayes.saturating_add(weight);
self.ayes = self.ayes.saturating_add(multiplied_weight);
self.bare_ayes = self.bare_ayes.saturating_add(weight);
}
false => {
self.nays = self.nays.saturating_add(weight);
self.nays = self.nays.saturating_add(multiplied_weight);
}
}
}

pub(self) fn remove_vote(&mut self, say: bool, weight: VoteWeight) {
pub(self) fn remove_vote(&mut self, say: bool, multiplied_weight: VoteWeight, weight: VoteWeight) {
match say {
true => {
self.ayes = self.ayes.saturating_sub(weight);
self.ayes = self.ayes.saturating_sub(multiplied_weight);
self.bare_ayes = self.bare_ayes.saturating_sub(weight);
}
false => {
self.nays = self.nays.saturating_sub(weight);
self.nays = self.nays.saturating_sub(multiplied_weight);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pallets/communities/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl<T: Config> VoteTally<VoteWeight, CommunityIdOf<T>> for Tally<T> {
}

fn support(&self, community_id: CommunityIdOf<T>) -> sp_runtime::Perbill {
Perbill::from_rational(self.bare_ayes, Self::max_ayes(community_id))
Perbill::from_rational(self.bare_ayes, Self::max_support(community_id))
}

fn approval(&self, _cid: CommunityIdOf<T>) -> sp_runtime::Perbill {
Expand Down
70 changes: 48 additions & 22 deletions pallets/communities/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ pub mod pallet {
#[pallet::storage]
pub(super) type CommunityMembersCount<T> = StorageMap<_, Blake2_128Concat, CommunityIdOf<T>, u32, ValueQuery>;

/// Stores the sum of members' ranks, managed by promote_member and demote_member
#[pallet::storage]
pub(super) type CommunityRanksSum<T> = StorageMap<_, Blake2_128Concat, CommunityIdOf<T>, u32, ValueQuery>;

/// Stores the decision method for a community
#[pallet::storage]
pub(super) type CommunityDecisionMethod<T> =
Expand Down Expand Up @@ -428,6 +432,13 @@ pub mod pallet {
)?;
CommunityMembersCount::<T>::mutate(community_id, |count| {
*count += 1;
CommunityRanksSum::<T>::mutate(community_id, |sum| {
let rank = T::MemberMgmt::get_membership(membership_id.clone(), &who)
.ok_or(Error::<T>::NotAMember)
.expect("a member has just been inserted; qed")
.rank();
*sum += Into::<u8>::into(rank) as u32;
});
});

Self::deposit_event(Event::MemberAdded { who, membership_id });
Expand Down Expand Up @@ -463,6 +474,11 @@ pub mod pallet {
)?;
CommunityMembersCount::<T>::mutate(community_id, |count| {
*count -= 1;

CommunityRanksSum::<T>::mutate(community_id, |sum| {
let rank = info.rank();
*sum -= Into::<u8>::into(rank) as u32;
});
});

Self::deposit_event(Event::MemberRemoved { who, membership_id });
Expand All @@ -476,16 +492,24 @@ pub mod pallet {
who: AccountIdLookupOf<T>,
membership_id: MembershipIdOf<T>,
) -> DispatchResult {
let _community_id = T::MemberMgmtOrigin::ensure_origin(origin)?;
let community_id = T::MemberMgmtOrigin::ensure_origin(origin)?;
let who = T::Lookup::lookup(who)?;

let mut m = T::MemberMgmt::get_membership(membership_id.clone(), &who).ok_or(Error::<T>::NotAMember)?;
let rank = m.rank();
m.set_rank(rank.promote_by(1.try_into().expect("can promote by 1")));
T::MemberMgmt::update(membership_id.clone(), m, None)?;
CommunityRanksSum::<T>::try_mutate(community_id, |sum| {
let mut m = T::MemberMgmt::get_membership(membership_id.clone(), &who).ok_or(Error::<T>::NotAMember)?;
let rank = m.rank();

Self::deposit_event(Event::MembershipRankUpdated { membership_id, rank });
Ok(())
*sum = sum.saturating_sub(Into::<u8>::into(rank) as u32);

let rank = rank.promote_by(1.try_into().expect("can demote by 1"));
m.set_rank(rank);
T::MemberMgmt::update(membership_id.clone(), m, None)?;

*sum += Into::<u8>::into(rank) as u32;

Self::deposit_event(Event::MembershipRankUpdated { membership_id, rank });
Ok(())
})
}

/// Decreases the rank of a member in the community
Expand All @@ -495,16 +519,24 @@ pub mod pallet {
who: AccountIdLookupOf<T>,
membership_id: MembershipIdOf<T>,
) -> DispatchResult {
let _community_id = T::MemberMgmtOrigin::ensure_origin(origin)?;
let community_id = T::MemberMgmtOrigin::ensure_origin(origin)?;
let who = T::Lookup::lookup(who)?;

let mut m = T::MemberMgmt::get_membership(membership_id.clone(), &who).ok_or(Error::<T>::NotAMember)?;
let rank = m.rank();
m.set_rank(rank.demote_by(1.try_into().expect("can promote by 1")));
T::MemberMgmt::update(membership_id.clone(), m, None)?;
CommunityRanksSum::<T>::try_mutate(community_id, |sum| {
let mut m = T::MemberMgmt::get_membership(membership_id.clone(), &who).ok_or(Error::<T>::NotAMember)?;
let rank = m.rank();

Self::deposit_event(Event::MembershipRankUpdated { membership_id, rank });
Ok(())
*sum = sum.saturating_sub(Into::<u8>::into(rank) as u32);

let rank = rank.demote_by(1.try_into().expect("can demote by 1"));
m.set_rank(rank);
T::MemberMgmt::update(membership_id.clone(), m, None)?;

*sum += Into::<u8>::into(rank) as u32;

Self::deposit_event(Event::MembershipRankUpdated { membership_id, rank });
Ok(())
})
}

// === Governance ===
Expand Down Expand Up @@ -532,10 +564,7 @@ pub mod pallet {
vote: VoteOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let _info = T::MemberMgmt::get_membership(membership_id.clone(), &who).ok_or(Error::<T>::NotAMember)?;
let community_id = CommunityIdOf::<T>::from(membership_id);

Self::do_vote(&who, &community_id, poll_index, vote)
Self::do_vote(&who, membership_id, poll_index, vote)
}

///
Expand All @@ -546,10 +575,7 @@ pub mod pallet {
#[pallet::compact] poll_index: PollIndexOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let _info = T::MemberMgmt::get_membership(membership_id.clone(), &who).ok_or(Error::<T>::NotAMember)?;
let community_id = CommunityIdOf::<T>::from(membership_id);

Self::do_remove_vote(&who, &community_id, poll_index)
Self::do_remove_vote(&who, membership_id, poll_index)
}

///
Expand Down
128 changes: 127 additions & 1 deletion pallets/communities/src/tests/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,133 @@ mod vote {
}

mod rank {
// TODO: Implement rank-based voting first
use frame_support::traits::Polling;

use super::*;

fn new_test_ext() -> sp_io::TestExternalities {
let mut ext = super::new_test_ext();

ext.execute_with(|| {
assert_ok!(Communities::promote_member(
Into::<RuntimeOrigin>::into(*OriginForCommunityD::get()),
ALICE,
MembershipId(COMMUNITY_D, 1)
));
assert_ok!(Communities::promote_member(
Into::<RuntimeOrigin>::into(*OriginForCommunityD::get()),
BOB,
MembershipId(COMMUNITY_D, 2)
));
assert_ok!(Communities::promote_member(
Into::<RuntimeOrigin>::into(*OriginForCommunityD::get()),
CHARLIE,
MembershipId(COMMUNITY_D, 3)
));

assert_ok!(Referenda::submit(
RuntimeOrigin::signed(CHARLIE),
OriginForCommunityD::get(),
ProposalCallPromoteCharlie::get(),
frame_support::traits::schedule::DispatchTime::After(1),
));

System::assert_has_event(
pallet_referenda::Event::<Test>::Submitted {
index: 3,
proposal: ProposalCallPromoteCharlie::get(),
track: COMMUNITY_D,
}
.into(),
);

assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(CHARLIE), 3));

tick_block();
});

ext
}

#[test]
fn it_works() {
new_test_ext().execute_with(|| {
assert_ok!(Communities::vote(
RuntimeOrigin::signed(ALICE),
MembershipId(COMMUNITY_D, 1),
3,
Vote::Standard(true)
));

tick_block();

assert_ok!(Communities::vote(
RuntimeOrigin::signed(BOB),
MembershipId(COMMUNITY_D, 2),
3,
Vote::Standard(false)
));

tick_block();

assert_ok!(Communities::vote(
RuntimeOrigin::signed(CHARLIE),
MembershipId(COMMUNITY_D, 3),
3,
Vote::Standard(true)
));

tick_blocks(3);

System::assert_has_event(pallet_referenda::Event::<Test>::ConfirmStarted { index: 3 }.into());
});
}

#[test]
fn it_works_with_different_ranks() {
new_test_ext().execute_with(|| {
assert_ok!(Communities::promote_member(
Into::<RuntimeOrigin>::into(*OriginForCommunityD::get()),
ALICE,
MembershipId(COMMUNITY_D, 1)
));

assert_ok!(Communities::vote(
RuntimeOrigin::signed(ALICE),
MembershipId(COMMUNITY_D, 1),
3,
Vote::Standard(false)
));

tick_block();

assert_ok!(Communities::vote(
RuntimeOrigin::signed(BOB),
MembershipId(COMMUNITY_D, 2),
3,
Vote::Standard(true)
));

tick_block();

assert_ok!(Communities::vote(
RuntimeOrigin::signed(CHARLIE),
MembershipId(COMMUNITY_D, 3),
3,
Vote::Standard(true)
));

assert_eq!(
Referenda::as_ongoing(3).expect("the poll was initiated; qed").0,
Tally {
ayes: 2,
nays: 2,
bare_ayes: 2,
..Default::default()
}
)
});
}
}
}

Expand Down
Loading

0 comments on commit 03f50ed

Please sign in to comment.