Skip to content

Commit

Permalink
Merge pull request #6 from logion-network/feature/vote
Browse files Browse the repository at this point in the history
Add vote extrinsic
  • Loading branch information
benoitdevos authored Jan 17, 2023
2 parents d0b142e + d28ed68 commit f806377
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 2 deletions.
72 changes: 71 additions & 1 deletion pallet-logion-vote/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub enum BallotStatus {
}

pub type VoteId = u64;
pub type VoteCompleted = bool;
pub type VoteApproved = bool;

#[frame_support::pallet]
pub mod pallet {
Expand All @@ -40,8 +42,10 @@ pub mod pallet {
dispatch::DispatchResultWithPostInfo,
pallet_prelude::*,
};
use frame_system::ensure_signed;
use frame_system::pallet_prelude::OriginFor;
use logion_shared::{IsLegalOfficer, LocValidity};
use crate::BallotStatus::{NotVoted, VotedNo, VotedYes};
use super::*;

#[pallet::config]
Expand Down Expand Up @@ -78,13 +82,21 @@ pub mod pallet {
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
/// Issued upon new Vote creation. [voteId, legalOfficers]
VoteCreated(VoteId, Vec<T::AccountId>)
VoteCreated(VoteId, Vec<T::AccountId>),
/// Issued upon new Vote creation. [voteId, ballot, completed, approved]
VoteUpdated(VoteId, Ballot<T::AccountId>, VoteCompleted, VoteApproved),
}

#[pallet::error]
pub enum Error<T> {
/// Given LOC is not valid (not found, or not closed or void) or does not belong to vote requester.
InvalidLoc,
/// Given vote does not exist.
VoteNotFound,
/// User is not allowed to vote on given vote.
NotAllowed,
/// User has already voted on given vote.
AlreadyVoted,
}

#[pallet::call]
Expand Down Expand Up @@ -115,5 +127,63 @@ pub mod pallet {
Err(Error::<T>::InvalidLoc)?
}
}

/// Vote.
#[pallet::weight(0)]
pub fn vote(
origin: OriginFor<T>,
#[pallet::compact] vote_id: VoteId,
vote_yes: bool,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;

if !<Votes<T>>::contains_key(&vote_id) {
Err(Error::<T>::VoteNotFound)?
} else {
let vote = <Votes<T>>::get(vote_id).unwrap();
let option_ballot_index = vote.ballots.iter().position(|vote| vote.voter == who);
if option_ballot_index.is_none() {
Err(Error::<T>::NotAllowed)?
} else {
let ballot_index = option_ballot_index.unwrap();
if vote.ballots[ballot_index].status != NotVoted {
Err(Error::<T>::AlreadyVoted)?
}
let status = match vote_yes {
true => VotedYes,
false => VotedNo
};
<Votes<T>>::mutate(vote_id, |vote| {
let mutable_vote = vote.as_mut().unwrap();
mutable_vote.ballots[ballot_index].status = status.clone();
});
let (completed, approved) = Self::is_vote_completed(vote_id);
Self::deposit_event(Event::VoteUpdated(
vote_id,
Ballot { status: status.clone(), voter: who },
completed,
approved)
);
Ok(().into())
}
}
}
}

impl<T: Config> Pallet<T> {
pub fn is_vote_completed(vote_id: VoteId) -> (VoteCompleted, VoteApproved) {
let vote = <Votes<T>>::get(vote_id).unwrap();
let not_voted = vote.ballots.iter().find(|ballot| ballot.status == NotVoted);
match not_voted {
Some(_) => (false, false),
None => {
let voted_no = vote.ballots.iter().find(|ballot| ballot.status == VotedNo);
match voted_no {
Some(_) => (true, false),
None => (true, true)
}
}
}
}
}
}
94 changes: 93 additions & 1 deletion pallet-logion-vote/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ fn it_creates_vote() {
new_test_ext().execute_with(|| {
assert_empty_storage();
assert_ok!(LogionVote::create_vote_for_all_legal_officers(RuntimeOrigin::signed(LEGAL_OFFICER1), LOC_ID));
assert_eq!(LogionVote::last_vote_id(), 1);
let vote_id = LogionVote::last_vote_id();
assert_eq!(vote_id, 1);
assert_eq!(LogionVote::votes(1), Some(
Vote {
loc_id: LOC_ID,
Expand All @@ -21,6 +22,7 @@ fn it_creates_vote() {
]
}));
assert_eq!(LogionVote::votes(2), None);
assert_eq!(LogionVote::is_vote_completed(vote_id), (false, false))
});
}

Expand All @@ -42,6 +44,96 @@ fn it_fails_to_create_vote_when_wrong_loc() {
});
}

