#![recursion_limit = "256"]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{vec, vec::Vec};
use codec::{Decode, Encode};
use pezframe_support::{
ensure,
traits::{
defensive_prelude::*,
schedule::{v3::Named as ScheduleNamed, DispatchTime},
Bounded, Currency, EnsureOrigin, Get, LockIdentifier, LockableCurrency, OnUnbalanced,
QueryPreimage, ReservableCurrency, StorePreimage, WithdrawReasons,
},
weights::Weight,
};
use pezframe_system::pezpallet_prelude::{BlockNumberFor, OriginFor};
use pezsp_runtime::{
traits::{BadOrigin, Bounded as ArithBounded, One, Saturating, StaticLookup, Zero},
ArithmeticError, DispatchError, DispatchResult,
};
mod conviction;
mod types;
mod vote;
mod vote_threshold;
pub mod weights;
pub use conviction::Conviction;
pub use pezpallet::*;
pub use types::{
Delegations, MetadataOwner, PropIndex, ReferendumIndex, 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;
pub(crate) const DEMOCRACY_ID: LockIdentifier = *b"democrac";
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as pezframe_system::Config>::AccountId,
>>::NegativeImbalance;
pub type CallOf<T> = <T as pezframe_system::Config>::RuntimeCall;
pub type BoundedCallOf<T> = Bounded<CallOf<T>, <T as pezframe_system::Config>::Hashing>;
type AccountIdLookupOf<T> = <<T as pezframe_system::Config>::Lookup as StaticLookup>::Source;
#[pezframe_support::pezpallet]
pub mod pezpallet {
use super::{DispatchResult, *};
use pezframe_support::pezpallet_prelude::*;
use pezframe_system::pezpallet_prelude::*;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pezpallet::pezpallet]
#[pezpallet::storage_version(STORAGE_VERSION)]
pub struct Pezpallet<T>(_);
#[pezpallet::config]
pub trait Config: pezframe_system::Config + Sized {
type WeightInfo: WeightInfo;
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
type Scheduler: ScheduleNamed<
BlockNumberFor<Self>,
CallOf<Self>,
Self::PalletsOrigin,
Hasher = Self::Hashing,
>;
type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
type Currency: ReservableCurrency<Self::AccountId>
+ LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>;
#[pezpallet::constant]
type EnactmentPeriod: Get<BlockNumberFor<Self>>;
#[pezpallet::constant]
type LaunchPeriod: Get<BlockNumberFor<Self>>;
#[pezpallet::constant]
type VotingPeriod: Get<BlockNumberFor<Self>>;
#[pezpallet::constant]
type VoteLockingPeriod: Get<BlockNumberFor<Self>>;
#[pezpallet::constant]
type MinimumDeposit: Get<BalanceOf<Self>>;
#[pezpallet::constant]
type InstantAllowed: Get<bool>;
#[pezpallet::constant]
type FastTrackVotingPeriod: Get<BlockNumberFor<Self>>;
#[pezpallet::constant]
type CooloffPeriod: Get<BlockNumberFor<Self>>;
#[pezpallet::constant]
type MaxVotes: Get<u32>;
#[pezpallet::constant]
type MaxProposals: Get<u32>;
#[pezpallet::constant]
type MaxDeposits: Get<u32>;
#[pezpallet::constant]
type MaxBlacklisted: Get<u32>;
type ExternalOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type ExternalMajorityOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type ExternalDefaultOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type SubmitOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
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<pezframe_system::RawOrigin<Self::AccountId>>;
type Slash: OnUnbalanced<NegativeImbalanceOf<Self>>;
}
#[pezpallet::storage]
pub type PublicPropCount<T> = StorageValue<_, PropIndex, ValueQuery>;
#[pezpallet::storage]
pub type PublicProps<T: Config> = StorageValue<
_,
BoundedVec<(PropIndex, BoundedCallOf<T>, T::AccountId), T::MaxProposals>,
ValueQuery,
>;
#[pezpallet::storage]
pub type DepositOf<T: Config> = StorageMap<
_,
Twox64Concat,
PropIndex,
(BoundedVec<T::AccountId, T::MaxDeposits>, BalanceOf<T>),
>;
#[pezpallet::storage]
pub type ReferendumCount<T> = StorageValue<_, ReferendumIndex, ValueQuery>;
#[pezpallet::storage]
pub type LowestUnbaked<T> = StorageValue<_, ReferendumIndex, ValueQuery>;
#[pezpallet::storage]
pub type ReferendumInfoOf<T: Config> = StorageMap<
_,
Twox64Concat,
ReferendumIndex,
ReferendumInfo<BlockNumberFor<T>, BoundedCallOf<T>, BalanceOf<T>>,
>;
#[pezpallet::storage]
pub type VotingOf<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
Voting<BalanceOf<T>, T::AccountId, BlockNumberFor<T>, T::MaxVotes>,
ValueQuery,
>;
#[pezpallet::storage]
pub type LastTabledWasExternal<T> = StorageValue<_, bool, ValueQuery>;
#[pezpallet::storage]
pub type NextExternal<T: Config> = StorageValue<_, (BoundedCallOf<T>, VoteThreshold)>;
#[pezpallet::storage]
pub type Blacklist<T: Config> = StorageMap<
_,
Identity,
T::Hash,
(BlockNumberFor<T>, BoundedVec<T::AccountId, T::MaxBlacklisted>),
>;
#[pezpallet::storage]
pub type Cancellations<T: Config> = StorageMap<_, Identity, T::Hash, bool, ValueQuery>;
#[pezpallet::storage]
pub type MetadataOf<T: Config> = StorageMap<_, Blake2_128Concat, MetadataOwner, T::Hash>;
#[pezpallet::genesis_config]
#[derive(pezframe_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
#[serde(skip)]
_config: core::marker::PhantomData<T>,
}
#[pezpallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
PublicPropCount::<T>::put(0 as PropIndex);
ReferendumCount::<T>::put(0 as ReferendumIndex);
LowestUnbaked::<T>::put(0 as ReferendumIndex);
}
}
#[pezpallet::event]
#[pezpallet::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: T::Hash, until: BlockNumberFor<T> },
Blacklisted { proposal_hash: T::Hash },
Voted { voter: T::AccountId, ref_index: ReferendumIndex, vote: AccountVote<BalanceOf<T>> },
Seconded { seconder: T::AccountId, prop_index: PropIndex },
ProposalCanceled { prop_index: PropIndex },
MetadataSet {
owner: MetadataOwner,
hash: T::Hash,
},
MetadataCleared {
owner: MetadataOwner,
hash: T::Hash,
},
MetadataTransferred {
prev_owner: MetadataOwner,
owner: MetadataOwner,
hash: T::Hash,
},
}
#[pezpallet::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,
PreimageNotExist,
}
#[pezpallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
Self::begin_block(n)
}
}
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
#[pezpallet::call_index(0)]
#[pezpallet::weight(T::WeightInfo::propose())]
pub fn propose(
origin: OriginFor<T>,
proposal: BoundedCallOf<T>,
#[pezpallet::compact] value: BalanceOf<T>,
) -> DispatchResult {
let who = T::SubmitOrigin::ensure_origin(origin)?;
ensure!(value >= T::MinimumDeposit::get(), Error::<T>::ValueLow);
let index = PublicPropCount::<T>::get();
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!(
pezframe_system::Pezpallet::<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(())
}
#[pezpallet::call_index(1)]
#[pezpallet::weight(T::WeightInfo::second())]
pub fn second(
origin: OriginFor<T>,
#[pezpallet::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 = DepositOf::<T>::get(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(())
}
#[pezpallet::call_index(2)]
#[pezpallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))]
pub fn vote(
origin: OriginFor<T>,
#[pezpallet::compact] ref_index: ReferendumIndex,
vote: AccountVote<BalanceOf<T>>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::try_vote(&who, ref_index, vote)
}
#[pezpallet::call_index(3)]
#[pezpallet::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(())
}
#[pezpallet::call_index(4)]
#[pezpallet::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!(
pezframe_system::Pezpallet::<T>::block_number() >= until,
Error::<T>::ProposalBlacklisted,
);
}
NextExternal::<T>::put((proposal, VoteThreshold::SuperMajorityApprove));
Ok(())
}
#[pezpallet::call_index(5)]
#[pezpallet::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(())
}
#[pezpallet::call_index(6)]
#[pezpallet::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(())
}
#[pezpallet::call_index(7)]
#[pezpallet::weight(T::WeightInfo::fast_track())]
pub fn fast_track(
origin: OriginFor<T>,
proposal_hash: T::Hash,
voting_period: BlockNumberFor<T>,
delay: BlockNumberFor<T>,
) -> DispatchResult {
let maybe_ensure_instant = if voting_period < T::FastTrackVotingPeriod::get() {
Some(origin)
} else {
T::FastTrackOrigin::try_origin(origin).err()
};
if let Some(ensure_instant) = maybe_ensure_instant {
T::InstantOrigin::ensure_origin(ensure_instant)?;
ensure!(T::InstantAllowed::get(), Error::<T>::InstantNotAllowed);
}
ensure!(voting_period > Zero::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 = pezframe_system::Pezpallet::<T>::block_number();
let ref_index = Self::inject_referendum(
now.saturating_add(voting_period),
ext_proposal,
threshold,
delay,
);
Self::transfer_metadata(MetadataOwner::External, MetadataOwner::Referendum(ref_index));
Ok(())
}
#[pezpallet::call_index(8)]
#[pezpallet::weight(T::WeightInfo::veto_external())]
pub fn veto_external(origin: OriginFor<T>, proposal_hash: T::Hash) -> 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 = pezframe_system::Pezpallet::<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();
Self::clear_metadata(MetadataOwner::External);
Ok(())
}
#[pezpallet::call_index(9)]
#[pezpallet::weight(T::WeightInfo::cancel_referendum())]
pub fn cancel_referendum(
origin: OriginFor<T>,
#[pezpallet::compact] ref_index: ReferendumIndex,
) -> DispatchResult {
ensure_root(origin)?;
Self::internal_cancel_referendum(ref_index);
Ok(())
}
#[pezpallet::call_index(10)]
#[pezpallet::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())
}
#[pezpallet::call_index(11)]
#[pezpallet::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())
}
#[pezpallet::call_index(12)]
#[pezpallet::weight(T::WeightInfo::clear_public_proposals())]
pub fn clear_public_proposals(origin: OriginFor<T>) -> DispatchResult {
ensure_root(origin)?;
PublicProps::<T>::kill();
Ok(())
}
#[pezpallet::call_index(13)]
#[pezpallet::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(())
}
#[pezpallet::call_index(14)]
#[pezpallet::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)
}
#[pezpallet::call_index(15)]
#[pezpallet::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(())
}
#[pezpallet::call_index(16)]
#[pezpallet::weight((T::WeightInfo::blacklist(), DispatchClass::Operational))]
pub fn blacklist(
origin: OriginFor<T>,
proposal_hash: T::Hash,
maybe_ref_index: Option<ReferendumIndex>,
) -> DispatchResult {
T::BlacklistOrigin::ensure_origin(origin)?;
let permanent =
(BlockNumberFor::<T>::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);
}
}
Self::clear_metadata(MetadataOwner::Proposal(prop_index));
}
});
if matches!(NextExternal::<T>::get(), Some((p, ..)) if p.hash() == proposal_hash) {
NextExternal::<T>::kill();
Self::clear_metadata(MetadataOwner::External);
}
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(())
}
#[pezpallet::call_index(17)]
#[pezpallet::weight(T::WeightInfo::cancel_proposal())]
pub fn cancel_proposal(
origin: OriginFor<T>,
#[pezpallet::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 });
Self::clear_metadata(MetadataOwner::Proposal(prop_index));
Ok(())
}
#[pezpallet::call_index(18)]
#[pezpallet::weight(
match (owner, maybe_hash) {
(MetadataOwner::External, Some(_)) => T::WeightInfo::set_external_metadata(),
(MetadataOwner::External, None) => T::WeightInfo::clear_external_metadata(),
(MetadataOwner::Proposal(_), Some(_)) => T::WeightInfo::set_proposal_metadata(),
(MetadataOwner::Proposal(_), None) => T::WeightInfo::clear_proposal_metadata(),
(MetadataOwner::Referendum(_), Some(_)) => T::WeightInfo::set_referendum_metadata(),
(MetadataOwner::Referendum(_), None) => T::WeightInfo::clear_referendum_metadata(),
}
)]
pub fn set_metadata(
origin: OriginFor<T>,
owner: MetadataOwner,
maybe_hash: Option<T::Hash>,
) -> DispatchResult {
match owner {
MetadataOwner::External => {
let (_, threshold) = NextExternal::<T>::get().ok_or(Error::<T>::NoProposal)?;
Self::ensure_external_origin(threshold, origin)?;
},
MetadataOwner::Proposal(index) => {
let who = ensure_signed(origin)?;
let (_, _, proposer) = Self::proposal(index)?;
ensure!(proposer == who, Error::<T>::NoPermission);
},
MetadataOwner::Referendum(index) => {
let is_root = ensure_signed_or_root(origin)?.is_none();
ensure!(is_root || maybe_hash.is_none(), Error::<T>::NoPermission);
ensure!(
is_root || Self::referendum_status(index).is_err(),
Error::<T>::NoPermission
);
},
}
if let Some(hash) = maybe_hash {
ensure!(T::Preimages::len(&hash).is_some(), Error::<T>::PreimageNotExist);
MetadataOf::<T>::insert(owner.clone(), hash);
Self::deposit_event(Event::<T>::MetadataSet { owner, hash });
} else {
Self::clear_metadata(owner);
}
Ok(())
}
}
}
pub trait EncodeInto: Encode {
fn encode_into<T: AsMut<[u8]> + Default, H: pezsp_core::Hasher>(&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 = H::hash(&data);
let hash = hash.as_ref();
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> Pezpallet<T> {
pub fn backing_for(proposal: PropIndex) -> Option<BalanceOf<T>> {
DepositOf::<T>::get(proposal).map(|(l, d)| d.saturating_mul((l.len() as u32).into()))
}
pub fn maturing_referenda_at(
n: BlockNumberFor<T>,
) -> Vec<(ReferendumIndex, ReferendumStatus<BlockNumberFor<T>, BoundedCallOf<T>, BalanceOf<T>>)>
{
let next = LowestUnbaked::<T>::get();
let last = ReferendumCount::<T>::get();
Self::maturing_referenda_at_inner(n, next..last)
}
fn maturing_referenda_at_inner(
n: BlockNumberFor<T>,
range: core::ops::Range<PropIndex>,
) -> Vec<(ReferendumIndex, ReferendumStatus<BlockNumberFor<T>, BoundedCallOf<T>, BalanceOf<T>>)>
{
range
.into_iter()
.map(|i| (i, ReferendumInfoOf::<T>::get(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: BlockNumberFor<T>,
) -> ReferendumIndex {
Pezpallet::<T>::inject_referendum(
pezframe_system::Pezpallet::<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);
Self::clear_metadata(MetadataOwner::Referendum(ref_index));
}
fn ensure_ongoing(
r: ReferendumInfo<BlockNumberFor<T>, BoundedCallOf<T>, BalanceOf<T>>,
) -> Result<ReferendumStatus<BlockNumberFor<T>, 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<BlockNumberFor<T>, 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::except(WithdrawReasons::RESERVE),
);
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 = pezframe_system::Pezpallet::<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(),
};
core::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 = pezframe_system::Pezpallet::<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::except(WithdrawReasons::RESERVE),
);
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();
core::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 = pezframe_system::Pezpallet::<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(pezframe_system::Pezpallet::<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::except(WithdrawReasons::RESERVE),
);
}
}
fn inject_referendum(
end: BlockNumberFor<T>,
proposal: BoundedCallOf<T>,
threshold: VoteThreshold,
delay: BlockNumberFor<T>,
) -> ReferendumIndex {
let ref_index = ReferendumCount::<T>::get();
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: BlockNumberFor<T>) -> 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: BlockNumberFor<T>) -> DispatchResult {
if let Some((proposal, threshold)) = NextExternal::<T>::take() {
LastTabledWasExternal::<T>::put(true);
Self::deposit_event(Event::<T>::ExternalTabled);
let ref_index = Self::inject_referendum(
now.saturating_add(T::VotingPeriod::get()),
proposal,
threshold,
T::EnactmentPeriod::get(),
);
Self::transfer_metadata(MetadataOwner::External, MetadataOwner::Referendum(ref_index));
Ok(())
} else {
return Err(Error::<T>::NoneWaiting.into());
}
}
fn launch_public(now: BlockNumberFor<T>) -> DispatchResult {
let mut public_props = PublicProps::<T>::get();
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 });
let ref_index = Self::inject_referendum(
now.saturating_add(T::VotingPeriod::get()),
proposal,
VoteThreshold::SuperMajorityApprove,
T::EnactmentPeriod::get(),
);
Self::transfer_metadata(
MetadataOwner::Proposal(prop_index),
MetadataOwner::Referendum(ref_index),
)
}
Ok(())
} else {
return Err(Error::<T>::NoneWaiting.into());
}
}
fn bake_referendum(
now: BlockNumberFor<T>,
index: ReferendumIndex,
status: ReferendumStatus<BlockNumberFor<T>, 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 });
let when = now.saturating_add(status.delay.max(One::one()));
if T::Scheduler::schedule_named(
(DEMOCRACY_ID, index).encode_into::<_, T::Hashing>(),
DispatchTime::At(when),
None,
63,
pezframe_system::RawOrigin::Root.into(),
status.proposal,
)
.is_err()
{
pezframe_support::print("LOGIC ERROR: bake_referendum/schedule_named failed");
}
} else {
Self::deposit_event(Event::<T>::NotPassed { ref_index: index });
}
approved
}
fn begin_block(now: BlockNumberFor<T>) -> Weight {
let max_block_weight = T::BlockWeights::get().max_block;
let mut weight = Weight::zero();
let next = LowestUnbaked::<T>::get();
let last = ReferendumCount::<T>::get();
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
&& ReferendumInfoOf::<T>::get(*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 proposal(index: PropIndex) -> Result<(PropIndex, BoundedCallOf<T>, T::AccountId), Error<T>> {
PublicProps::<T>::get()
.into_iter()
.find(|(prop_index, _, _)| prop_index == &index)
.ok_or(Error::<T>::ProposalMissing)
}
fn clear_metadata(owner: MetadataOwner) {
if let Some(hash) = MetadataOf::<T>::take(&owner) {
Self::deposit_event(Event::<T>::MetadataCleared { owner, hash });
}
}
fn transfer_metadata(owner: MetadataOwner, new_owner: MetadataOwner) {
if let Some(hash) = MetadataOf::<T>::take(&owner) {
MetadataOf::<T>::insert(&new_owner, hash);
Self::deposit_event(Event::<T>::MetadataTransferred {
prev_owner: owner,
owner: new_owner,
hash,
});
}
}
fn ensure_external_origin(
threshold: VoteThreshold,
origin: OriginFor<T>,
) -> Result<(), BadOrigin> {
match threshold {
VoteThreshold::SuperMajorityApprove => {
T::ExternalOrigin::ensure_origin(origin)?;
},
VoteThreshold::SuperMajorityAgainst => {
T::ExternalDefaultOrigin::ensure_origin(origin)?;
},
VoteThreshold::SimpleMajority => {
T::ExternalMajorityOrigin::ensure_origin(origin)?;
},
};
Ok(())
}
}
fn decode_compact_u32_at(key: &[u8]) -> Option<u32> {
let mut buf = [0u8; 5];
let bytes = pezsp_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(_) => {
pezsp_runtime::print("Failed to decode compact u32 at:");
pezsp_runtime::print(key);
None
},
}
}