#![recursion_limit = "256"]
#![allow(clippy::string_lit_as_bytes)]
#![allow(clippy::redundant_closure_call)]
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod tests;
use codec::Codec;
use frame_support::{
decl_error,
decl_event,
decl_module,
decl_storage,
ensure,
Parameter,
};
use frame_system::{
self as system,
ensure_signed,
};
use sp_runtime::{
traits::{
AtLeast32BitUnsigned,
CheckedSub,
MaybeSerializeDeserialize,
Member,
Zero,
},
DispatchError,
DispatchResult,
Permill,
};
use sp_std::{
fmt::Debug,
prelude::*,
};
use util::{
traits::{
AccessGenesis,
Apply,
ApplyVote,
CheckVoteStatus,
GenerateUniqueID,
GetGroup,
GetVoteOutcome,
IDIsAvailable,
MintableSignal,
OpenThresholdVote,
OpenVote,
OrganizationSupervisorPermissions,
ShareInformation,
UpdateVoteTopic,
VoteOnProposal,
VoteVector,
},
vote::{
Vote,
VoteOutcome,
VoteState,
VoterView,
},
};
pub trait Trait: frame_system::Trait + org::Trait {
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
type VoteId: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ PartialOrd
+ PartialEq
+ Zero;
type Signal: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ PartialOrd
+ CheckedSub
+ Zero
+ From<Self::Shares>;
}
decl_event!(
pub enum Event<T>
where
<T as frame_system::Trait>::AccountId,
<T as org::Trait>::OrgId,
<T as Trait>::VoteId,
{
NewVoteStarted(AccountId, OrgId, VoteId),
Voted(VoteId, AccountId, VoterView),
}
);
decl_error! {
pub enum Error for Module<T: Trait> {
VotePastExpirationTimeSoVotesNotAccepted,
SignalNotMintedForVoter,
NotAuthorizedToCreateVoteForOrganization,
NoVoteStateForOutcomeQuery,
NoVoteStateForVoteRequest,
CannotMintSignalBecauseGroupMembershipDNE,
CannotMintSignalBecauseMembershipShapeDNE,
OldVoteDirectionEqualsNewVoteDirectionSoNoChange,
CannotUpdateVoteTopicIfVoteStateDNE,
VoteChangeNotSupported,
}
}
decl_storage! {
trait Store for Module<T: Trait> as Vote {
VoteIdCounter get(fn vote_id_counter): T::VoteId;
pub OpenVoteCounter get(fn open_vote_counter): u32;
pub VoteStates get(fn vote_states): map
hasher(opaque_blake2_256) T::VoteId => Option<VoteState<T::Signal, T::BlockNumber, T::IpfsReference>>;
pub TotalSignalIssuance get(fn total_signal_issuance): map
hasher(opaque_blake2_256) T::VoteId => Option<T::Signal>;
pub VoteLogger get(fn vote_logger): double_map
hasher(opaque_blake2_256) T::VoteId,
hasher(opaque_blake2_256) T::AccountId => Option<Vote<T::Signal, T::IpfsReference>>;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
#[weight = 0]
pub fn create_signal_threshold_vote(
origin,
topic: Option<T::IpfsReference>,
organization: T::OrgId,
support_requirement: T::Signal,
rejection_requirement: Option<T::Signal>,
duration: Option<T::BlockNumber>,
) -> DispatchResult {
let vote_creator = ensure_signed(origin)?;
let authentication: bool = <org::Module<T>>::is_organization_supervisor(organization, &vote_creator);
ensure!(authentication, Error::<T>::NotAuthorizedToCreateVoteForOrganization);
let new_vote_id = Self::open_vote(topic, organization, support_requirement, rejection_requirement, duration)?;
Self::deposit_event(RawEvent::NewVoteStarted(vote_creator, organization, new_vote_id));
Ok(())
}
#[weight = 0]
pub fn create_percent_threshold_vote(
origin,
topic: Option<T::IpfsReference>,
organization: T::OrgId,
support_threshold: Permill,
rejection_threshold: Option<Permill>,
duration: Option<T::BlockNumber>,
) -> DispatchResult {
let vote_creator = ensure_signed(origin)?;
let authentication: bool = <org::Module<T>>::is_organization_supervisor(organization, &vote_creator);
ensure!(authentication, Error::<T>::NotAuthorizedToCreateVoteForOrganization);
let new_vote_id = Self::open_threshold_vote(topic, organization, support_threshold, rejection_threshold, duration)?;
Self::deposit_event(RawEvent::NewVoteStarted(vote_creator, organization, new_vote_id));
Ok(())
}
#[weight = 0]
pub fn create_unanimous_consent_vote(
origin,
topic: Option<T::IpfsReference>,
organization: T::OrgId,
duration: Option<T::BlockNumber>,
) -> DispatchResult {
let vote_creator = ensure_signed(origin)?;
let authentication: bool = <org::Module<T>>::is_organization_supervisor(organization, &vote_creator);
ensure!(authentication, Error::<T>::NotAuthorizedToCreateVoteForOrganization);
let new_vote_id = Self::open_unanimous_consent(topic, organization, duration)?;
Self::deposit_event(RawEvent::NewVoteStarted(vote_creator, organization, new_vote_id));
Ok(())
}
#[weight = 0]
pub fn submit_vote(
origin,
vote_id: T::VoteId,
direction: VoterView,
justification: Option<T::IpfsReference>,
) -> DispatchResult {
let voter = ensure_signed(origin)?;
Self::vote_on_proposal(vote_id, voter.clone(), direction, justification)?;
Self::deposit_event(RawEvent::Voted(vote_id, voter, direction));
Ok(())
}
}
}
impl<T: Trait> IDIsAvailable<T::VoteId> for Module<T> {
fn id_is_available(id: T::VoteId) -> bool {
<VoteStates<T>>::get(id).is_none()
}
}
impl<T: Trait> GenerateUniqueID<T::VoteId> for Module<T> {
fn generate_unique_id() -> T::VoteId {
let mut id_counter = <VoteIdCounter<T>>::get() + 1u32.into();
while <VoteStates<T>>::get(id_counter).is_some() {
id_counter += 1u32.into();
}
<VoteIdCounter<T>>::put(id_counter);
id_counter
}
}
impl<T: Trait> GetVoteOutcome<T::VoteId> for Module<T> {
type Outcome = VoteOutcome;
fn get_vote_outcome(
vote_id: T::VoteId,
) -> Result<Self::Outcome, DispatchError> {
let vote_state = <VoteStates<T>>::get(vote_id)
.ok_or(Error::<T>::NoVoteStateForOutcomeQuery)?;
Ok(vote_state.outcome())
}
}
impl<T: Trait> OpenVote<T::OrgId, T::Signal, T::BlockNumber, T::IpfsReference>
for Module<T>
{
type VoteIdentifier = T::VoteId;
fn open_vote(
topic: Option<T::IpfsReference>,
organization: T::OrgId,
passage_threshold: T::Signal,
rejection_threshold: Option<T::Signal>,
duration: Option<T::BlockNumber>,
) -> Result<Self::VoteIdentifier, DispatchError> {
let now = system::Module::<T>::block_number();
let ends: Option<T::BlockNumber> = if let Some(time_to_add) = duration {
Some(now + time_to_add)
} else {
None
};
let new_vote_id = Self::generate_unique_id();
let total_possible_turnout =
Self::batch_mint_signal(new_vote_id, organization)?;
let new_vote_state = VoteState::new(
topic,
total_possible_turnout,
passage_threshold,
rejection_threshold,
now,
ends,
);
<VoteStates<T>>::insert(new_vote_id, new_vote_state);
let new_vote_count = <OpenVoteCounter>::get() + 1u32;
<OpenVoteCounter>::put(new_vote_count);
Ok(new_vote_id)
}
fn open_unanimous_consent(
topic: Option<T::IpfsReference>,
organization: T::OrgId,
duration: Option<T::BlockNumber>,
) -> Result<Self::VoteIdentifier, DispatchError> {
let now = system::Module::<T>::block_number();
let ends: Option<T::BlockNumber> = if let Some(time_to_add) = duration {
Some(now + time_to_add)
} else {
None
};
let new_vote_id = Self::generate_unique_id();
let total_possible_turnout =
Self::batch_mint_equal_signal(new_vote_id, organization)?;
let new_vote_state = VoteState::new_unanimous_consent(
topic,
total_possible_turnout,
now,
ends,
);
<VoteStates<T>>::insert(new_vote_id, new_vote_state);
let new_vote_count = <OpenVoteCounter>::get() + 1u32;
<OpenVoteCounter>::put(new_vote_count);
Ok(new_vote_id)
}
}
impl<T: Trait> UpdateVoteTopic<T::VoteId, T::IpfsReference> for Module<T> {
fn update_vote_topic(
vote_id: T::VoteId,
new_topic: T::IpfsReference,
clear_previous_vote_state: bool,
) -> DispatchResult {
let old_vote_state = <VoteStates<T>>::get(vote_id)
.ok_or(Error::<T>::CannotUpdateVoteTopicIfVoteStateDNE)?;
let new_vote_state = if clear_previous_vote_state {
old_vote_state.update_topic_and_clear_state(new_topic)
} else {
old_vote_state.update_topic_without_clearing_state(new_topic)
};
<VoteStates<T>>::insert(vote_id, new_vote_state);
Ok(())
}
}
impl<T: Trait>
MintableSignal<
T::AccountId,
T::OrgId,
T::Signal,
T::BlockNumber,
T::VoteId,
T::IpfsReference,
> for Module<T>
{
fn mint_custom_signal_for_account(
vote_id: T::VoteId,
who: &T::AccountId,
signal: T::Signal,
) {
let new_vote = Vote::new(signal, VoterView::NoVote, None);
<VoteLogger<T>>::insert(vote_id, who, new_vote);
}
fn batch_mint_equal_signal(
vote_id: T::VoteId,
organization: T::OrgId,
) -> Result<T::Signal, DispatchError> {
let new_vote_group = <org::Module<T>>::get_group(organization)
.ok_or(Error::<T>::CannotMintSignalBecauseGroupMembershipDNE)?;
let total_minted: T::Signal = (new_vote_group.0.len() as u32).into();
new_vote_group.0.into_iter().for_each(|who| {
let minted_signal: T::Signal = 1u32.into();
let new_vote = Vote::new(minted_signal, VoterView::NoVote, None);
<VoteLogger<T>>::insert(vote_id, who, new_vote);
});
<TotalSignalIssuance<T>>::insert(vote_id, total_minted);
Ok(total_minted)
}
fn batch_mint_signal(
vote_id: T::VoteId,
organization: T::OrgId,
) -> Result<T::Signal, DispatchError> {
let new_vote_group =
<org::Module<T>>::get_membership_with_shape(organization)
.ok_or(Error::<T>::CannotMintSignalBecauseMembershipShapeDNE)?;
let total_minted: T::Signal = new_vote_group.total().into();
new_vote_group.account_ownership().into_iter().for_each(
|(who, shares)| {
let minted_signal: T::Signal = shares.into();
let new_vote =
Vote::new(minted_signal, VoterView::NoVote, None);
<VoteLogger<T>>::insert(vote_id, who, new_vote);
},
);
<TotalSignalIssuance<T>>::insert(vote_id, total_minted);
Ok(total_minted)
}
}
impl<T: Trait> ApplyVote<T::IpfsReference> for Module<T> {
type Signal = T::Signal;
type Direction = VoterView;
type Vote = Vote<T::Signal, T::IpfsReference>;
type State = VoteState<T::Signal, T::BlockNumber, T::IpfsReference>;
fn apply_vote(
state: Self::State,
vote_magnitude: T::Signal,
old_vote_view: Self::Direction,
new_vote_view: Self::Direction,
) -> Option<Self::State> {
state.apply(vote_magnitude, old_vote_view, new_vote_view)
}
}
impl<T: Trait> CheckVoteStatus<T::IpfsReference, T::VoteId> for Module<T> {
fn check_vote_expired(state: &Self::State) -> bool {
let now = system::Module::<T>::block_number();
if let Some(n) = state.expires() {
return n < now
}
false
}
}
impl<T: Trait>
VoteOnProposal<
T::AccountId,
T::OrgId,
T::Signal,
T::BlockNumber,
T::VoteId,
T::IpfsReference,
> for Module<T>
{
fn vote_on_proposal(
vote_id: T::VoteId,
voter: T::AccountId,
direction: Self::Direction,
justification: Option<T::IpfsReference>,
) -> DispatchResult {
let vote_state = <VoteStates<T>>::get(vote_id)
.ok_or(Error::<T>::NoVoteStateForVoteRequest)?;
ensure!(
!Self::check_vote_expired(&vote_state),
Error::<T>::VotePastExpirationTimeSoVotesNotAccepted
);
let old_vote = <VoteLogger<T>>::get(vote_id, voter.clone())
.ok_or(Error::<T>::SignalNotMintedForVoter)?;
let new_vote = old_vote.set_new_view(direction, justification).ok_or(
Error::<T>::OldVoteDirectionEqualsNewVoteDirectionSoNoChange,
)?;
let new_state = Self::apply_vote(
vote_state,
old_vote.magnitude(),
old_vote.direction(),
direction,
)
.ok_or(Error::<T>::VoteChangeNotSupported)?;
<VoteLogger<T>>::insert(vote_id, voter, new_vote);
<VoteStates<T>>::insert(vote_id, new_state);
Ok(())
}
}
impl<T: Trait>
OpenThresholdVote<
T::OrgId,
T::Signal,
T::BlockNumber,
T::IpfsReference,
Permill,
> for Module<T>
{
const THIRTY_FOUR_PERCENT: Permill = Permill::from_percent(34);
const FIFTY_ONE_PERCENT: Permill = Permill::from_percent(51);
const SIXTY_SEVEN_PERCENT: Permill = Permill::from_percent(67);
const SEVENTY_SIX_PERCENT: Permill = Permill::from_percent(76);
const NINETY_ONE_PERCENT: Permill = Permill::from_percent(91);
fn open_threshold_vote(
topic: Option<T::IpfsReference>,
organization: T::OrgId,
passage_threshold_pct: Permill,
rejection_threshold_pct: Option<Permill>,
duration: Option<T::BlockNumber>,
) -> Result<Self::VoteIdentifier, DispatchError> {
let now = system::Module::<T>::block_number();
let ends: Option<T::BlockNumber> = if let Some(time_to_add) = duration {
Some(now + time_to_add)
} else {
None
};
let new_vote_id = Self::generate_unique_id();
let total_possible_turnout =
Self::batch_mint_signal(new_vote_id, organization)?;
let passage_threshold =
passage_threshold_pct.mul_ceil(total_possible_turnout);
let rejection_threshold = if let Some(r_t) = rejection_threshold_pct {
Some(r_t * total_possible_turnout)
} else {
None
};
let new_vote_state = VoteState::new(
topic,
total_possible_turnout,
passage_threshold,
rejection_threshold,
now,
ends,
);
<VoteStates<T>>::insert(new_vote_id, new_vote_state);
let new_vote_count = <OpenVoteCounter>::get() + 1u32;
<OpenVoteCounter>::put(new_vote_count);
Ok(new_vote_id)
}
fn open_34_pct_passage_threshold_vote(
topic: Option<T::IpfsReference>,
organization: T::OrgId,
duration: Option<T::BlockNumber>,
) -> Result<Self::VoteIdentifier, DispatchError> {
let now = system::Module::<T>::block_number();
let ends: Option<T::BlockNumber> = if let Some(time_to_add) = duration {
Some(now + time_to_add)
} else {
None
};
let new_vote_id = Self::generate_unique_id();
let total_possible_turnout =
Self::batch_mint_signal(new_vote_id, organization)?;
let passage_threshold =
Self::THIRTY_FOUR_PERCENT.mul_ceil(total_possible_turnout);
let new_vote_state = VoteState::new(
topic,
total_possible_turnout,
passage_threshold,
None,
now,
ends,
);
<VoteStates<T>>::insert(new_vote_id, new_vote_state);
let new_vote_count = <OpenVoteCounter>::get() + 1u32;
<OpenVoteCounter>::put(new_vote_count);
Ok(new_vote_id)
}
fn open_51_pct_passage_threshold_vote(
topic: Option<T::IpfsReference>,
organization: T::OrgId,
duration: Option<T::BlockNumber>,
) -> Result<Self::VoteIdentifier, DispatchError> {
let now = system::Module::<T>::block_number();
let ends: Option<T::BlockNumber> = if let Some(time_to_add) = duration {
Some(now + time_to_add)
} else {
None
};
let new_vote_id = Self::generate_unique_id();
let total_possible_turnout =
Self::batch_mint_signal(new_vote_id, organization)?;
let passage_threshold =
Self::FIFTY_ONE_PERCENT.mul_ceil(total_possible_turnout);
let new_vote_state = VoteState::new(
topic,
total_possible_turnout,
passage_threshold,
None,
now,
ends,
);
<VoteStates<T>>::insert(new_vote_id, new_vote_state);
let new_vote_count = <OpenVoteCounter>::get() + 1u32;
<OpenVoteCounter>::put(new_vote_count);
Ok(new_vote_id)
}
fn open_67_pct_passage_threshold_vote(
topic: Option<T::IpfsReference>,
organization: T::OrgId,
duration: Option<T::BlockNumber>,
) -> Result<Self::VoteIdentifier, DispatchError> {
let now = system::Module::<T>::block_number();
let ends: Option<T::BlockNumber> = if let Some(time_to_add) = duration {
Some(now + time_to_add)
} else {
None
};
let new_vote_id = Self::generate_unique_id();
let total_possible_turnout =
Self::batch_mint_signal(new_vote_id, organization)?;
let passage_threshold =
Self::SIXTY_SEVEN_PERCENT.mul_ceil(total_possible_turnout);
let new_vote_state = VoteState::new(
topic,
total_possible_turnout,
passage_threshold,
None,
now,
ends,
);
<VoteStates<T>>::insert(new_vote_id, new_vote_state);
let new_vote_count = <OpenVoteCounter>::get() + 1u32;
<OpenVoteCounter>::put(new_vote_count);
Ok(new_vote_id)
}
fn open_76_pct_passage_threshold_vote(
topic: Option<T::IpfsReference>,
organization: T::OrgId,
duration: Option<T::BlockNumber>,
) -> Result<Self::VoteIdentifier, DispatchError> {
let now = system::Module::<T>::block_number();
let ends: Option<T::BlockNumber> = if let Some(time_to_add) = duration {
Some(now + time_to_add)
} else {
None
};
let new_vote_id = Self::generate_unique_id();
let total_possible_turnout =
Self::batch_mint_signal(new_vote_id, organization)?;
let passage_threshold =
Self::SEVENTY_SIX_PERCENT.mul_ceil(total_possible_turnout);
let new_vote_state = VoteState::new(
topic,
total_possible_turnout,
passage_threshold,
None,
now,
ends,
);
<VoteStates<T>>::insert(new_vote_id, new_vote_state);
let new_vote_count = <OpenVoteCounter>::get() + 1u32;
<OpenVoteCounter>::put(new_vote_count);
Ok(new_vote_id)
}
fn open_91_pct_passage_threshold_vote(
topic: Option<T::IpfsReference>,
organization: T::OrgId,
duration: Option<T::BlockNumber>,
) -> Result<Self::VoteIdentifier, DispatchError> {
let now = system::Module::<T>::block_number();
let ends: Option<T::BlockNumber> = if let Some(time_to_add) = duration {
Some(now + time_to_add)
} else {
None
};
let new_vote_id = Self::generate_unique_id();
let total_possible_turnout =
Self::batch_mint_signal(new_vote_id, organization)?;
let passage_threshold =
Self::NINETY_ONE_PERCENT.mul_ceil(total_possible_turnout);
let new_vote_state = VoteState::new(
topic,
total_possible_turnout,
passage_threshold,
None,
now,
ends,
);
<VoteStates<T>>::insert(new_vote_id, new_vote_state);
let new_vote_count = <OpenVoteCounter>::get() + 1u32;
<OpenVoteCounter>::put(new_vote_count);
Ok(new_vote_id)
}
}