Files
pezkuwi-subxt/substrate/frame/democracy/src/lib.rs
T
Vivek Pandya 2009821cde Remove use of trait Store from all pallets and deprecate it. (#13535)
* Remove use of trait Store from staking pallet

* Remove use of trait Store from bounties pallet

* Remove use of trait Store from collective pallet

* Remove use of trait Store from babe pallet

* Remove use of trait Store from assets pallet

* Remove use of trait Store from grandpa pallet

* Remove use of trait Store from balances pallet

* Remove use of trait Store from authorship pallet

* Remove use of trait Store from authority-discovery pallet

* Remove use of trait Store from atomic-swap pallet

* Remove use of trait Store from sudo pallet

* Remove use of trait Store from scheduler pallet

* Remove use of trait Store from scored-pool pallet

* Remove use of trait Store from society pallet

* Remove use of trait Store from lottery pallet

* Remove use of trait Store from executive pallet

* Remove use of trait Store from democracy pallet

* Remove use of trait Store from elections-phragmen pallet

* Remove use of trait Store from indices pallet

* Remove use of trait Store from identity pallet

* Remove use of trait Store from multisig pallet

* Remove use of trait Store from merkle-mountain-range pallet

* Remove use of trait Store from im-online pallet

* Remove use of trait Store from membership pallet

* Remove use of trait Store from nicks pallet

* Remove use of trait Store from session pallet

* Remove use of trait Store from transaction-payment pallet

* Remove use of trait Store from utility pallet

* Remove use of trait Store from child-bounties pallet

* Remove use of trait Store from nis pallet

* Remove use of trait Store from nfts pallet

* Remove use of trait Store from conviction-voting pallet

* Remove use of trait Store from treasury pallet

* Remove use of trait Store from vesting pallet

* Remove use of trait Store from preimage pallet

* Remove use of trait Store from uniques pallet

* Remove use of trait Store from ranked-collective pallet

* Remove use of trait Store from beefy-mmr pallet

* Remove use of trait Store from referenda pallet

* Remove use of trait Store from whitelist pallet

* Remove use of trait Store from alliance pallet

* Remove use of trait Store from nomination-pools pallet

* Remove use of trait Store from state-trie-migration pallet

* Remove use of trait Store from message-queue pallet

* Remove use of trait Store from root-offences pallet

* Remove use of trait Store from root-testing pallet

* Remove use of trait Store from timestamps pallet

* Remove use of trait Store from system pallet

* Remove use of trait Store from offences pallet

* Remove use of trait Store from recovery pallet

* Remove use of trait Store from node-authorization pallet

* Remove use of trait Store from proxy pallet

* Remove use of trait Store from benchmarking pallet

* Remove use of trait Store from bags-list pallet

* Add deprecated warning in store_trait

* Change warning message

* Run cargo fmt

* Fix warning and update tests

* Remove unnecessary allow deprecated

* Remove use of trait Store

* Fix mismatch in expected output

* Minor update to warning message for deprecation of generate_store with Store trait attribute

* Fixes as per review comments

* Fixes as per review suggestions

* Remove use of Store trait from core-fellowship pallet

* Fix type in store_trait.rs

* Fixes as pre review comment
2023-03-13 15:30:05 +00:00

1747 lines
63 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// This file is part of Substrate.
// 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.
//! # Democracy Pallet
//!
//! - [`Config`]
//! - [`Call`]
//!
//! ## Overview
//!
//! The Democracy pallet handles the administration of general stakeholder voting.
//!
//! There are two different queues that a proposal can be added to before it
//! becomes a referendum, 1) the proposal queue consisting of all public proposals
//! and 2) the external queue consisting of a single proposal that originates
//! from one of the _external_ origins (such as a collective group).
//!
//! Every launch period - a length defined in the runtime - the Democracy pallet
//! launches a referendum from a proposal that it takes from either the proposal
//! queue or the external queue in turn. Any token holder in the system can vote
//! on referenda. The voting system
//! uses time-lock voting by allowing the token holder to set their _conviction_
//! behind a vote. The conviction will dictate the length of time the tokens
//! will be locked, as well as the multiplier that scales the vote power.
//!
//! ### Terminology
//!
//! - **Enactment Period:** The minimum period of locking and the period between a proposal being
//! approved and enacted.
//! - **Lock Period:** A period of time after proposal enactment that the tokens of _winning_ voters
//! will be locked.
//! - **Conviction:** An indication of a voter's strength of belief in their vote. An increase
//! of one in conviction indicates that a token holder is willing to lock their tokens for twice
//! as many lock periods after enactment.
//! - **Vote:** A value that can either be in approval ("Aye") or rejection ("Nay") of a particular
//! referendum.
//! - **Proposal:** A submission to the chain that represents an action that a proposer (either an
//! account or an external origin) suggests that the system adopt.
//! - **Referendum:** A proposal that is in the process of being voted on for either acceptance or
//! rejection as a change to the system.
//! - **Delegation:** The act of granting your voting power to the decisions of another account for
//! up to a certain conviction.
//!
//! ### Adaptive Quorum Biasing
//!
//! A _referendum_ can be either simple majority-carries in which 50%+1 of the
//! votes decide the outcome or _adaptive quorum biased_. Adaptive quorum biasing
//! makes the threshold for passing or rejecting a referendum higher or lower
//! depending on how the referendum was originally proposed. There are two types of
//! adaptive quorum biasing: 1) _positive turnout bias_ makes a referendum
//! require a super-majority to pass that decreases as turnout increases and
//! 2) _negative turnout bias_ makes a referendum require a super-majority to
//! reject that decreases as turnout increases. Another way to think about the
//! quorum biasing is that _positive bias_ referendums will be rejected by
//! default and _negative bias_ referendums get passed by default.
//!
//! ## Interface
//!
//! ### Dispatchable Functions
//!
//! #### Public
//!
//! These calls can be made from any externally held account capable of creating
//! a signed extrinsic.
//!
//! Basic actions:
//! - `propose` - Submits a sensitive action, represented as a hash. Requires a deposit.
//! - `second` - Signals agreement with a proposal, moves it higher on the proposal queue, and
//! requires a matching deposit to the original.
//! - `vote` - Votes in a referendum, either the vote is "Aye" to enact the proposal or "Nay" to
//! keep the status quo.
//! - `unvote` - Cancel a previous vote, this must be done by the voter before the vote ends.
//! - `delegate` - Delegates the voting power (tokens * conviction) to another account.
//! - `undelegate` - Stops the delegation of voting power to another account.
//!
//! Administration actions that can be done to any account:
//! - `reap_vote` - Remove some account's expired votes.
//! - `unlock` - Redetermine the account's balance lock, potentially making tokens available.
//!
//! Preimage actions:
//! - `note_preimage` - Registers the preimage for an upcoming proposal, requires a deposit that is
//! returned once the proposal is enacted.
//! - `note_preimage_operational` - same but provided by `T::OperationalPreimageOrigin`.
//! - `note_imminent_preimage` - Registers the preimage for an upcoming proposal. Does not require a
//! deposit, but the proposal must be in the dispatch queue.
//! - `note_imminent_preimage_operational` - same but provided by `T::OperationalPreimageOrigin`.
//! - `reap_preimage` - Removes the preimage for an expired proposal. Will only work under the
//! condition that it's the same account that noted it and after the voting period, OR it's a
//! different account after the enactment period.
//!
//! #### Cancellation Origin
//!
//! This call can only be made by the `CancellationOrigin`.
//!
//! - `emergency_cancel` - Schedules an emergency cancellation of a referendum. Can only happen once
//! to a specific referendum.
//!
//! #### ExternalOrigin
//!
//! This call can only be made by the `ExternalOrigin`.
//!
//! - `external_propose` - Schedules a proposal to become a referendum once it is is legal for an
//! externally proposed referendum.
//!
//! #### External Majority Origin
//!
//! This call can only be made by the `ExternalMajorityOrigin`.
//!
//! - `external_propose_majority` - Schedules a proposal to become a majority-carries referendum
//! once it is legal for an externally proposed referendum.
//!
//! #### External Default Origin
//!
//! This call can only be made by the `ExternalDefaultOrigin`.
//!
//! - `external_propose_default` - Schedules a proposal to become a negative-turnout-bias referendum
//! once it is legal for an externally proposed referendum.
//!
//! #### Fast Track Origin
//!
//! This call can only be made by the `FastTrackOrigin`.
//!
//! - `fast_track` - Schedules the current externally proposed proposal that is "majority-carries"
//! to become a referendum immediately.
//!
//! #### Veto Origin
//!
//! This call can only be made by the `VetoOrigin`.
//!
//! - `veto_external` - Vetoes and blacklists the external proposal hash.
//!
//! #### Root
//!
//! - `cancel_referendum` - Removes a referendum.
//! - `cancel_queued` - Cancels a proposal that is queued for enactment.
//! - `clear_public_proposal` - Removes all public proposals.
#![recursion_limit = "256"]
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use frame_support::{
ensure,
error::BadOrigin,
traits::{
defensive_prelude::*,
schedule::{v3::Named as ScheduleNamed, DispatchTime},
Bounded, Currency, EnsureOrigin, Get, Hash as PreimageHash, LockIdentifier,
LockableCurrency, OnUnbalanced, QueryPreimage, ReservableCurrency, StorePreimage,
WithdrawReasons,
},
weights::Weight,
};
use frame_system::pallet_prelude::OriginFor;
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, 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;
const DEMOCRACY_ID: LockIdentifier = *b"democrac";
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;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[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>;
/// The Scheduler.
type Scheduler: ScheduleNamed<Self::BlockNumber, CallOf<Self>, Self::PalletsOrigin>;
/// The Preimage provider.
type Preimages: QueryPreimage + StorePreimage;
/// Currency type for this pallet.
type Currency: ReservableCurrency<Self::AccountId>
+ LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>;
/// The period between a proposal being approved and enacted.
///
/// It should generally be a little more than the unstake period to ensure that
/// voting stakers have an opportunity to remove themselves from the system in the case
/// where they are on the losing side of a vote.
#[pallet::constant]
type EnactmentPeriod: Get<Self::BlockNumber>;
/// How often (in blocks) new public referenda are launched.
#[pallet::constant]
type LaunchPeriod: Get<Self::BlockNumber>;
/// How often (in blocks) to check for new votes.
#[pallet::constant]
type VotingPeriod: Get<Self::BlockNumber>;
/// The minimum period of vote locking.
///
/// It should be no shorter than enactment period to ensure that in the case of an approval,
/// those successful voters are locked into the consequences that their votes entail.
#[pallet::constant]
type VoteLockingPeriod: Get<Self::BlockNumber>;
/// The minimum amount to be used as a deposit for a public referendum proposal.
#[pallet::constant]
type MinimumDeposit: Get<BalanceOf<Self>>;
/// Indicator for whether an emergency origin is even allowed to happen. Some chains may
/// want to set this permanently to `false`, others may want to condition it on things such
/// as an upgrade having happened recently.
#[pallet::constant]
type InstantAllowed: Get<bool>;
/// Minimum voting period allowed for a fast-track referendum.
#[pallet::constant]
type FastTrackVotingPeriod: Get<Self::BlockNumber>;
/// Period in blocks where an external proposal may not be re-submitted after being vetoed.
#[pallet::constant]
type CooloffPeriod: Get<Self::BlockNumber>;
/// The maximum number of votes for an account.
///
/// Also used to compute weight, an overly big value can
/// lead to extrinsic with very big weight: see `delegate` for instance.
#[pallet::constant]
type MaxVotes: Get<u32>;
/// The maximum number of public proposals that can exist at any time.
#[pallet::constant]
type MaxProposals: Get<u32>;
/// The maximum number of deposits a public proposal may have at any time.
#[pallet::constant]
type MaxDeposits: Get<u32>;
/// The maximum number of items which can be blacklisted.
#[pallet::constant]
type MaxBlacklisted: Get<u32>;
/// Origin from which the next tabled referendum may be forced. This is a normal
/// "super-majority-required" referendum.
type ExternalOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Origin from which the next tabled referendum may be forced; this allows for the tabling
/// of a majority-carries referendum.
type ExternalMajorityOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Origin from which the next tabled referendum may be forced; this allows for the tabling
/// of a negative-turnout-bias (default-carries) referendum.
type ExternalDefaultOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Origin from which the new proposal can be made.
///
/// The success variant is the account id of the depositor.
type SubmitOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
/// Origin from which the next majority-carries (or more permissive) referendum may be
/// tabled to vote according to the `FastTrackVotingPeriod` asynchronously in a similar
/// manner to the emergency origin. It retains its threshold method.
type FastTrackOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Origin from which the next majority-carries (or more permissive) referendum may be
/// tabled to vote immediately and asynchronously in a similar manner to the emergency
/// origin. It retains its threshold method.
type InstantOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Origin from which any referendum may be cancelled in an emergency.
type CancellationOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Origin from which proposals may be blacklisted.
type BlacklistOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Origin from which a proposal may be cancelled and its backers slashed.
type CancelProposalOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Origin for anyone able to veto proposals.
type VetoOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
/// Overarching type of all pallets origins.
type PalletsOrigin: From<frame_system::RawOrigin<Self::AccountId>>;
/// Handler for the unbalanced reduction when slashing a preimage deposit.
type Slash: OnUnbalanced<NegativeImbalanceOf<Self>>;
}
/// The number of (public) proposals that have been made so far.
#[pallet::storage]
#[pallet::getter(fn public_prop_count)]
pub type PublicPropCount<T> = StorageValue<_, PropIndex, ValueQuery>;
/// The public proposals. Unsorted. The second item is the proposal.
#[pallet::storage]
#[pallet::getter(fn public_props)]
pub type PublicProps<T: Config> = StorageValue<
_,
BoundedVec<(PropIndex, BoundedCallOf<T>, T::AccountId), T::MaxProposals>,
ValueQuery,
>;
/// Those who have locked a deposit.
///
/// TWOX-NOTE: Safe, as increasing integer keys are safe.
#[pallet::storage]
#[pallet::getter(fn deposit_of)]
pub type DepositOf<T: Config> = StorageMap<
_,
Twox64Concat,
PropIndex,
(BoundedVec<T::AccountId, T::MaxDeposits>, BalanceOf<T>),
>;
/// The next free referendum index, aka the number of referenda started so far.
#[pallet::storage]
#[pallet::getter(fn referendum_count)]
pub type ReferendumCount<T> = StorageValue<_, ReferendumIndex, ValueQuery>;
/// The lowest referendum index representing an unbaked referendum. Equal to
/// `ReferendumCount` if there isn't a unbaked referendum.
#[pallet::storage]
#[pallet::getter(fn lowest_unbaked)]
pub type LowestUnbaked<T> = StorageValue<_, ReferendumIndex, ValueQuery>;
/// Information concerning any given referendum.
///
/// TWOX-NOTE: SAFE as indexes are not under an attackers control.
#[pallet::storage]
#[pallet::getter(fn referendum_info)]
pub type ReferendumInfoOf<T: Config> = StorageMap<
_,
Twox64Concat,
ReferendumIndex,
ReferendumInfo<T::BlockNumber, BoundedCallOf<T>, BalanceOf<T>>,
>;
/// All votes for a particular voter. We store the balance for the number of votes that we
/// have recorded. The second item is the total amount of delegations, that will be added.
///
/// TWOX-NOTE: SAFE as `AccountId`s are crypto hashes anyway.
#[pallet::storage]
pub type VotingOf<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
Voting<BalanceOf<T>, T::AccountId, T::BlockNumber, T::MaxVotes>,
ValueQuery,
>;
/// True if the last referendum tabled was submitted externally. False if it was a public
/// proposal.
#[pallet::storage]
pub type LastTabledWasExternal<T> = StorageValue<_, bool, ValueQuery>;
/// The referendum to be tabled whenever it would be valid to table an external proposal.
/// This happens when a referendum needs to be tabled and one of two conditions are met:
/// - `LastTabledWasExternal` is `false`; or
/// - `PublicProps` is empty.
#[pallet::storage]
pub type NextExternal<T: Config> = StorageValue<_, (BoundedCallOf<T>, VoteThreshold)>;
/// A record of who vetoed what. Maps proposal hash to a possible existent block number
/// (until when it may not be resubmitted) and who vetoed it.
#[pallet::storage]
pub type Blacklist<T: Config> = StorageMap<
_,
Identity,
H256,
(T::BlockNumber, BoundedVec<T::AccountId, T::MaxBlacklisted>),
>;
/// Record of all proposals that have been subject to emergency cancellation.
#[pallet::storage]
pub type Cancellations<T: Config> = StorageMap<_, Identity, H256, bool, ValueQuery>;
/// General information concerning any proposal or referendum.
/// The `PreimageHash` refers to the preimage of the `Preimages` provider which can be a JSON
/// dump or IPFS hash of a JSON file.
///
/// Consider a garbage collection for a metadata of finished referendums to `unrequest` (remove)
/// large preimages.
#[pallet::storage]
pub type MetadataOf<T: Config> = StorageMap<_, Blake2_128Concat, MetadataOwner, PreimageHash>;
#[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> {
/// A motion has been proposed by a public account.
Proposed { proposal_index: PropIndex, deposit: BalanceOf<T> },
/// A public proposal has been tabled for referendum vote.
Tabled { proposal_index: PropIndex, deposit: BalanceOf<T> },
/// An external proposal has been tabled.
ExternalTabled,
/// A referendum has begun.
Started { ref_index: ReferendumIndex, threshold: VoteThreshold },
/// A proposal has been approved by referendum.
Passed { ref_index: ReferendumIndex },
/// A proposal has been rejected by referendum.
NotPassed { ref_index: ReferendumIndex },
/// A referendum has been cancelled.
Cancelled { ref_index: ReferendumIndex },
/// An account has delegated their vote to another account.
Delegated { who: T::AccountId, target: T::AccountId },
/// An account has cancelled a previous delegation operation.
Undelegated { account: T::AccountId },
/// An external proposal has been vetoed.
Vetoed { who: T::AccountId, proposal_hash: H256, until: T::BlockNumber },
/// A proposal_hash has been blacklisted permanently.
Blacklisted { proposal_hash: H256 },
/// An account has voted in a referendum
Voted { voter: T::AccountId, ref_index: ReferendumIndex, vote: AccountVote<BalanceOf<T>> },
/// An account has secconded a proposal
Seconded { seconder: T::AccountId, prop_index: PropIndex },
/// A proposal got canceled.
ProposalCanceled { prop_index: PropIndex },
/// Metadata for a proposal or a referendum has been set.
MetadataSet {
/// Metadata owner.
owner: MetadataOwner,
/// Preimage hash.
hash: PreimageHash,
},
/// Metadata for a proposal or a referendum has been cleared.
MetadataCleared {
/// Metadata owner.
owner: MetadataOwner,
/// Preimage hash.
hash: PreimageHash,
},
/// Metadata has been transferred to new owner.
MetadataTransferred {
/// Previous metadata owner.
prev_owner: MetadataOwner,
/// New metadata owner.
owner: MetadataOwner,
/// Preimage hash.
hash: PreimageHash,
},
}
#[pallet::error]
pub enum Error<T> {
/// Value too low
ValueLow,
/// Proposal does not exist
ProposalMissing,
/// Cannot cancel the same proposal twice
AlreadyCanceled,
/// Proposal already made
DuplicateProposal,
/// Proposal still blacklisted
ProposalBlacklisted,
/// Next external proposal not simple majority
NotSimpleMajority,
/// Invalid hash
InvalidHash,
/// No external proposal
NoProposal,
/// Identity may not veto a proposal twice
AlreadyVetoed,
/// Vote given for invalid referendum
ReferendumInvalid,
/// No proposals waiting
NoneWaiting,
/// The given account did not vote on the referendum.
NotVoter,
/// The actor has no permission to conduct the action.
NoPermission,
/// The account is already delegating.
AlreadyDelegating,
/// Too high a balance was provided that the account cannot afford.
InsufficientFunds,
/// The account is not currently delegating.
NotDelegating,
/// The account currently has votes attached to it and the operation cannot succeed until
/// these are removed, either through `unvote` or `reap_vote`.
VotesExist,
/// The instant referendum origin is currently disallowed.
InstantNotAllowed,
/// Delegation to oneself makes no sense.
Nonsense,
/// Invalid upper bound.
WrongUpperBound,
/// Maximum number of votes reached.
MaxVotesReached,
/// Maximum number of items reached.
TooMany,
/// Voting period too low
VotingPeriodLow,
/// The preimage does not exist.
PreimageNotExist,
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
/// Weight: see `begin_block`
fn on_initialize(n: T::BlockNumber) -> Weight {
Self::begin_block(n)
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Propose a sensitive action to be taken.
///
/// The dispatch origin of this call must be _Signed_ and the sender must
/// have funds to cover the deposit.
///
/// - `proposal_hash`: The hash of the proposal preimage.
/// - `value`: The amount of deposit (must be at least `MinimumDeposit`).
///
/// Emits `Proposed`.
#[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 = T::SubmitOrigin::ensure_origin(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(())
}
/// Signals agreement with a particular proposal.
///
/// The dispatch origin of this call must be _Signed_ and the sender
/// must have funds to cover the deposit, equal to the original deposit.
///
/// - `proposal`: The index of the proposal to second.
#[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(())
}
/// Vote in a referendum. If `vote.is_aye()`, the vote is to enact the proposal;
/// otherwise it is a vote to keep the status quo.
///
/// The dispatch origin of this call must be _Signed_.
///
/// - `ref_index`: The index of the referendum to vote for.
/// - `vote`: The vote configuration.
#[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)
}
/// Schedule an emergency cancellation of a referendum. Cannot happen twice to the same
/// referendum.
///
/// The dispatch origin of this call must be `CancellationOrigin`.
///
/// -`ref_index`: The index of the referendum to cancel.
///
/// Weight: `O(1)`.
#[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(())
}
/// Schedule a referendum to be tabled once it is legal to schedule an external
/// referendum.
///
/// The dispatch origin of this call must be `ExternalOrigin`.
///
/// - `proposal_hash`: The preimage hash of the proposal.
#[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(())
}
/// Schedule a majority-carries referendum to be tabled next once it is legal to schedule
/// an external referendum.
///
/// The dispatch of this call must be `ExternalMajorityOrigin`.
///
/// - `proposal_hash`: The preimage hash of the proposal.
///
/// Unlike `external_propose`, blacklisting has no effect on this and it may replace a
/// pre-scheduled `external_propose` call.
///
/// Weight: `O(1)`
#[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(())
}
/// Schedule a negative-turnout-bias referendum to be tabled next once it is legal to
/// schedule an external referendum.
///
/// The dispatch of this call must be `ExternalDefaultOrigin`.
///
/// - `proposal_hash`: The preimage hash of the proposal.
///
/// Unlike `external_propose`, blacklisting has no effect on this and it may replace a
/// pre-scheduled `external_propose` call.
///
/// Weight: `O(1)`
#[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(())
}
/// Schedule the currently externally-proposed majority-carries referendum to be tabled
/// immediately. If there is no externally-proposed referendum currently, or if there is one
/// but it is not a majority-carries referendum then it fails.
///
/// The dispatch of this call must be `FastTrackOrigin`.
///
/// - `proposal_hash`: The hash of the current external proposal.
/// - `voting_period`: The period that is allowed for voting on this proposal. Increased to
/// Must be always greater than zero.
/// For `FastTrackOrigin` must be equal or greater than `FastTrackVotingPeriod`.
/// - `delay`: The number of block after voting has ended in approval and this should be
/// enacted. This doesn't have a minimum amount.
///
/// Emits `Started`.
///
/// Weight: `O(1)`
#[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 {
// Rather complicated bit of code to ensure that either:
// - `voting_period` is at least `FastTrackVotingPeriod` and `origin` is
// `FastTrackOrigin`; or
// - `InstantAllowed` is `true` and `origin` is `InstantOrigin`.
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();
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(())
}
/// Veto and blacklist the external proposal hash.
///
/// The dispatch origin of this call must be `VetoOrigin`.
///
/// - `proposal_hash`: The preimage hash of the proposal to veto and blacklist.
///
/// Emits `Vetoed`.
///
/// Weight: `O(V + log(V))` where V is number of `existing vetoers`
#[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();
Self::clear_metadata(MetadataOwner::External);
Ok(())
}
/// Remove a referendum.
///
/// The dispatch origin of this call must be _Root_.
///
/// - `ref_index`: The index of the referendum to cancel.
///
/// # Weight: `O(1)`.
#[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(())
}
/// Delegate the voting power (with some given conviction) of the sending account.
///
/// The balance delegated is locked for as long as it's delegated, and thereafter for the
/// time appropriate for the conviction's lock period.
///
/// The dispatch origin of this call must be _Signed_, and the signing account must either:
/// - be delegating already; or
/// - have no voting activity (if there is, then it will need to be removed/consolidated
/// through `reap_vote` or `unvote`).
///
/// - `to`: The account whose voting the `target` account's voting power will follow.
/// - `conviction`: The conviction that will be attached to the delegated votes. When the
/// account is undelegated, the funds will be locked for the corresponding period.
/// - `balance`: The amount of the account's balance to be used in delegating. This must not
/// be more than the account's current balance.
///
/// Emits `Delegated`.
///
/// Weight: `O(R)` where R is the number of referendums the voter delegating to has
/// voted on. Weight is charged as if maximum votes.
// NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure
// because a valid delegation cover decoding a direct voting with max votes.
#[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())
}
/// Undelegate the voting power of the sending account.
///
/// Tokens may be unlocked following once an amount of time consistent with the lock period
/// of the conviction with which the delegation was issued.
///
/// The dispatch origin of this call must be _Signed_ and the signing account must be
/// currently delegating.
///
/// Emits `Undelegated`.
///
/// Weight: `O(R)` where R is the number of referendums the voter delegating to has
/// voted on. Weight is charged as if maximum votes.
// NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure
// because a valid delegation cover decoding a direct voting with max votes.
#[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())
}
/// Clears all public proposals.
///
/// The dispatch origin of this call must be _Root_.
///
/// Weight: `O(1)`.
#[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(())
}
/// Unlock tokens that have an expired lock.
///
/// The dispatch origin of this call must be _Signed_.
///
/// - `target`: The account to remove the lock on.
///
/// Weight: `O(R)` with R number of vote of target.
#[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(())
}
/// Remove a vote for a referendum.
///
/// If:
/// - the referendum was cancelled, or
/// - the referendum is ongoing, or
/// - the referendum has ended such that
/// - the vote of the account was in opposition to the result; or
/// - there was no conviction to the account's vote; or
/// - the account made a split vote
/// ...then the vote is removed cleanly and a following call to `unlock` may result in more
/// funds being available.
///
/// If, however, the referendum has ended and:
/// - it finished corresponding to the vote of the account, and
/// - the account made a standard vote with conviction, and
/// - the lock period of the conviction is not over
/// ...then the lock will be aggregated into the overall account's lock, which may involve
/// *overlocking* (where the two locks are combined into a single lock that is the maximum
/// of both the amount locked and the time is it locked for).
///
/// The dispatch origin of this call must be _Signed_, and the signer must have a vote
/// registered for referendum `index`.
///
/// - `index`: The index of referendum of the vote to be removed.
///
/// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on.
/// Weight is calculated for the maximum number of vote.
#[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)
}
/// Remove a vote for a referendum.
///
/// If the `target` is equal to the signer, then this function is exactly equivalent to
/// `remove_vote`. If not equal to the signer, then the vote must have expired,
/// either because the referendum was cancelled, because the voter lost the referendum or
/// because the conviction period is over.
///
/// The dispatch origin of this call must be _Signed_.
///
/// - `target`: The account of the vote to be removed; this account must have voted for
/// referendum `index`.
/// - `index`: The index of referendum of the vote to be removed.
///
/// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on.
/// Weight is calculated for the maximum number of vote.
#[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(())
}
/// Permanently place a proposal into the blacklist. This prevents it from ever being
/// proposed again.
///
/// If called on a queued public or external proposal, then this will result in it being
/// removed. If the `ref_index` supplied is an active referendum with the proposal hash,
/// then it will be cancelled.
///
/// The dispatch origin of this call must be `BlacklistOrigin`.
///
/// - `proposal_hash`: The proposal hash to blacklist permanently.
/// - `ref_index`: An ongoing referendum whose hash is `proposal_hash`, which will be
/// cancelled.
///
/// Weight: `O(p)` (though as this is an high-privilege dispatch, we assume it has a
/// reasonable value).
#[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)?;
// Insert the proposal into the blacklist.
let permanent = (T::BlockNumber::max_value(), BoundedVec::<T::AccountId, _>::default());
Blacklist::<T>::insert(&proposal_hash, permanent);
// Remove the queued proposal, if it's there.
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));
}
});
// Remove the external queued referendum, if it's there.
if matches!(NextExternal::<T>::get(), Some((p, ..)) if p.hash() == proposal_hash) {
NextExternal::<T>::kill();
Self::clear_metadata(MetadataOwner::External);
}
// Remove the referendum, if it's there.
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(())
}
/// Remove a proposal.
///
/// The dispatch origin of this call must be `CancelProposalOrigin`.
///
/// - `prop_index`: The index of the proposal to cancel.
///
/// Weight: `O(p)` where `p = PublicProps::<T>::decode_len()`
#[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 });
Self::clear_metadata(MetadataOwner::Proposal(prop_index));
Ok(())
}
/// Set or clear a metadata of a proposal or a referendum.
///
/// Parameters:
/// - `origin`: Must correspond to the `MetadataOwner`.
/// - `ExternalOrigin` for an external proposal with the `SuperMajorityApprove`
/// threshold.
/// - `ExternalDefaultOrigin` for an external proposal with the `SuperMajorityAgainst`
/// threshold.
/// - `ExternalMajorityOrigin` for an external proposal with the `SimpleMajority`
/// threshold.
/// - `Signed` by a creator for a public proposal.
/// - `Signed` to clear a metadata for a finished referendum.
/// - `Root` to set a metadata for an ongoing referendum.
/// - `owner`: an identifier of a metadata owner.
/// - `maybe_hash`: The hash of an on-chain stored preimage. `None` to clear a metadata.
#[pallet::call_index(18)]
#[pallet::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<PreimageHash>,
) -> 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>(&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 {
// encoded self is too big to fit into a T. hash it and use the first bytes of that
// instead.
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> {
// exposed immutables.
/// Get the amount locked in support of `proposal`; `None` if proposal isn't a valid proposal
/// index.
pub fn backing_for(proposal: PropIndex) -> Option<BalanceOf<T>> {
Self::deposit_of(proposal).map(|(l, d)| d.saturating_mul((l.len() as u32).into()))
}
/// Get all referenda ready for tally at block `n`.
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()
}
// Exposed mutables.
/// Start a referendum.
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,
)
}
/// Remove a referendum.
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));
}
// private.
/// Ok if the given referendum is active, Err otherwise
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)
}
/// Actually enact a vote, if legit.
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) => {
// Shouldn't be possible to fail, but we handle it gracefully.
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 });
// Shouldn't be possible to fail, but we handle it gracefully.
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())
}
})?;
// Extend the lock to `balance` (rather than setting it) since we don't know what other
// votes are in place.
T::Currency::extend_lock(DEMOCRACY_ID, who, vote.balance(), WithdrawReasons::TRANSFER);
ReferendumInfoOf::<T>::insert(ref_index, ReferendumInfo::Ongoing(status));
Ok(())
}
/// Remove the account's vote for the given referendum if possible. This is possible when:
/// - The referendum has not finished.
/// - The referendum has finished and the voter lost their direction.
/// - The referendum has finished and the voter's lock period is up.
///
/// This will generally be combined with a call to `unlock`.
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);
// Shouldn't be possible to fail, but we handle it gracefully.
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 => {}, // Referendum was cancelled.
}
votes.remove(i);
}
Ok(())
})?;
Ok(())
}
/// Return the number of votes for `who`
fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations<BalanceOf<T>>) -> u32 {
VotingOf::<T>::mutate(who, |voting| match voting {
Voting::Delegating { delegations, .. } => {
// We don't support second level delegating, so we don't need to do anything more.
*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
},
})
}
/// Return the number of votes for `who`
fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations<BalanceOf<T>>) -> u32 {
VotingOf::<T>::mutate(who, |voting| match voting {
Voting::Delegating { delegations, .. } => {
// We don't support second level delegating, so we don't need to do anything more.
*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
},
})
}
/// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`.
///
/// Return the upstream number of votes.
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, ..
} => {
// remove any delegation votes to our current target.
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 } => {
// here we just ensure that we're currently idling with no votes recorded.
ensure!(votes.is_empty(), Error::<T>::VotesExist);
voting.set_common(delegations, prior);
},
}
let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance));
// Extend the lock to `balance` (rather than setting it) since we don't know what other
// votes are in place.
T::Currency::extend_lock(DEMOCRACY_ID, &who, balance, WithdrawReasons::TRANSFER);
Ok(votes)
})?;
Self::deposit_event(Event::<T>::Delegated { who, target });
Ok(votes)
}
/// Attempt to end the current delegation.
///
/// Return the number of votes of upstream.
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 } => {
// remove any delegation votes to our current target.
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)
}
/// Rejig the lock on an account. It will never get more stringent (since that would indicate
/// a security hole) but may be reduced from what they are currently.
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);
}
}
/// Start a referendum
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
}
/// Table the next waiting proposal for a vote.
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())
}
/// Table the waiting external proposal for a vote, if there is one.
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);
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())
}
}
/// Table the waiting public proposal with the highest backing for a vote.
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(
// defensive only: All current public proposals have an amount locked
|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) {
// refund depositors
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: 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 });
// Actually `hold` the proposal now since we didn't hold it when it came in via the
// submit extrinsic and we now know that it will be needed. This will be reversed by
// Scheduler pallet once it is executed which assumes that we will already have placed
// a `hold` on it.
T::Preimages::hold(&status.proposal);
// Earliest it can be scheduled for is next block.
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
}
/// Current era is ending; we should finish up any proposals.
///
///
/// ## Complexity:
/// If a referendum is launched or maturing, this will take full block weight if queue is not
/// empty. Otherwise, `O(R)` where `R` is the number of unbaked referenda.
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);
// pick out another public referendum if it's time.
if (now % T::LaunchPeriod::get()).is_zero() {
// Errors come from the queue being empty. If the queue is not empty, it will take
// full block weight.
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));
}
// tally up votes for any expiring referenda.
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;
}
// Notes:
// * We don't consider the lowest unbaked to be the last maturing in case some referenda
// have a longer voting period than others.
// * The iteration here shouldn't trigger any storage read that are not in cache, due to
// `maturing_referenda_at_inner` having already read them.
// * We shouldn't iterate more than `LaunchPeriod/VotingPeriod + 1` times because the number
// of unbaked referendum is bounded by this number. In case those number have changed in a
// runtime upgrade the formula should be adjusted but the bound should still be sensible.
<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
}
/// Reads the length of account in DepositOf without getting the complete value in the runtime.
///
/// Return 0 if no deposit for this proposal.
fn len_of_deposit_of(proposal: PropIndex) -> Option<u32> {
// DepositOf first tuple element is a vec, decoding its len is equivalent to decode a
// `Compact<u32>`.
decode_compact_u32_at(&<DepositOf<T>>::hashed_key_for(proposal))
}
/// Return a proposal of an index.
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)
}
/// Clear metadata if exist for a given owner.
fn clear_metadata(owner: MetadataOwner) {
if let Some(hash) = MetadataOf::<T>::take(&owner) {
Self::deposit_event(Event::<T>::MetadataCleared { owner, hash });
}
}
/// Transfer the metadata of an `owner` to a `new_owner`.
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,
});
}
}
/// Ensure external origin for corresponding vote threshold.
fn ensure_external_origin(
threshold: VoteThreshold,
origin: OriginFor<T>,
) -> Result<(), BadOrigin> {
match threshold {
VoteThreshold::SuperMajorityApprove => {
let _ = T::ExternalOrigin::ensure_origin(origin)?;
},
VoteThreshold::SuperMajorityAgainst => {
let _ = T::ExternalDefaultOrigin::ensure_origin(origin)?;
},
VoteThreshold::SimpleMajority => {
let _ = T::ExternalMajorityOrigin::ensure_origin(origin)?;
},
};
Ok(())
}
}
/// Decode `Compact<u32>` from the trie at given key.
fn decode_compact_u32_at(key: &[u8]) -> Option<u32> {
// `Compact<u32>` takes at most 5 bytes.
let mut buf = [0u8; 5];
let bytes = sp_io::storage::read(key, &mut buf, 0)?;
// The value may be smaller than 5 bytes.
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
},
}
}