#![recursion_limit = "256"]
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use frame_support::{
ensure,
traits::{
defensive_prelude::*,
schedule::{v3::Named as ScheduleNamed, DispatchTime},
Bounded, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, QueryPreimage,
ReservableCurrency, StorePreimage, WithdrawReasons,
},
weights::Weight,
};
use sp_runtime::{
traits::{Bounded as ArithBounded, One, Saturating, StaticLookup, Zero},
ArithmeticError, DispatchError, DispatchResult,
};
use sp_std::prelude::*;
mod conviction;
mod types;
mod vote;
mod vote_threshold;
pub mod weights;
pub use conviction::Conviction;
pub use pallet::*;
pub use types::{Delegations, ReferendumInfo, ReferendumStatus, Tally, UnvoteScope};
pub use vote::{AccountVote, Vote, Voting};
pub use vote_threshold::{Approved, VoteThreshold};
pub use weights::WeightInfo;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
pub mod migrations;
const DEMOCRACY_ID: LockIdentifier = *b"democrac";
pub type PropIndex = u32;
pub type ReferendumIndex = u32;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;
pub type CallOf<T> = <T as frame_system::Config>::RuntimeCall;
pub type BoundedCallOf<T> = Bounded<CallOf<T>>;
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
#[frame_support::pallet]
pub mod pallet {
use super::{DispatchResult, *};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_core::H256;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config + Sized {
type WeightInfo: WeightInfo;
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Scheduler: ScheduleNamed<Self::BlockNumber, CallOf<Self>, Self::PalletsOrigin>;
type Preimages: QueryPreimage + StorePreimage;
type Currency: ReservableCurrency<Self::AccountId>
+ LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>;
#[pallet::constant]
type EnactmentPeriod: Get<Self::BlockNumber>;
#[pallet::constant]
type LaunchPeriod: Get<Self::BlockNumber>;
#[pallet::constant]
type VotingPeriod: Get<Self::BlockNumber>;
#[pallet::constant]
type VoteLockingPeriod: Get<Self::BlockNumber>;
#[pallet::constant]
type MinimumDeposit: Get<BalanceOf<Self>>;
#[pallet::constant]
type InstantAllowed: Get<bool>;
#[pallet::constant]
type FastTrackVotingPeriod: Get<Self::BlockNumber>;
#[pallet::constant]
type CooloffPeriod: Get<Self::BlockNumber>;
#[pallet::constant]
type MaxVotes: Get<u32>;
#[pallet::constant]
type MaxProposals: Get<u32>;
#[pallet::constant]
type MaxDeposits: Get<u32>;
#[pallet::constant]
type MaxBlacklisted: Get<u32>;
type ExternalOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type ExternalMajorityOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type ExternalDefaultOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type FastTrackOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type InstantOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type CancellationOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type BlacklistOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type CancelProposalOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type VetoOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
type PalletsOrigin: From<frame_system::RawOrigin<Self::AccountId>>;
type Slash: OnUnbalanced<NegativeImbalanceOf<Self>>;
}
#[pallet::storage]
#[pallet::getter(fn public_prop_count)]
pub type PublicPropCount<T> = StorageValue<_, PropIndex, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn public_props)]
pub type PublicProps<T: Config> = StorageValue<
_,
BoundedVec<(PropIndex, BoundedCallOf<T>, T::AccountId), T::MaxProposals>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn deposit_of)]
pub type DepositOf<T: Config> = StorageMap<
_,
Twox64Concat,
PropIndex,
(BoundedVec<T::AccountId, T::MaxDeposits>, BalanceOf<T>),
>;
#[pallet::storage]
#[pallet::getter(fn referendum_count)]
pub type ReferendumCount<T> = StorageValue<_, ReferendumIndex, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn lowest_unbaked)]
pub type LowestUnbaked<T> = StorageValue<_, ReferendumIndex, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn referendum_info)]
pub type ReferendumInfoOf<T: Config> = StorageMap<
_,
Twox64Concat,
ReferendumIndex,
ReferendumInfo<T::BlockNumber, BoundedCallOf<T>, BalanceOf<T>>,
>;
#[pallet::storage]
pub type VotingOf<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
Voting<BalanceOf<T>, T::AccountId, T::BlockNumber, T::MaxVotes>,
ValueQuery,
>;
#[pallet::storage]
pub type LastTabledWasExternal<T> = StorageValue<_, bool, ValueQuery>;
#[pallet::storage]
pub type NextExternal<T: Config> = StorageValue<_, (BoundedCallOf<T>, VoteThreshold)>;
#[pallet::storage]
pub type Blacklist<T: Config> = StorageMap<
_,
Identity,
H256,
(T::BlockNumber, BoundedVec<T::AccountId, T::MaxBlacklisted>),
>;
#[pallet::storage]
pub type Cancellations<T: Config> = StorageMap<_, Identity, H256, bool, ValueQuery>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
_phantom: sp_std::marker::PhantomData<T>,
}
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig { _phantom: Default::default() }
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
PublicPropCount::<T>::put(0 as PropIndex);
ReferendumCount::<T>::put(0 as ReferendumIndex);
LowestUnbaked::<T>::put(0 as ReferendumIndex);
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Proposed { proposal_index: PropIndex, deposit: BalanceOf<T> },
Tabled { proposal_index: PropIndex, deposit: BalanceOf<T> },
ExternalTabled,
Started { ref_index: ReferendumIndex, threshold: VoteThreshold },
Passed { ref_index: ReferendumIndex },
NotPassed { ref_index: ReferendumIndex },
Cancelled { ref_index: ReferendumIndex },
Delegated { who: T::AccountId, target: T::AccountId },
Undelegated { account: T::AccountId },
Vetoed { who: T::AccountId, proposal_hash: H256, until: T::BlockNumber },
Blacklisted { proposal_hash: H256 },
Voted { voter: T::AccountId, ref_index: ReferendumIndex, vote: AccountVote<BalanceOf<T>> },
Seconded { seconder: T::AccountId, prop_index: PropIndex },
ProposalCanceled { prop_index: PropIndex },
}
#[pallet::error]
pub enum Error<T> {
ValueLow,
ProposalMissing,
AlreadyCanceled,
DuplicateProposal,
ProposalBlacklisted,
NotSimpleMajority,
InvalidHash,
NoProposal,
AlreadyVetoed,
ReferendumInvalid,
NoneWaiting,
NotVoter,
NoPermission,
AlreadyDelegating,
InsufficientFunds,
NotDelegating,
VotesExist,
InstantNotAllowed,
Nonsense,
WrongUpperBound,
MaxVotesReached,
TooMany,
VotingPeriodLow,
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight {
Self::begin_block(n)
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::propose())]
pub fn propose(
origin: OriginFor<T>,
proposal: BoundedCallOf<T>,
#[pallet::compact] value: BalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(value >= T::MinimumDeposit::get(), Error::<T>::ValueLow);
let index = Self::public_prop_count();
let real_prop_count = PublicProps::<T>::decode_len().unwrap_or(0) as u32;
let max_proposals = T::MaxProposals::get();
ensure!(real_prop_count < max_proposals, Error::<T>::TooMany);
let proposal_hash = proposal.hash();
if let Some((until, _)) = <Blacklist<T>>::get(proposal_hash) {
ensure!(
<frame_system::Pallet<T>>::block_number() >= until,
Error::<T>::ProposalBlacklisted,
);
}
T::Currency::reserve(&who, value)?;
let depositors = BoundedVec::<_, T::MaxDeposits>::truncate_from(vec![who.clone()]);
DepositOf::<T>::insert(index, (depositors, value));
PublicPropCount::<T>::put(index + 1);
PublicProps::<T>::try_append((index, proposal, who))
.map_err(|_| Error::<T>::TooMany)?;
Self::deposit_event(Event::<T>::Proposed { proposal_index: index, deposit: value });
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::second())]
pub fn second(
origin: OriginFor<T>,
#[pallet::compact] proposal: PropIndex,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let seconds = Self::len_of_deposit_of(proposal).ok_or(Error::<T>::ProposalMissing)?;
ensure!(seconds < T::MaxDeposits::get(), Error::<T>::TooMany);
let mut deposit = Self::deposit_of(proposal).ok_or(Error::<T>::ProposalMissing)?;
T::Currency::reserve(&who, deposit.1)?;
let ok = deposit.0.try_push(who.clone()).is_ok();
debug_assert!(ok, "`seconds` is below static limit; `try_insert` should succeed; qed");
<DepositOf<T>>::insert(proposal, deposit);
Self::deposit_event(Event::<T>::Seconded { seconder: who, prop_index: proposal });
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))]
pub fn vote(
origin: OriginFor<T>,
#[pallet::compact] ref_index: ReferendumIndex,
vote: AccountVote<BalanceOf<T>>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::try_vote(&who, ref_index, vote)
}
#[pallet::call_index(3)]
#[pallet::weight((T::WeightInfo::emergency_cancel(), DispatchClass::Operational))]
pub fn emergency_cancel(
origin: OriginFor<T>,
ref_index: ReferendumIndex,
) -> DispatchResult {
T::CancellationOrigin::ensure_origin(origin)?;
let status = Self::referendum_status(ref_index)?;
let h = status.proposal.hash();
ensure!(!<Cancellations<T>>::contains_key(h), Error::<T>::AlreadyCanceled);
<Cancellations<T>>::insert(h, true);
Self::internal_cancel_referendum(ref_index);
Ok(())
}
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::external_propose())]
pub fn external_propose(
origin: OriginFor<T>,
proposal: BoundedCallOf<T>,
) -> DispatchResult {
T::ExternalOrigin::ensure_origin(origin)?;
ensure!(!<NextExternal<T>>::exists(), Error::<T>::DuplicateProposal);
if let Some((until, _)) = <Blacklist<T>>::get(proposal.hash()) {
ensure!(
<frame_system::Pallet<T>>::block_number() >= until,
Error::<T>::ProposalBlacklisted,
);
}
<NextExternal<T>>::put((proposal, VoteThreshold::SuperMajorityApprove));
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::external_propose_majority())]
pub fn external_propose_majority(
origin: OriginFor<T>,
proposal: BoundedCallOf<T>,
) -> DispatchResult {
T::ExternalMajorityOrigin::ensure_origin(origin)?;
<NextExternal<T>>::put((proposal, VoteThreshold::SimpleMajority));
Ok(())
}
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::external_propose_default())]
pub fn external_propose_default(
origin: OriginFor<T>,
proposal: BoundedCallOf<T>,
) -> DispatchResult {
T::ExternalDefaultOrigin::ensure_origin(origin)?;
<NextExternal<T>>::put((proposal, VoteThreshold::SuperMajorityAgainst));
Ok(())
}
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::fast_track())]
pub fn fast_track(
origin: OriginFor<T>,
proposal_hash: H256,
voting_period: T::BlockNumber,
delay: T::BlockNumber,
) -> DispatchResult {
let maybe_ensure_instant = if voting_period < T::FastTrackVotingPeriod::get() {
Some(origin)
} else if let Err(origin) = T::FastTrackOrigin::try_origin(origin) {
Some(origin)
} else {
None
};
if let Some(ensure_instant) = maybe_ensure_instant {
T::InstantOrigin::ensure_origin(ensure_instant)?;
ensure!(T::InstantAllowed::get(), Error::<T>::InstantNotAllowed);
}
ensure!(voting_period > T::BlockNumber::zero(), Error::<T>::VotingPeriodLow);
let (ext_proposal, threshold) =
<NextExternal<T>>::get().ok_or(Error::<T>::ProposalMissing)?;
ensure!(
threshold != VoteThreshold::SuperMajorityApprove,
Error::<T>::NotSimpleMajority,
);
ensure!(proposal_hash == ext_proposal.hash(), Error::<T>::InvalidHash);
<NextExternal<T>>::kill();
let now = <frame_system::Pallet<T>>::block_number();
Self::inject_referendum(
now.saturating_add(voting_period),
ext_proposal,
threshold,
delay,
);
Ok(())
}
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::veto_external())]
pub fn veto_external(origin: OriginFor<T>, proposal_hash: H256) -> DispatchResult {
let who = T::VetoOrigin::ensure_origin(origin)?;
if let Some((ext_proposal, _)) = NextExternal::<T>::get() {
ensure!(proposal_hash == ext_proposal.hash(), Error::<T>::ProposalMissing);
} else {
return Err(Error::<T>::NoProposal.into())
}
let mut existing_vetoers =
<Blacklist<T>>::get(&proposal_hash).map(|pair| pair.1).unwrap_or_default();
let insert_position =
existing_vetoers.binary_search(&who).err().ok_or(Error::<T>::AlreadyVetoed)?;
existing_vetoers
.try_insert(insert_position, who.clone())
.map_err(|_| Error::<T>::TooMany)?;
let until =
<frame_system::Pallet<T>>::block_number().saturating_add(T::CooloffPeriod::get());
<Blacklist<T>>::insert(&proposal_hash, (until, existing_vetoers));
Self::deposit_event(Event::<T>::Vetoed { who, proposal_hash, until });
<NextExternal<T>>::kill();
Ok(())
}
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::cancel_referendum())]
pub fn cancel_referendum(
origin: OriginFor<T>,
#[pallet::compact] ref_index: ReferendumIndex,
) -> DispatchResult {
ensure_root(origin)?;
Self::internal_cancel_referendum(ref_index);
Ok(())
}
#[pallet::call_index(10)]
#[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))]
pub fn delegate(
origin: OriginFor<T>,
to: AccountIdLookupOf<T>,
conviction: Conviction,
balance: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let to = T::Lookup::lookup(to)?;
let votes = Self::try_delegate(who, to, conviction, balance)?;
Ok(Some(T::WeightInfo::delegate(votes)).into())
}
#[pallet::call_index(11)]
#[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get()))]
pub fn undelegate(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let votes = Self::try_undelegate(who)?;
Ok(Some(T::WeightInfo::undelegate(votes)).into())
}
#[pallet::call_index(12)]
#[pallet::weight(T::WeightInfo::clear_public_proposals())]
pub fn clear_public_proposals(origin: OriginFor<T>) -> DispatchResult {
ensure_root(origin)?;
<PublicProps<T>>::kill();
Ok(())
}
#[pallet::call_index(13)]
#[pallet::weight(T::WeightInfo::unlock_set(T::MaxVotes::get()).max(T::WeightInfo::unlock_remove(T::MaxVotes::get())))]
pub fn unlock(origin: OriginFor<T>, target: AccountIdLookupOf<T>) -> DispatchResult {
ensure_signed(origin)?;
let target = T::Lookup::lookup(target)?;
Self::update_lock(&target);
Ok(())
}
#[pallet::call_index(14)]
#[pallet::weight(T::WeightInfo::remove_vote(T::MaxVotes::get()))]
pub fn remove_vote(origin: OriginFor<T>, index: ReferendumIndex) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::try_remove_vote(&who, index, UnvoteScope::Any)
}
#[pallet::call_index(15)]
#[pallet::weight(T::WeightInfo::remove_other_vote(T::MaxVotes::get()))]
pub fn remove_other_vote(
origin: OriginFor<T>,
target: AccountIdLookupOf<T>,
index: ReferendumIndex,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let target = T::Lookup::lookup(target)?;
let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired };
Self::try_remove_vote(&target, index, scope)?;
Ok(())
}
#[pallet::call_index(16)]
#[pallet::weight((T::WeightInfo::blacklist(), DispatchClass::Operational))]
pub fn blacklist(
origin: OriginFor<T>,
proposal_hash: H256,
maybe_ref_index: Option<ReferendumIndex>,
) -> DispatchResult {
T::BlacklistOrigin::ensure_origin(origin)?;
let permanent = (T::BlockNumber::max_value(), BoundedVec::<T::AccountId, _>::default());
Blacklist::<T>::insert(&proposal_hash, permanent);
PublicProps::<T>::mutate(|props| {
if let Some(index) = props.iter().position(|p| p.1.hash() == proposal_hash) {
let (prop_index, ..) = props.remove(index);
if let Some((whos, amount)) = DepositOf::<T>::take(prop_index) {
for who in whos.into_iter() {
T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0);
}
}
}
});
if matches!(NextExternal::<T>::get(), Some((p, ..)) if p.hash() == proposal_hash) {
NextExternal::<T>::kill();
}
if let Some(ref_index) = maybe_ref_index {
if let Ok(status) = Self::referendum_status(ref_index) {
if status.proposal.hash() == proposal_hash {
Self::internal_cancel_referendum(ref_index);
}
}
}
Self::deposit_event(Event::<T>::Blacklisted { proposal_hash });
Ok(())
}
#[pallet::call_index(17)]
#[pallet::weight(T::WeightInfo::cancel_proposal())]
pub fn cancel_proposal(
origin: OriginFor<T>,
#[pallet::compact] prop_index: PropIndex,
) -> DispatchResult {
T::CancelProposalOrigin::ensure_origin(origin)?;
PublicProps::<T>::mutate(|props| props.retain(|p| p.0 != prop_index));
if let Some((whos, amount)) = DepositOf::<T>::take(prop_index) {
for who in whos.into_iter() {
T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0);
}
}
Self::deposit_event(Event::<T>::ProposalCanceled { prop_index });
Ok(())
}
}
}
pub trait EncodeInto: Encode {
fn encode_into<T: AsMut<[u8]> + Default>(&self) -> T {
let mut t = T::default();
self.using_encoded(|data| {
if data.len() <= t.as_mut().len() {
t.as_mut()[0..data.len()].copy_from_slice(data);
} else {
let hash = sp_io::hashing::blake2_256(data);
let l = t.as_mut().len().min(hash.len());
t.as_mut()[0..l].copy_from_slice(&hash[0..l]);
}
});
t
}
}
impl<T: Encode> EncodeInto for T {}
impl<T: Config> Pallet<T> {
pub fn backing_for(proposal: PropIndex) -> Option<BalanceOf<T>> {
Self::deposit_of(proposal).map(|(l, d)| d.saturating_mul((l.len() as u32).into()))
}
pub fn maturing_referenda_at(
n: T::BlockNumber,
) -> Vec<(ReferendumIndex, ReferendumStatus<T::BlockNumber, BoundedCallOf<T>, BalanceOf<T>>)> {
let next = Self::lowest_unbaked();
let last = Self::referendum_count();
Self::maturing_referenda_at_inner(n, next..last)
}
fn maturing_referenda_at_inner(
n: T::BlockNumber,
range: core::ops::Range<PropIndex>,
) -> Vec<(ReferendumIndex, ReferendumStatus<T::BlockNumber, BoundedCallOf<T>, BalanceOf<T>>)> {
range
.into_iter()
.map(|i| (i, Self::referendum_info(i)))
.filter_map(|(i, maybe_info)| match maybe_info {
Some(ReferendumInfo::Ongoing(status)) => Some((i, status)),
_ => None,
})
.filter(|(_, status)| status.end == n)
.collect()
}
pub fn internal_start_referendum(
proposal: BoundedCallOf<T>,
threshold: VoteThreshold,
delay: T::BlockNumber,
) -> ReferendumIndex {
<Pallet<T>>::inject_referendum(
<frame_system::Pallet<T>>::block_number().saturating_add(T::VotingPeriod::get()),
proposal,
threshold,
delay,
)
}
pub fn internal_cancel_referendum(ref_index: ReferendumIndex) {
Self::deposit_event(Event::<T>::Cancelled { ref_index });
ReferendumInfoOf::<T>::remove(ref_index);
}
fn ensure_ongoing(
r: ReferendumInfo<T::BlockNumber, BoundedCallOf<T>, BalanceOf<T>>,
) -> Result<ReferendumStatus<T::BlockNumber, BoundedCallOf<T>, BalanceOf<T>>, DispatchError> {
match r {
ReferendumInfo::Ongoing(s) => Ok(s),
_ => Err(Error::<T>::ReferendumInvalid.into()),
}
}
fn referendum_status(
ref_index: ReferendumIndex,
) -> Result<ReferendumStatus<T::BlockNumber, BoundedCallOf<T>, BalanceOf<T>>, DispatchError> {
let info = ReferendumInfoOf::<T>::get(ref_index).ok_or(Error::<T>::ReferendumInvalid)?;
Self::ensure_ongoing(info)
}
fn try_vote(
who: &T::AccountId,
ref_index: ReferendumIndex,
vote: AccountVote<BalanceOf<T>>,
) -> DispatchResult {
let mut status = Self::referendum_status(ref_index)?;
ensure!(vote.balance() <= T::Currency::free_balance(who), Error::<T>::InsufficientFunds);
VotingOf::<T>::try_mutate(who, |voting| -> DispatchResult {
if let Voting::Direct { ref mut votes, delegations, .. } = voting {
match votes.binary_search_by_key(&ref_index, |i| i.0) {
Ok(i) => {
status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?;
if let Some(approve) = votes[i].1.as_standard() {
status.tally.reduce(approve, *delegations);
}
votes[i].1 = vote;
},
Err(i) => {
votes
.try_insert(i, (ref_index, vote))
.map_err(|_| Error::<T>::MaxVotesReached)?;
},
}
Self::deposit_event(Event::<T>::Voted { voter: who.clone(), ref_index, vote });
status.tally.add(vote).ok_or(ArithmeticError::Overflow)?;
if let Some(approve) = vote.as_standard() {
status.tally.increase(approve, *delegations);
}
Ok(())
} else {
Err(Error::<T>::AlreadyDelegating.into())
}
})?;
T::Currency::extend_lock(DEMOCRACY_ID, who, vote.balance(), WithdrawReasons::TRANSFER);
ReferendumInfoOf::<T>::insert(ref_index, ReferendumInfo::Ongoing(status));
Ok(())
}
fn try_remove_vote(
who: &T::AccountId,
ref_index: ReferendumIndex,
scope: UnvoteScope,
) -> DispatchResult {
let info = ReferendumInfoOf::<T>::get(ref_index);
VotingOf::<T>::try_mutate(who, |voting| -> DispatchResult {
if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting {
let i = votes
.binary_search_by_key(&ref_index, |i| i.0)
.map_err(|_| Error::<T>::NotVoter)?;
match info {
Some(ReferendumInfo::Ongoing(mut status)) => {
ensure!(matches!(scope, UnvoteScope::Any), Error::<T>::NoPermission);
status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?;
if let Some(approve) = votes[i].1.as_standard() {
status.tally.reduce(approve, *delegations);
}
ReferendumInfoOf::<T>::insert(ref_index, ReferendumInfo::Ongoing(status));
},
Some(ReferendumInfo::Finished { end, approved }) => {
if let Some((lock_periods, balance)) = votes[i].1.locked_if(approved) {
let unlock_at = end.saturating_add(
T::VoteLockingPeriod::get().saturating_mul(lock_periods.into()),
);
let now = frame_system::Pallet::<T>::block_number();
if now < unlock_at {
ensure!(
matches!(scope, UnvoteScope::Any),
Error::<T>::NoPermission
);
prior.accumulate(unlock_at, balance)
}
}
},
None => {}, }
votes.remove(i);
}
Ok(())
})?;
Ok(())
}
fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations<BalanceOf<T>>) -> u32 {
VotingOf::<T>::mutate(who, |voting| match voting {
Voting::Delegating { delegations, .. } => {
*delegations = delegations.saturating_add(amount);
1
},
Voting::Direct { votes, delegations, .. } => {
*delegations = delegations.saturating_add(amount);
for &(ref_index, account_vote) in votes.iter() {
if let AccountVote::Standard { vote, .. } = account_vote {
ReferendumInfoOf::<T>::mutate(ref_index, |maybe_info| {
if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info {
status.tally.increase(vote.aye, amount);
}
});
}
}
votes.len() as u32
},
})
}
fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations<BalanceOf<T>>) -> u32 {
VotingOf::<T>::mutate(who, |voting| match voting {
Voting::Delegating { delegations, .. } => {
*delegations = delegations.saturating_sub(amount);
1
},
Voting::Direct { votes, delegations, .. } => {
*delegations = delegations.saturating_sub(amount);
for &(ref_index, account_vote) in votes.iter() {
if let AccountVote::Standard { vote, .. } = account_vote {
ReferendumInfoOf::<T>::mutate(ref_index, |maybe_info| {
if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info {
status.tally.reduce(vote.aye, amount);
}
});
}
}
votes.len() as u32
},
})
}
fn try_delegate(
who: T::AccountId,
target: T::AccountId,
conviction: Conviction,
balance: BalanceOf<T>,
) -> Result<u32, DispatchError> {
ensure!(who != target, Error::<T>::Nonsense);
ensure!(balance <= T::Currency::free_balance(&who), Error::<T>::InsufficientFunds);
let votes = VotingOf::<T>::try_mutate(&who, |voting| -> Result<u32, DispatchError> {
let mut old = Voting::Delegating {
balance,
target: target.clone(),
conviction,
delegations: Default::default(),
prior: Default::default(),
};
sp_std::mem::swap(&mut old, voting);
match old {
Voting::Delegating {
balance, target, conviction, delegations, mut prior, ..
} => {
Self::reduce_upstream_delegation(&target, conviction.votes(balance));
let now = frame_system::Pallet::<T>::block_number();
let lock_periods = conviction.lock_periods().into();
let unlock_block = now
.saturating_add(T::VoteLockingPeriod::get().saturating_mul(lock_periods));
prior.accumulate(unlock_block, balance);
voting.set_common(delegations, prior);
},
Voting::Direct { votes, delegations, prior } => {
ensure!(votes.is_empty(), Error::<T>::VotesExist);
voting.set_common(delegations, prior);
},
}
let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance));
T::Currency::extend_lock(DEMOCRACY_ID, &who, balance, WithdrawReasons::TRANSFER);
Ok(votes)
})?;
Self::deposit_event(Event::<T>::Delegated { who, target });
Ok(votes)
}
fn try_undelegate(who: T::AccountId) -> Result<u32, DispatchError> {
let votes = VotingOf::<T>::try_mutate(&who, |voting| -> Result<u32, DispatchError> {
let mut old = Voting::default();
sp_std::mem::swap(&mut old, voting);
match old {
Voting::Delegating { balance, target, conviction, delegations, mut prior } => {
let votes =
Self::reduce_upstream_delegation(&target, conviction.votes(balance));
let now = frame_system::Pallet::<T>::block_number();
let lock_periods = conviction.lock_periods().into();
let unlock_block = now
.saturating_add(T::VoteLockingPeriod::get().saturating_mul(lock_periods));
prior.accumulate(unlock_block, balance);
voting.set_common(delegations, prior);
Ok(votes)
},
Voting::Direct { .. } => Err(Error::<T>::NotDelegating.into()),
}
})?;
Self::deposit_event(Event::<T>::Undelegated { account: who });
Ok(votes)
}
fn update_lock(who: &T::AccountId) {
let lock_needed = VotingOf::<T>::mutate(who, |voting| {
voting.rejig(frame_system::Pallet::<T>::block_number());
voting.locked_balance()
});
if lock_needed.is_zero() {
T::Currency::remove_lock(DEMOCRACY_ID, who);
} else {
T::Currency::set_lock(DEMOCRACY_ID, who, lock_needed, WithdrawReasons::TRANSFER);
}
}
fn inject_referendum(
end: T::BlockNumber,
proposal: BoundedCallOf<T>,
threshold: VoteThreshold,
delay: T::BlockNumber,
) -> ReferendumIndex {
let ref_index = Self::referendum_count();
ReferendumCount::<T>::put(ref_index + 1);
let status =
ReferendumStatus { end, proposal, threshold, delay, tally: Default::default() };
let item = ReferendumInfo::Ongoing(status);
<ReferendumInfoOf<T>>::insert(ref_index, item);
Self::deposit_event(Event::<T>::Started { ref_index, threshold });
ref_index
}
fn launch_next(now: T::BlockNumber) -> DispatchResult {
if LastTabledWasExternal::<T>::take() {
Self::launch_public(now).or_else(|_| Self::launch_external(now))
} else {
Self::launch_external(now).or_else(|_| Self::launch_public(now))
}
.map_err(|_| Error::<T>::NoneWaiting.into())
}
fn launch_external(now: T::BlockNumber) -> DispatchResult {
if let Some((proposal, threshold)) = <NextExternal<T>>::take() {
LastTabledWasExternal::<T>::put(true);
Self::deposit_event(Event::<T>::ExternalTabled);
Self::inject_referendum(
now.saturating_add(T::VotingPeriod::get()),
proposal,
threshold,
T::EnactmentPeriod::get(),
);
Ok(())
} else {
return Err(Error::<T>::NoneWaiting.into())
}
}
fn launch_public(now: T::BlockNumber) -> DispatchResult {
let mut public_props = Self::public_props();
if let Some((winner_index, _)) = public_props.iter().enumerate().max_by_key(
|x| Self::backing_for((x.1).0).defensive_unwrap_or_else(Zero::zero),
) {
let (prop_index, proposal, _) = public_props.swap_remove(winner_index);
<PublicProps<T>>::put(public_props);
if let Some((depositors, deposit)) = <DepositOf<T>>::take(prop_index) {
for d in depositors.iter() {
T::Currency::unreserve(d, deposit);
}
Self::deposit_event(Event::<T>::Tabled { proposal_index: prop_index, deposit });
Self::inject_referendum(
now.saturating_add(T::VotingPeriod::get()),
proposal,
VoteThreshold::SuperMajorityApprove,
T::EnactmentPeriod::get(),
);
}
Ok(())
} else {
return Err(Error::<T>::NoneWaiting.into())
}
}
fn bake_referendum(
now: T::BlockNumber,
index: ReferendumIndex,
status: ReferendumStatus<T::BlockNumber, BoundedCallOf<T>, BalanceOf<T>>,
) -> bool {
let total_issuance = T::Currency::total_issuance();
let approved = status.threshold.approved(status.tally, total_issuance);
if approved {
Self::deposit_event(Event::<T>::Passed { ref_index: index });
T::Preimages::hold(&status.proposal);
let when = now.saturating_add(status.delay.max(One::one()));
if T::Scheduler::schedule_named(
(DEMOCRACY_ID, index).encode_into(),
DispatchTime::At(when),
None,
63,
frame_system::RawOrigin::Root.into(),
status.proposal,
)
.is_err()
{
frame_support::print("LOGIC ERROR: bake_referendum/schedule_named failed");
}
} else {
Self::deposit_event(Event::<T>::NotPassed { ref_index: index });
}
approved
}
fn begin_block(now: T::BlockNumber) -> Weight {
let max_block_weight = T::BlockWeights::get().max_block;
let mut weight = Weight::zero();
let next = Self::lowest_unbaked();
let last = Self::referendum_count();
let r = last.saturating_sub(next);
if (now % T::LaunchPeriod::get()).is_zero() {
if Self::launch_next(now).is_ok() {
weight = max_block_weight;
} else {
weight.saturating_accrue(T::WeightInfo::on_initialize_base_with_launch_period(r));
}
} else {
weight.saturating_accrue(T::WeightInfo::on_initialize_base(r));
}
for (index, info) in Self::maturing_referenda_at_inner(now, next..last).into_iter() {
let approved = Self::bake_referendum(now, index, info);
ReferendumInfoOf::<T>::insert(index, ReferendumInfo::Finished { end: now, approved });
weight = max_block_weight;
}
<LowestUnbaked<T>>::mutate(|ref_index| {
while *ref_index < last &&
Self::referendum_info(*ref_index)
.map_or(true, |info| matches!(info, ReferendumInfo::Finished { .. }))
{
*ref_index += 1
}
});
weight
}
fn len_of_deposit_of(proposal: PropIndex) -> Option<u32> {
decode_compact_u32_at(&<DepositOf<T>>::hashed_key_for(proposal))
}
}
fn decode_compact_u32_at(key: &[u8]) -> Option<u32> {
let mut buf = [0u8; 5];
let bytes = sp_io::storage::read(key, &mut buf, 0)?;
let mut input = &buf[0..buf.len().min(bytes as usize)];
match codec::Compact::<u32>::decode(&mut input) {
Ok(c) => Some(c.0),
Err(_) => {
sp_runtime::print("Failed to decode compact u32 at:");
sp_runtime::print(key);
None
},
}
}