#[test]
fn it_votes_yes() {
new_test_ext().execute_with(|| {
assert_empty_storage();
assert_ok!(LogionVote::create_vote_for_all_legal_officers(RuntimeOrigin::signed(LEGAL_OFFICER1), LOC_ID));
let vote_id: u64 = LogionVote::last_vote_id();
assert_ok!(LogionVote::vote(RuntimeOrigin::signed(LEGAL_OFFICER1), vote_id, true));
assert_eq!(LogionVote::votes(vote_id), Some(
Vote {
loc_id: LOC_ID,
ballots: vec![
Ballot { voter: LEGAL_OFFICER1, status: BallotStatus::VotedYes },
Ballot { voter: LEGAL_OFFICER2, status: BallotStatus::NotVoted },
]
}));
assert_eq!(LogionVote::is_vote_completed(vote_id), (false, false))
});
}

#[test]
fn it_votes_yes_and_no() {
new_test_ext().execute_with(|| {
assert_empty_storage();
assert_ok!(LogionVote::create_vote_for_all_legal_officers(RuntimeOrigin::signed(LEGAL_OFFICER1), LOC_ID));
let vote_id: u64 = LogionVote::last_vote_id();
assert_ok!(LogionVote::vote(RuntimeOrigin::signed(LEGAL_OFFICER1), vote_id, true));
assert_ok!(LogionVote::vote(RuntimeOrigin::signed(LEGAL_OFFICER2), vote_id, false));
assert_eq!(LogionVote::votes(vote_id), Some(
Vote {
loc_id: LOC_ID,
ballots: vec![
Ballot { voter: LEGAL_OFFICER1, status: BallotStatus::VotedYes },
Ballot { voter: LEGAL_OFFICER2, status: BallotStatus::VotedNo },
]
}));
assert_eq!(LogionVote::is_vote_completed(vote_id), (true, false))
});
}

#[test]
fn it_votes_yes_and_yes() {
new_test_ext().execute_with(|| {
assert_empty_storage();
assert_ok!(LogionVote::create_vote_for_all_legal_officers(RuntimeOrigin::signed(LEGAL_OFFICER1), LOC_ID));
let vote_id: u64 = LogionVote::last_vote_id();
assert_ok!(LogionVote::vote(RuntimeOrigin::signed(LEGAL_OFFICER1), vote_id, true));
assert_ok!(LogionVote::vote(RuntimeOrigin::signed(LEGAL_OFFICER2), vote_id, true));
assert_eq!(LogionVote::votes(vote_id), Some(
Vote {
loc_id: LOC_ID,
ballots: vec![
Ballot { voter: LEGAL_OFFICER1, status: BallotStatus::VotedYes },
Ballot { voter: LEGAL_OFFICER2, status: BallotStatus::VotedYes },
]
}));
assert_eq!(LogionVote::is_vote_completed(vote_id), (true, true))
});
}

#[test]
fn it_fails_to_vote_wrong_vote_id() {
new_test_ext().execute_with(|| {
assert_empty_storage();
assert_ok!(LogionVote::create_vote_for_all_legal_officers(RuntimeOrigin::signed(LEGAL_OFFICER1), LOC_ID));
let wrong_vote_id: u64 = LogionVote::last_vote_id() + 100;
assert_err!(LogionVote::vote(RuntimeOrigin::signed(LEGAL_OFFICER2), wrong_vote_id, false), Error::<Test>::VoteNotFound);
});
}

#[test]
fn it_fails_to_vote_wrong_voter() {
new_test_ext().execute_with(|| {
assert_empty_storage();
assert_ok!(LogionVote::create_vote_for_all_legal_officers(RuntimeOrigin::signed(LEGAL_OFFICER1), LOC_ID));
let vote_id: u64 = LogionVote::last_vote_id();
assert_err!(LogionVote::vote(RuntimeOrigin::signed(WALLET_USER), vote_id, true), Error::<Test>::NotAllowed);
});
}

#[test]
fn it_fails_to_vote_twice() {
new_test_ext().execute_with(|| {
assert_empty_storage();
assert_ok!(LogionVote::create_vote_for_all_legal_officers(RuntimeOrigin::signed(LEGAL_OFFICER1), LOC_ID));
let vote_id: u64 = LogionVote::last_vote_id();
assert_ok!(LogionVote::vote(RuntimeOrigin::signed(LEGAL_OFFICER2), vote_id, false));
assert_err!(LogionVote::vote(RuntimeOrigin::signed(LEGAL_OFFICER2), vote_id, true), Error::<Test>::AlreadyVoted);
});
}

fn assert_empty_storage() {
assert_eq!(LogionVote::votes(0), None);
assert_eq!(LogionVote::votes(1), None);
Expand Down

0 comments on commit f806377

Please sign in to comment.