// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! The vote datatype. use crate::{Conviction, Delegations}; use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, Input, MaxEncodedLen, Output}; use pezframe_support::{pezpallet_prelude::Get, BoundedVec}; use scale_info::TypeInfo; use pezsp_runtime::{ traits::{Saturating, Zero}, RuntimeDebug, }; /// A number of lock periods, plus a vote, one way or the other. #[derive( DecodeWithMemTracking, Copy, Clone, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen, )] pub struct Vote { pub aye: bool, pub conviction: Conviction, } impl Encode for Vote { fn encode_to(&self, output: &mut T) { output.push_byte(u8::from(self.conviction) | if self.aye { 0b1000_0000 } else { 0 }); } } impl EncodeLike for Vote {} impl Decode for Vote { fn decode(input: &mut I) -> Result { let b = input.read_byte()?; Ok(Vote { aye: (b & 0b1000_0000) == 0b1000_0000, conviction: Conviction::try_from(b & 0b0111_1111) .map_err(|_| codec::Error::from("Invalid conviction"))?, }) } } impl TypeInfo for Vote { type Identity = Self; fn type_info() -> scale_info::Type { scale_info::Type::builder() .path(scale_info::Path::new("Vote", module_path!())) .composite( scale_info::build::Fields::unnamed() .field(|f| f.ty::().docs(&["Raw vote byte, encodes aye + conviction"])), ) } } /// A vote for a referendum of a particular account. #[derive( Encode, Decode, DecodeWithMemTracking, Copy, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, )] pub enum AccountVote { /// A standard vote, one-way (approve or reject) with a given amount of conviction. Standard { vote: Vote, balance: Balance }, /// A split vote with balances given for both ways, and with no conviction, useful for /// teyrchains when voting. Split { aye: Balance, nay: Balance }, /// A split vote with balances given for both ways as well as abstentions, and with no /// conviction, useful for teyrchains when voting, other off-chain aggregate accounts and /// individuals who wish to abstain. SplitAbstain { aye: Balance, nay: Balance, abstain: Balance }, } /// Present the conditions under which an account's Funds are locked after a voting action. #[derive(Copy, Clone, Eq, PartialEq, RuntimeDebug)] pub enum LockedIf { /// Lock the funds if the outcome of the referendum matches the voting behavior of the user. /// /// `true` means they voted `aye` and `false` means `nay`. Status(bool), /// Always lock the funds. Always, } impl AccountVote { /// Returns `Some` of the lock periods that the account is locked for, assuming that the /// referendum passed if `approved` is `true`. pub fn locked_if(self, approved: LockedIf) -> Option<(u32, Balance)> { // winning side: can only be removed after the lock period ends. match (self, approved) { // If the vote has no conviction, always return None (AccountVote::Standard { vote: Vote { conviction: Conviction::None, .. }, .. }, _) => None, // For Standard votes, check the approval condition (AccountVote::Standard { vote, balance }, LockedIf::Status(is_approved)) if vote.aye == is_approved => Some((vote.conviction.lock_periods(), balance)), // If LockedIf::Always, return the lock period regardless of the vote (AccountVote::Standard { vote, balance }, LockedIf::Always) => Some((vote.conviction.lock_periods(), balance)), // All other cases return None _ => None, } } /// The total balance involved in this vote. pub fn balance(self) -> Balance { match self { AccountVote::Standard { balance, .. } => balance, AccountVote::Split { aye, nay } => aye.saturating_add(nay), AccountVote::SplitAbstain { aye, nay, abstain } => aye.saturating_add(nay).saturating_add(abstain), } } /// Returns `Some` with whether the vote is an aye vote if it is standard, otherwise `None` if /// it is split. pub fn as_standard(self) -> Option { match self { AccountVote::Standard { vote, .. } => Some(vote.aye), _ => None, } } } /// A "prior" lock, i.e. a lock for some now-forgotten reason. #[derive( Encode, Decode, DecodeWithMemTracking, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen, )] pub struct PriorLock(BlockNumber, Balance); impl PriorLock { /// Accumulates an additional lock. pub fn accumulate(&mut self, until: BlockNumber, amount: Balance) { self.0 = self.0.max(until); self.1 = self.1.max(amount); } pub fn locked(&self) -> Balance { self.1 } pub fn rejig(&mut self, now: BlockNumber) { if now >= self.0 { self.0 = Zero::zero(); self.1 = Zero::zero(); } } } /// Information concerning the delegation of some voting power. #[derive( Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, )] pub struct Delegating { /// The amount of balance delegated. pub balance: Balance, /// The account to which the voting power is delegated. pub target: AccountId, /// The conviction with which the voting power is delegated. When this gets undelegated, the /// relevant lock begins. pub conviction: Conviction, /// The total amount of delegations that this account has received, post-conviction-weighting. pub delegations: Delegations, /// Any pre-existing locks from past voting/delegating activity. pub prior: PriorLock, } /// Information concerning the direct vote-casting of some voting power. #[derive( Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, )] #[scale_info(skip_type_params(MaxVotes))] #[codec(mel_bound(Balance: MaxEncodedLen, BlockNumber: MaxEncodedLen, PollIndex: MaxEncodedLen))] pub struct Casting where MaxVotes: Get, { /// The current votes of the account. pub votes: BoundedVec<(PollIndex, AccountVote), MaxVotes>, /// The total amount of delegations that this account has received, post-conviction-weighting. pub delegations: Delegations, /// Any pre-existing locks from past voting/delegating activity. pub prior: PriorLock, } /// An indicator for what an account is doing; it can either be delegating or voting. #[derive( Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, )] #[scale_info(skip_type_params(MaxVotes))] #[codec(mel_bound( Balance: MaxEncodedLen, AccountId: MaxEncodedLen, BlockNumber: MaxEncodedLen, PollIndex: MaxEncodedLen, ))] pub enum Voting where MaxVotes: Get, { /// The account is voting directly. Casting(Casting), /// The account is delegating `balance` of its balance to a `target` account with `conviction`. Delegating(Delegating), } impl Default for Voting where MaxVotes: Get, { fn default() -> Self { Voting::Casting(Casting { votes: Default::default(), delegations: Default::default(), prior: PriorLock(Zero::zero(), Default::default()), }) } } impl AsMut> for Voting where MaxVotes: Get, { fn as_mut(&mut self) -> &mut PriorLock { match self { Voting::Casting(Casting { prior, .. }) => prior, Voting::Delegating(Delegating { prior, .. }) => prior, } } } impl< Balance: Saturating + Ord + Zero + Copy, BlockNumber: Ord + Copy + Zero, AccountId, PollIndex, MaxVotes, > Voting where MaxVotes: Get, { pub fn rejig(&mut self, now: BlockNumber) { AsMut::>::as_mut(self).rejig(now); } /// The amount of this account's balance that must currently be locked due to voting. pub fn locked_balance(&self) -> Balance { match self { Voting::Casting(Casting { votes, prior, .. }) => votes.iter().map(|i| i.1.balance()).fold(prior.locked(), |a, i| a.max(i)), Voting::Delegating(Delegating { balance, prior, .. }) => *balance.max(&prior.locked()), } } pub fn set_common( &mut self, delegations: Delegations, prior: PriorLock, ) { let (d, p) = match self { Voting::Casting(Casting { ref mut delegations, ref mut prior, .. }) => (delegations, prior), Voting::Delegating(Delegating { ref mut delegations, ref mut prior, .. }) => (delegations, prior), }; *d = delegations; *p = prior; } }