mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Referenda and Conviction Voting pallets (#10195)
* Initial draft of new referendum state machine. * Docs * Fixes * Fixes * Add conviction-voting pallet * Basic build * Building * Some TODOs * Tests building * Add missing file * Basic lifecycle test * Add couple of tests * Another test * More tests * Fixes * Fixes * Formatting * Fixes * Tests * Fixes * Fixes * More tests * Formatting * First few benchmarks * First few benchmarks * Defered queue servicing * More testing * Benchmarks * Fiddly benchmark * Final nudge benchmarks * Formatting * Formatting * Finished up benchmarks * cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_referenda --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/referenda/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Events finished * Missing file * No GenesisConfig for Referenda * Formatting * Docs * Docs * Docs * Per-class conviction voting * New test & mock utils * More tests * Tests * Tests finished 🎉 * Benchmarking stuff * Fixes * Test harness * Test harness * Benchmarks for Conviction=Voting * Benchmarking pipeline complete * Docs * Formatting * Remove unneeded warning * Fix UI tests * cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_conviction_voting --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/conviction-voting/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Docs * Update frame/conviction-voting/src/vote.rs Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> * update sp-runtime version * MEL Fixes for Referenda and Conviction Voting (#10725) * free maxencodedlen * more maxencodedlen * more MEL * more mel * disable storage info * More Referenda Patches (#10760) * basic fixes * fix benchmarking * fix license * prevent panic in curve math * fmt * bump crate versions * Update mock.rs Co-authored-by: Parity Bot <admin@parity.io> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Generated
+41
@@ -4871,6 +4871,7 @@ dependencies = [
|
||||
"pallet-contracts",
|
||||
"pallet-contracts-primitives",
|
||||
"pallet-contracts-rpc-runtime-api",
|
||||
"pallet-conviction-voting",
|
||||
"pallet-democracy",
|
||||
"pallet-election-provider-multi-phase",
|
||||
"pallet-elections-phragmen",
|
||||
@@ -4889,6 +4890,7 @@ dependencies = [
|
||||
"pallet-proxy",
|
||||
"pallet-randomness-collective-flip",
|
||||
"pallet-recovery",
|
||||
"pallet-referenda",
|
||||
"pallet-scheduler",
|
||||
"pallet-session",
|
||||
"pallet-session-benchmarking",
|
||||
@@ -5672,6 +5674,25 @@ dependencies = [
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-conviction-voting"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"pallet-balances",
|
||||
"pallet-scheduler",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serde",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-democracy"
|
||||
version = "4.0.0-dev"
|
||||
@@ -6122,6 +6143,26 @@ dependencies = [
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-referenda"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"pallet-balances",
|
||||
"pallet-preimage",
|
||||
"pallet-scheduler",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serde",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-scheduler"
|
||||
version = "4.0.0-dev"
|
||||
|
||||
@@ -67,6 +67,9 @@ members = [
|
||||
"frame/authority-discovery",
|
||||
"frame/authorship",
|
||||
"frame/babe",
|
||||
"frame/bags-list",
|
||||
"frame/bags-list/fuzzer",
|
||||
"frame/bags-list/remote-tests",
|
||||
"frame/balances",
|
||||
"frame/beefy",
|
||||
"frame/beefy-mmr",
|
||||
@@ -78,6 +81,7 @@ members = [
|
||||
"frame/contracts",
|
||||
"frame/contracts/rpc",
|
||||
"frame/contracts/rpc/runtime-api",
|
||||
"frame/conviction-voting",
|
||||
"frame/democracy",
|
||||
"frame/try-runtime",
|
||||
"frame/election-provider-multi-phase",
|
||||
@@ -104,6 +108,7 @@ members = [
|
||||
"frame/proxy",
|
||||
"frame/randomness-collective-flip",
|
||||
"frame/recovery",
|
||||
"frame/referenda",
|
||||
"frame/scheduler",
|
||||
"frame/scored-pool",
|
||||
"frame/session",
|
||||
@@ -133,9 +138,6 @@ members = [
|
||||
"frame/uniques",
|
||||
"frame/utility",
|
||||
"frame/vesting",
|
||||
"frame/bags-list",
|
||||
"frame/bags-list/remote-tests",
|
||||
"frame/bags-list/fuzzer",
|
||||
"primitives/api",
|
||||
"primitives/api/proc-macro",
|
||||
"primitives/api/test",
|
||||
|
||||
@@ -64,6 +64,7 @@ pallet-collective = { version = "4.0.0-dev", default-features = false, path = ".
|
||||
pallet-contracts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/contracts" }
|
||||
pallet-contracts-primitives = { version = "5.0.0", default-features = false, path = "../../../frame/contracts/common/" }
|
||||
pallet-contracts-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/contracts/rpc/runtime-api/" }
|
||||
pallet-conviction-voting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/conviction-voting" }
|
||||
pallet-democracy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/democracy" }
|
||||
pallet-election-provider-multi-phase = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-multi-phase" }
|
||||
pallet-elections-phragmen = { version = "5.0.0-dev", default-features = false, path = "../../../frame/elections-phragmen" }
|
||||
@@ -82,9 +83,8 @@ pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../
|
||||
pallet-proxy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/proxy" }
|
||||
pallet-randomness-collective-flip = { version = "4.0.0-dev", default-features = false, path = "../../../frame/randomness-collective-flip" }
|
||||
pallet-recovery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/recovery" }
|
||||
pallet-session = { version = "4.0.0-dev", features = [
|
||||
"historical",
|
||||
], path = "../../../frame/session", default-features = false }
|
||||
pallet-referenda = { version = "4.0.0-dev", default-features = false, path = "../../../frame/referenda" }
|
||||
pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = "../../../frame/session", default-features = false }
|
||||
pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true }
|
||||
pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" }
|
||||
pallet-staking-reward-curve = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/reward-curve" }
|
||||
@@ -125,6 +125,7 @@ std = [
|
||||
"pallet-contracts/std",
|
||||
"pallet-contracts-primitives/std",
|
||||
"pallet-contracts-rpc-runtime-api/std",
|
||||
"pallet-conviction-voting/std",
|
||||
"pallet-democracy/std",
|
||||
"pallet-elections-phragmen/std",
|
||||
"frame-executive/std",
|
||||
@@ -170,6 +171,7 @@ std = [
|
||||
"pallet-utility/std",
|
||||
"sp-version/std",
|
||||
"pallet-society/std",
|
||||
"pallet-referenda/std",
|
||||
"pallet-recovery/std",
|
||||
"pallet-uniques/std",
|
||||
"pallet-vesting/std",
|
||||
@@ -183,7 +185,6 @@ runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-election-provider-multi-phase/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"pallet-assets/runtime-benchmarks",
|
||||
"pallet-babe/runtime-benchmarks",
|
||||
@@ -193,7 +194,9 @@ runtime-benchmarks = [
|
||||
"pallet-child-bounties/runtime-benchmarks",
|
||||
"pallet-collective/runtime-benchmarks",
|
||||
"pallet-contracts/runtime-benchmarks",
|
||||
"pallet-conviction-voting/runtime-benchmarks",
|
||||
"pallet-democracy/runtime-benchmarks",
|
||||
"pallet-election-provider-multi-phase/runtime-benchmarks",
|
||||
"pallet-elections-phragmen/runtime-benchmarks",
|
||||
"pallet-gilt/runtime-benchmarks",
|
||||
"pallet-grandpa/runtime-benchmarks",
|
||||
@@ -204,9 +207,12 @@ runtime-benchmarks = [
|
||||
"pallet-membership/runtime-benchmarks",
|
||||
"pallet-mmr/runtime-benchmarks",
|
||||
"pallet-multisig/runtime-benchmarks",
|
||||
"pallet-offences-benchmarking",
|
||||
"pallet-preimage/runtime-benchmarks",
|
||||
"pallet-proxy/runtime-benchmarks",
|
||||
"pallet-scheduler/runtime-benchmarks",
|
||||
"pallet-referenda/runtime-benchmarks",
|
||||
"pallet-session-benchmarking",
|
||||
"pallet-society/runtime-benchmarks",
|
||||
"pallet-staking/runtime-benchmarks",
|
||||
"pallet-timestamp/runtime-benchmarks",
|
||||
@@ -216,8 +222,6 @@ runtime-benchmarks = [
|
||||
"pallet-utility/runtime-benchmarks",
|
||||
"pallet-uniques/runtime-benchmarks",
|
||||
"pallet-vesting/runtime-benchmarks",
|
||||
"pallet-offences-benchmarking",
|
||||
"pallet-session-benchmarking",
|
||||
"frame-system-benchmarking",
|
||||
"hex-literal",
|
||||
]
|
||||
@@ -234,35 +238,37 @@ try-runtime = [
|
||||
"pallet-child-bounties/try-runtime",
|
||||
"pallet-collective/try-runtime",
|
||||
"pallet-contracts/try-runtime",
|
||||
"pallet-conviction-voting/try-runtime",
|
||||
"pallet-democracy/try-runtime",
|
||||
"pallet-election-provider-multi-phase/try-runtime",
|
||||
"pallet-elections-phragmen/try-runtime",
|
||||
"pallet-gilt/try-runtime",
|
||||
"pallet-grandpa/try-runtime",
|
||||
"pallet-identity/try-runtime",
|
||||
"pallet-im-online/try-runtime",
|
||||
"pallet-indices/try-runtime",
|
||||
"pallet-lottery/try-runtime",
|
||||
"pallet-membership/try-runtime",
|
||||
"pallet-mmr/try-runtime",
|
||||
"pallet-multisig/try-runtime",
|
||||
"pallet-identity/try-runtime",
|
||||
"pallet-scheduler/try-runtime",
|
||||
"pallet-offences/try-runtime",
|
||||
"pallet-preimage/try-runtime",
|
||||
"pallet-proxy/try-runtime",
|
||||
"pallet-randomness-collective-flip/try-runtime",
|
||||
"pallet-recovery/try-runtime",
|
||||
"pallet-referenda/try-runtime",
|
||||
"pallet-scheduler/try-runtime",
|
||||
"pallet-session/try-runtime",
|
||||
"pallet-society/try-runtime",
|
||||
"pallet-staking/try-runtime",
|
||||
"pallet-sudo/try-runtime",
|
||||
"pallet-election-provider-multi-phase/try-runtime",
|
||||
"pallet-timestamp/try-runtime",
|
||||
"pallet-tips/try-runtime",
|
||||
"pallet-transaction-payment/try-runtime",
|
||||
"pallet-treasury/try-runtime",
|
||||
"pallet-utility/try-runtime",
|
||||
"pallet-society/try-runtime",
|
||||
"pallet-recovery/try-runtime",
|
||||
"pallet-uniques/try-runtime",
|
||||
"pallet-utility/try-runtime",
|
||||
"pallet-vesting/try-runtime",
|
||||
"pallet-gilt/try-runtime",
|
||||
]
|
||||
# Make contract callable functions marked as __unstable__ available. Do not enable
|
||||
# on live chains as those are subject to change.
|
||||
|
||||
@@ -698,6 +698,83 @@ impl pallet_bags_list::Config for Runtime {
|
||||
type BagThresholds = BagThresholds;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const VoteLockingPeriod: BlockNumber = 30 * DAYS;
|
||||
}
|
||||
|
||||
impl pallet_conviction_voting::Config for Runtime {
|
||||
type WeightInfo = pallet_conviction_voting::weights::SubstrateWeight<Self>;
|
||||
type Event = Event;
|
||||
type Currency = Balances;
|
||||
type VoteLockingPeriod = VoteLockingPeriod;
|
||||
type MaxVotes = ConstU32<512>;
|
||||
type MaxTurnout = frame_support::traits::TotalIssuanceOf<Balances, Self::AccountId>;
|
||||
type Polls = Referenda;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const AlarmInterval: BlockNumber = 1;
|
||||
pub const SubmissionDeposit: Balance = 100 * DOLLARS;
|
||||
pub const UndecidingTimeout: BlockNumber = 28 * DAYS;
|
||||
}
|
||||
|
||||
pub struct TracksInfo;
|
||||
impl pallet_referenda::TracksInfo<Balance, BlockNumber> for TracksInfo {
|
||||
type Id = u8;
|
||||
type Origin = <Origin as frame_support::traits::OriginTrait>::PalletsOrigin;
|
||||
fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo<Balance, BlockNumber>)] {
|
||||
static DATA: [(u8, pallet_referenda::TrackInfo<Balance, BlockNumber>); 1] = [(
|
||||
0u8,
|
||||
pallet_referenda::TrackInfo {
|
||||
name: "root",
|
||||
max_deciding: 1,
|
||||
decision_deposit: 10,
|
||||
prepare_period: 4,
|
||||
decision_period: 4,
|
||||
confirm_period: 2,
|
||||
min_enactment_period: 4,
|
||||
min_approval: pallet_referenda::Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(100),
|
||||
delta: Perbill::from_percent(50),
|
||||
},
|
||||
min_turnout: pallet_referenda::Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(100),
|
||||
delta: Perbill::from_percent(100),
|
||||
},
|
||||
},
|
||||
)];
|
||||
&DATA[..]
|
||||
}
|
||||
fn track_for(id: &Self::Origin) -> Result<Self::Id, ()> {
|
||||
if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) {
|
||||
match system_origin {
|
||||
frame_system::RawOrigin::Root => Ok(0),
|
||||
_ => Err(()),
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_referenda::Config for Runtime {
|
||||
type WeightInfo = pallet_referenda::weights::SubstrateWeight<Self>;
|
||||
type Call = Call;
|
||||
type Event = Event;
|
||||
type Scheduler = Scheduler;
|
||||
type Currency = pallet_balances::Pallet<Self>;
|
||||
type CancelOrigin = EnsureRoot<AccountId>;
|
||||
type KillOrigin = EnsureRoot<AccountId>;
|
||||
type Slash = ();
|
||||
type Votes = pallet_conviction_voting::VotesOf<Runtime>;
|
||||
type Tally = pallet_conviction_voting::TallyOf<Runtime>;
|
||||
type SubmissionDeposit = SubmissionDeposit;
|
||||
type MaxQueued = ConstU32<100>;
|
||||
type UndecidingTimeout = UndecidingTimeout;
|
||||
type AlarmInterval = AlarmInterval;
|
||||
type Tracks = TracksInfo;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES;
|
||||
pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES;
|
||||
@@ -1336,6 +1413,8 @@ construct_runtime!(
|
||||
TransactionStorage: pallet_transaction_storage,
|
||||
BagsList: pallet_bags_list,
|
||||
ChildBounties: pallet_child_bounties,
|
||||
Referenda: pallet_referenda,
|
||||
ConvictionVoting: pallet_conviction_voting,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1405,6 +1484,7 @@ mod benches {
|
||||
[pallet_bounties, Bounties]
|
||||
[pallet_child_bounties, ChildBounties]
|
||||
[pallet_collective, Council]
|
||||
[pallet_conviction_voting, ConvictionVoting]
|
||||
[pallet_contracts, Contracts]
|
||||
[pallet_democracy, Democracy]
|
||||
[pallet_election_provider_multi_phase, ElectionProviderMultiPhase]
|
||||
@@ -1421,6 +1501,7 @@ mod benches {
|
||||
[pallet_offences, OffencesBench::<Runtime>]
|
||||
[pallet_preimage, Preimage]
|
||||
[pallet_proxy, Proxy]
|
||||
[pallet_referenda, Referenda]
|
||||
[pallet_scheduler, Scheduler]
|
||||
[pallet_session, SessionBench::<Runtime>]
|
||||
[pallet_staking, Staking]
|
||||
|
||||
@@ -1517,7 +1517,7 @@ where
|
||||
.map_err(|_| Error::<T, I>::LiquidityRestrictions)?;
|
||||
|
||||
// TODO: This is over-conservative. There may now be other providers, and
|
||||
// this pallet may not even be a provider.
|
||||
// this pallet may not even be a provider.
|
||||
let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
|
||||
let allow_death =
|
||||
allow_death && system::Pallet::<T>::can_dec_provider(transactor);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
[package]
|
||||
name = "pallet-conviction-voting"
|
||||
version = "4.0.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME pallet for conviction voting in referenda"
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.126", optional = true, features = ["derive"] }
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [
|
||||
"derive",
|
||||
] }
|
||||
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" }
|
||||
sp-io = { version = "5.0.0", default-features = false, path = "../../primitives/io" }
|
||||
sp-runtime = { version = "5.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true }
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
|
||||
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
|
||||
assert_matches = "1.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
sp-core = { version = "5.0.0", path = "../../primitives/core" }
|
||||
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
|
||||
pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"sp-std/std",
|
||||
"sp-io/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"sp-runtime/std",
|
||||
"frame-system/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
@@ -0,0 +1,8 @@
|
||||
# Voting Pallet
|
||||
|
||||
- [`assembly::Config`](https://docs.rs/pallet-assembly/latest/pallet_assembly/trait.Config.html)
|
||||
- [`Call`](https://docs.rs/pallet-assembly/latest/pallet_assembly/enum.Call.html)
|
||||
|
||||
## Overview
|
||||
|
||||
Pallet for voting in referenda.
|
||||
@@ -0,0 +1,278 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020-2021 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.
|
||||
|
||||
//! ConvictionVoting pallet benchmarking.
|
||||
|
||||
use super::*;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use frame_benchmarking::{account, benchmarks, whitelist_account};
|
||||
use frame_support::{
|
||||
dispatch::RawOrigin,
|
||||
traits::{fungible, Currency, Get},
|
||||
};
|
||||
use sp_runtime::traits::Bounded;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
|
||||
use crate::Pallet as ConvictionVoting;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
/// Fill all classes as much as possible up to `MaxVotes` and return the Class with the most votes
|
||||
/// ongoing.
|
||||
fn fill_voting<T: Config>() -> (ClassOf<T>, BTreeMap<ClassOf<T>, Vec<IndexOf<T>>>) {
|
||||
let mut r = BTreeMap::<ClassOf<T>, Vec<IndexOf<T>>>::new();
|
||||
for class in T::Polls::classes().into_iter() {
|
||||
for _ in 0..T::MaxVotes::get() {
|
||||
match T::Polls::create_ongoing(class.clone()) {
|
||||
Ok(i) => r.entry(class.clone()).or_default().push(i),
|
||||
Err(()) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
let c = r.iter().max_by_key(|(_, ref v)| v.len()).unwrap().0.clone();
|
||||
(c, r)
|
||||
}
|
||||
|
||||
fn funded_account<T: Config>(name: &'static str, index: u32) -> T::AccountId {
|
||||
let caller: T::AccountId = account(name, index, SEED);
|
||||
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
|
||||
caller
|
||||
}
|
||||
|
||||
fn account_vote<T: Config>(b: BalanceOf<T>) -> AccountVote<BalanceOf<T>> {
|
||||
let v = Vote { aye: true, conviction: Conviction::Locked1x };
|
||||
|
||||
AccountVote::Standard { vote: v, balance: b }
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
where_clause { where T::MaxVotes: core::fmt::Debug }
|
||||
|
||||
vote_new {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
let account_vote = account_vote::<T>(100u32.into());
|
||||
|
||||
let (class, all_polls) = fill_voting::<T>();
|
||||
let polls = &all_polls[&class];
|
||||
let r = polls.len() - 1;
|
||||
// We need to create existing votes
|
||||
for i in polls.iter().skip(1) {
|
||||
ConvictionVoting::<T>::vote(RawOrigin::Signed(caller.clone()).into(), *i, account_vote.clone())?;
|
||||
}
|
||||
let votes = match VotingFor::<T>::get(&caller, &class) {
|
||||
Voting::Casting(Casting { votes, .. }) => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(votes.len(), r as usize, "Votes were not recorded.");
|
||||
|
||||
let index = polls[0];
|
||||
}: vote(RawOrigin::Signed(caller.clone()), index, account_vote)
|
||||
verify {
|
||||
assert_matches!(
|
||||
VotingFor::<T>::get(&caller, &class),
|
||||
Voting::Casting(Casting { votes, .. }) if votes.len() == (r + 1) as usize
|
||||
);
|
||||
}
|
||||
|
||||
vote_existing {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
let old_account_vote = account_vote::<T>(100u32.into());
|
||||
|
||||
let (class, all_polls) = fill_voting::<T>();
|
||||
let polls = &all_polls[&class];
|
||||
let r = polls.len();
|
||||
// We need to create existing votes
|
||||
for i in polls.iter() {
|
||||
ConvictionVoting::<T>::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote.clone())?;
|
||||
}
|
||||
let votes = match VotingFor::<T>::get(&caller, &class) {
|
||||
Voting::Casting(Casting { votes, .. }) => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(votes.len(), r, "Votes were not recorded.");
|
||||
|
||||
let new_account_vote = account_vote::<T>(200u32.into());
|
||||
let index = polls[0];
|
||||
}: vote(RawOrigin::Signed(caller.clone()), index, new_account_vote)
|
||||
verify {
|
||||
assert_matches!(
|
||||
VotingFor::<T>::get(&caller, &class),
|
||||
Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize
|
||||
);
|
||||
}
|
||||
|
||||
remove_vote {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
let old_account_vote = account_vote::<T>(100u32.into());
|
||||
|
||||
let (class, all_polls) = fill_voting::<T>();
|
||||
let polls = &all_polls[&class];
|
||||
let r = polls.len();
|
||||
// We need to create existing votes
|
||||
for i in polls.iter() {
|
||||
ConvictionVoting::<T>::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote.clone())?;
|
||||
}
|
||||
let votes = match VotingFor::<T>::get(&caller, &class) {
|
||||
Voting::Casting(Casting { votes, .. }) => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(votes.len(), r, "Votes were not recorded.");
|
||||
|
||||
let index = polls[0];
|
||||
}: _(RawOrigin::Signed(caller.clone()), Some(class.clone()), index)
|
||||
verify {
|
||||
assert_matches!(
|
||||
VotingFor::<T>::get(&caller, &class),
|
||||
Voting::Casting(Casting { votes, .. }) if votes.len() == (r - 1) as usize
|
||||
);
|
||||
}
|
||||
|
||||
remove_other_vote {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
let voter = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
let old_account_vote = account_vote::<T>(100u32.into());
|
||||
|
||||
let (class, all_polls) = fill_voting::<T>();
|
||||
let polls = &all_polls[&class];
|
||||
let r = polls.len();
|
||||
// We need to create existing votes
|
||||
for i in polls.iter() {
|
||||
ConvictionVoting::<T>::vote(RawOrigin::Signed(voter.clone()).into(), *i, old_account_vote.clone())?;
|
||||
}
|
||||
let votes = match VotingFor::<T>::get(&caller, &class) {
|
||||
Voting::Casting(Casting { votes, .. }) => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(votes.len(), r, "Votes were not recorded.");
|
||||
|
||||
let index = polls[0];
|
||||
assert!(T::Polls::end_ongoing(index, false).is_ok());
|
||||
}: _(RawOrigin::Signed(caller.clone()), voter.clone(), class.clone(), index)
|
||||
verify {
|
||||
assert_matches!(
|
||||
VotingFor::<T>::get(&voter, &class),
|
||||
Voting::Casting(Casting { votes, .. }) if votes.len() == (r - 1) as usize
|
||||
);
|
||||
}
|
||||
|
||||
delegate {
|
||||
let r in 0 .. T::MaxVotes::get().min(T::Polls::max_ongoing().1);
|
||||
|
||||
let all_polls = fill_voting::<T>().1;
|
||||
let class = T::Polls::max_ongoing().0;
|
||||
let polls = &all_polls[&class];
|
||||
let voter = funded_account::<T>("voter", 0);
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
|
||||
let delegated_balance: BalanceOf<T> = 1000u32.into();
|
||||
let delegate_vote = account_vote::<T>(delegated_balance);
|
||||
|
||||
// We need to create existing delegations
|
||||
for i in polls.iter().take(r as usize) {
|
||||
ConvictionVoting::<T>::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote.clone())?;
|
||||
}
|
||||
assert_matches!(
|
||||
VotingFor::<T>::get(&voter, &class),
|
||||
Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize
|
||||
);
|
||||
|
||||
}: _(RawOrigin::Signed(caller.clone()), class.clone(), voter.clone(), Conviction::Locked1x, delegated_balance)
|
||||
verify {
|
||||
assert_matches!(VotingFor::<T>::get(&caller, &class), Voting::Delegating(_));
|
||||
}
|
||||
|
||||
undelegate {
|
||||
let r in 0 .. T::MaxVotes::get().min(T::Polls::max_ongoing().1);
|
||||
|
||||
let all_polls = fill_voting::<T>().1;
|
||||
let class = T::Polls::max_ongoing().0;
|
||||
let polls = &all_polls[&class];
|
||||
let voter = funded_account::<T>("voter", 0);
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
|
||||
let delegated_balance: BalanceOf<T> = 1000u32.into();
|
||||
let delegate_vote = account_vote::<T>(delegated_balance);
|
||||
|
||||
ConvictionVoting::<T>::delegate(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
class.clone(),
|
||||
voter.clone(),
|
||||
Conviction::Locked1x,
|
||||
delegated_balance,
|
||||
)?;
|
||||
|
||||
// We need to create delegations
|
||||
for i in polls.iter().take(r as usize) {
|
||||
ConvictionVoting::<T>::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote.clone())?;
|
||||
}
|
||||
assert_matches!(
|
||||
VotingFor::<T>::get(&voter, &class),
|
||||
Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize
|
||||
);
|
||||
assert_matches!(VotingFor::<T>::get(&caller, &class), Voting::Delegating(_));
|
||||
}: _(RawOrigin::Signed(caller.clone()), class.clone())
|
||||
verify {
|
||||
assert_matches!(VotingFor::<T>::get(&caller, &class), Voting::Casting(_));
|
||||
}
|
||||
|
||||
unlock {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
let normal_account_vote = account_vote::<T>(T::Currency::free_balance(&caller) - 100u32.into());
|
||||
let big_account_vote = account_vote::<T>(T::Currency::free_balance(&caller));
|
||||
|
||||
// Fill everything up to the max by filling all classes with votes and voting on them all.
|
||||
let (class, all_polls) = fill_voting::<T>();
|
||||
assert!(all_polls.len() > 0);
|
||||
for (class, polls) in all_polls.iter() {
|
||||
assert!(polls.len() > 0);
|
||||
for i in polls.iter() {
|
||||
ConvictionVoting::<T>::vote(RawOrigin::Signed(caller.clone()).into(), *i, normal_account_vote.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
let orig_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, false);
|
||||
let polls = &all_polls[&class];
|
||||
|
||||
// Vote big on the class with the most ongoing votes of them to bump the lock and make it
|
||||
// hard to recompute when removed.
|
||||
ConvictionVoting::<T>::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote.clone())?;
|
||||
let now_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, false);
|
||||
assert_eq!(orig_usable - now_usable, 100u32.into());
|
||||
|
||||
// Remove the vote
|
||||
ConvictionVoting::<T>::remove_vote(RawOrigin::Signed(caller.clone()).into(), Some(class.clone()), polls[0])?;
|
||||
|
||||
// We can now unlock on `class` from 200 to 100...
|
||||
}: _(RawOrigin::Signed(caller.clone()), class, caller.clone())
|
||||
verify {
|
||||
assert_eq!(orig_usable, <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, false));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
ConvictionVoting,
|
||||
crate::tests::new_test_ext(),
|
||||
crate::tests::Test
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The conviction datatype.
|
||||
|
||||
use crate::types::Delegations;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, CheckedDiv, CheckedMul, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::{convert::TryFrom, result::Result};
|
||||
|
||||
/// A value denoting the strength of conviction of a vote.
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub enum Conviction {
|
||||
/// 0.1x votes, unlocked.
|
||||
None,
|
||||
/// 1x votes, locked for an enactment period following a successful vote.
|
||||
Locked1x,
|
||||
/// 2x votes, locked for 2x enactment periods following a successful vote.
|
||||
Locked2x,
|
||||
/// 3x votes, locked for 4x...
|
||||
Locked3x,
|
||||
/// 4x votes, locked for 8x...
|
||||
Locked4x,
|
||||
/// 5x votes, locked for 16x...
|
||||
Locked5x,
|
||||
/// 6x votes, locked for 32x...
|
||||
Locked6x,
|
||||
}
|
||||
|
||||
impl Default for Conviction {
|
||||
fn default() -> Self {
|
||||
Conviction::None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Conviction> for u8 {
|
||||
fn from(c: Conviction) -> u8 {
|
||||
match c {
|
||||
Conviction::None => 0,
|
||||
Conviction::Locked1x => 1,
|
||||
Conviction::Locked2x => 2,
|
||||
Conviction::Locked3x => 3,
|
||||
Conviction::Locked4x => 4,
|
||||
Conviction::Locked5x => 5,
|
||||
Conviction::Locked6x => 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Conviction {
|
||||
type Error = ();
|
||||
fn try_from(i: u8) -> Result<Conviction, ()> {
|
||||
Ok(match i {
|
||||
0 => Conviction::None,
|
||||
1 => Conviction::Locked1x,
|
||||
2 => Conviction::Locked2x,
|
||||
3 => Conviction::Locked3x,
|
||||
4 => Conviction::Locked4x,
|
||||
5 => Conviction::Locked5x,
|
||||
6 => Conviction::Locked6x,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Conviction {
|
||||
/// The amount of time (in number of periods) that our conviction implies a successful voter's
|
||||
/// balance should be locked for.
|
||||
pub fn lock_periods(self) -> u32 {
|
||||
match self {
|
||||
Conviction::None => 0,
|
||||
Conviction::Locked1x => 1,
|
||||
Conviction::Locked2x => 2,
|
||||
Conviction::Locked3x => 4,
|
||||
Conviction::Locked4x => 8,
|
||||
Conviction::Locked5x => 16,
|
||||
Conviction::Locked6x => 32,
|
||||
}
|
||||
}
|
||||
|
||||
/// The votes of a voter of the given `balance` with our conviction.
|
||||
pub fn votes<B: From<u8> + Zero + Copy + CheckedMul + CheckedDiv + Bounded>(
|
||||
self,
|
||||
capital: B,
|
||||
) -> Delegations<B> {
|
||||
let votes = match self {
|
||||
Conviction::None => capital.checked_div(&10u8.into()).unwrap_or_else(Zero::zero),
|
||||
x => capital.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value),
|
||||
};
|
||||
Delegations { votes, capital }
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded for Conviction {
|
||||
fn min_value() -> Self {
|
||||
Conviction::None
|
||||
}
|
||||
fn max_value() -> Self {
|
||||
Conviction::Locked6x
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,632 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 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.
|
||||
|
||||
//! # Voting Pallet
|
||||
//!
|
||||
//! - [`Config`]
|
||||
//! - [`Call`]
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! Pallet for managing actual voting in polls.
|
||||
|
||||
#![recursion_limit = "256"]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use frame_support::{
|
||||
ensure,
|
||||
traits::{
|
||||
fungible, Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling,
|
||||
ReservableCurrency, WithdrawReasons,
|
||||
},
|
||||
};
|
||||
use sp_runtime::{
|
||||
traits::{AtLeast32BitUnsigned, Saturating, Zero},
|
||||
ArithmeticError, DispatchError, DispatchResult, Perbill,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
mod conviction;
|
||||
mod types;
|
||||
mod vote;
|
||||
pub mod weights;
|
||||
pub use conviction::Conviction;
|
||||
pub use pallet::*;
|
||||
pub use types::{Delegations, Tally, UnvoteScope};
|
||||
pub use vote::{AccountVote, Casting, Delegating, Vote, Voting};
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmarking;
|
||||
|
||||
const CONVICTION_VOTING_ID: LockIdentifier = *b"pyconvot";
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type VotingOf<T> = Voting<
|
||||
BalanceOf<T>,
|
||||
<T as frame_system::Config>::AccountId,
|
||||
<T as frame_system::Config>::BlockNumber,
|
||||
PollIndexOf<T>,
|
||||
<T as Config>::MaxVotes,
|
||||
>;
|
||||
#[allow(dead_code)]
|
||||
type DelegatingOf<T> = Delegating<
|
||||
BalanceOf<T>,
|
||||
<T as frame_system::Config>::AccountId,
|
||||
<T as frame_system::Config>::BlockNumber,
|
||||
>;
|
||||
pub type TallyOf<T> = Tally<BalanceOf<T>, <T as Config>::MaxTurnout>;
|
||||
pub type VotesOf<T> = BalanceOf<T>;
|
||||
type PollIndexOf<T> = <<T as Config>::Polls as Polling<TallyOf<T>>>::Index;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type IndexOf<T> = <<T as Config>::Polls as Polling<TallyOf<T>>>::Index;
|
||||
type ClassOf<T> = <<T as Config>::Polls as Polling<TallyOf<T>>>::Class;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use sp_runtime::DispatchResult;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + Sized {
|
||||
// System level stuff.
|
||||
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
/// Currency type with which voting happens.
|
||||
type Currency: ReservableCurrency<Self::AccountId>
|
||||
+ LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>
|
||||
+ fungible::Inspect<Self::AccountId>;
|
||||
|
||||
/// The implementation of the logic which conducts polls.
|
||||
type Polls: Polling<TallyOf<Self>, Votes = BalanceOf<Self>, Moment = Self::BlockNumber>;
|
||||
|
||||
/// The maximum amount of tokens which may be used for voting. May just be
|
||||
/// `Currency::total_issuance`, but you might want to reduce this in order to account for
|
||||
/// funds in the system which are unable to vote (e.g. parachain auction deposits).
|
||||
type MaxTurnout: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The maximum number of concurrent votes an account may have.
|
||||
///
|
||||
/// Also used to compute weight, an overly large value can
|
||||
/// lead to extrinsic with large weight estimation: see `delegate` for instance.
|
||||
#[pallet::constant]
|
||||
type MaxVotes: Get<u32>;
|
||||
|
||||
/// 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>;
|
||||
}
|
||||
|
||||
/// All voting for a particular voter in a particular voting class. We store the balance for the
|
||||
/// number of votes that we have recorded.
|
||||
#[pallet::storage]
|
||||
pub type VotingFor<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
T::AccountId,
|
||||
Twox64Concat,
|
||||
ClassOf<T>,
|
||||
VotingOf<T>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
/// The voting classes which have a non-zero lock requirement and the lock amounts which they
|
||||
/// require. The actual amount locked on behalf of this pallet should always be the maximum of
|
||||
/// this list.
|
||||
#[pallet::storage]
|
||||
pub type ClassLocksFor<T: Config> =
|
||||
StorageMap<_, Twox64Concat, T::AccountId, Vec<(ClassOf<T>, BalanceOf<T>)>, ValueQuery>;
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// An account has delegated their vote to another account. \[who, target\]
|
||||
Delegated(T::AccountId, T::AccountId),
|
||||
/// An \[account\] has cancelled a previous delegation operation.
|
||||
Undelegated(T::AccountId),
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Poll is not ongoing.
|
||||
NotOngoing,
|
||||
/// The given account did not vote on the poll.
|
||||
NotVoter,
|
||||
/// The actor has no permission to conduct the action.
|
||||
NoPermission,
|
||||
/// The actor has no permission to conduct the action right now but will do in the future.
|
||||
NoPermissionYet,
|
||||
/// The account is already delegating.
|
||||
AlreadyDelegating,
|
||||
/// The account currently has votes attached to it and the operation cannot succeed until
|
||||
/// these are removed, either through `unvote` or `reap_vote`.
|
||||
AlreadyVoting,
|
||||
/// Too high a balance was provided that the account cannot afford.
|
||||
InsufficientFunds,
|
||||
/// The account is not currently delegating.
|
||||
NotDelegating,
|
||||
/// Delegation to oneself makes no sense.
|
||||
Nonsense,
|
||||
/// Maximum number of votes reached.
|
||||
MaxVotesReached,
|
||||
/// The class must be supplied since it is not easily determinable from the state.
|
||||
ClassNeeded,
|
||||
/// The class ID supplied is invalid.
|
||||
BadClass,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Vote in a poll. 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_.
|
||||
///
|
||||
/// - `poll_index`: The index of the poll to vote for.
|
||||
/// - `vote`: The vote configuration.
|
||||
///
|
||||
/// Weight: `O(R)` where R is the number of polls the voter has voted on.
|
||||
#[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))]
|
||||
pub fn vote(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] poll_index: PollIndexOf<T>,
|
||||
vote: AccountVote<BalanceOf<T>>,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
Self::try_vote(&who, poll_index, vote)
|
||||
}
|
||||
|
||||
/// Delegate the voting power (with some given conviction) of the sending account for a
|
||||
/// particular class of polls.
|
||||
///
|
||||
/// 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.
|
||||
/// - `class`: The class of polls to delegate. To delegate multiple classes, multiple calls
|
||||
/// to this function are required.
|
||||
/// - `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 polls the voter delegating to has
|
||||
/// voted on. Weight is initially charged as if maximum votes, but is refunded later.
|
||||
// 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::weight(T::WeightInfo::delegate(T::MaxVotes::get()))]
|
||||
pub fn delegate(
|
||||
origin: OriginFor<T>,
|
||||
class: ClassOf<T>,
|
||||
to: T::AccountId,
|
||||
conviction: Conviction,
|
||||
balance: BalanceOf<T>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
let votes = Self::try_delegate(who, class, to, conviction, balance)?;
|
||||
|
||||
Ok(Some(T::WeightInfo::delegate(votes)).into())
|
||||
}
|
||||
|
||||
/// Undelegate the voting power of the sending account for a particular class of polls.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// - `class`: The class of polls to remove the delegation from.
|
||||
///
|
||||
/// Emits `Undelegated`.
|
||||
///
|
||||
/// Weight: `O(R)` where R is the number of polls the voter delegating to has
|
||||
/// voted on. Weight is initially charged as if maximum votes, but is refunded later.
|
||||
// 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::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))]
|
||||
pub fn undelegate(origin: OriginFor<T>, class: ClassOf<T>) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
let votes = Self::try_undelegate(who, class)?;
|
||||
Ok(Some(T::WeightInfo::undelegate(votes)).into())
|
||||
}
|
||||
|
||||
/// Remove the lock caused prior voting/delegating which has expired within a particluar
|
||||
/// class.
|
||||
///
|
||||
/// The dispatch origin of this call must be _Signed_.
|
||||
///
|
||||
/// - `class`: The class of polls to unlock.
|
||||
/// - `target`: The account to remove the lock on.
|
||||
///
|
||||
/// Weight: `O(R)` with R number of vote of target.
|
||||
#[pallet::weight(T::WeightInfo::unlock())]
|
||||
pub fn unlock(
|
||||
origin: OriginFor<T>,
|
||||
class: ClassOf<T>,
|
||||
target: T::AccountId,
|
||||
) -> DispatchResult {
|
||||
ensure_signed(origin)?;
|
||||
Self::update_lock(&class, &target);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a vote for a poll.
|
||||
///
|
||||
/// If:
|
||||
/// - the poll was cancelled, or
|
||||
/// - the poll is ongoing, or
|
||||
/// - the poll 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 poll 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 poll `index`.
|
||||
///
|
||||
/// - `index`: The index of poll of the vote to be removed.
|
||||
/// - `class`: Optional parameter, if given it indicates the class of the poll. For polls
|
||||
/// which have finished or are cancelled, this must be `Some`.
|
||||
///
|
||||
/// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on.
|
||||
/// Weight is calculated for the maximum number of vote.
|
||||
#[pallet::weight(T::WeightInfo::remove_vote())]
|
||||
pub fn remove_vote(
|
||||
origin: OriginFor<T>,
|
||||
class: Option<ClassOf<T>>,
|
||||
index: PollIndexOf<T>,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
Self::try_remove_vote(&who, index, class, UnvoteScope::Any)
|
||||
}
|
||||
|
||||
/// Remove a vote for a poll.
|
||||
///
|
||||
/// 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 poll was cancelled, because the voter lost the poll 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 poll
|
||||
/// `index`.
|
||||
/// - `index`: The index of poll of the vote to be removed.
|
||||
/// - `class`: The class of the poll.
|
||||
///
|
||||
/// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on.
|
||||
/// Weight is calculated for the maximum number of vote.
|
||||
#[pallet::weight(T::WeightInfo::remove_other_vote())]
|
||||
pub fn remove_other_vote(
|
||||
origin: OriginFor<T>,
|
||||
target: T::AccountId,
|
||||
class: ClassOf<T>,
|
||||
index: PollIndexOf<T>,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired };
|
||||
Self::try_remove_vote(&target, index, Some(class), scope)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Actually enact a vote, if legit.
|
||||
fn try_vote(
|
||||
who: &T::AccountId,
|
||||
poll_index: PollIndexOf<T>,
|
||||
vote: AccountVote<BalanceOf<T>>,
|
||||
) -> DispatchResult {
|
||||
ensure!(vote.balance() <= T::Currency::free_balance(who), Error::<T>::InsufficientFunds);
|
||||
T::Polls::try_access_poll(poll_index, |poll_status| {
|
||||
let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::<T>::NotOngoing)?;
|
||||
VotingFor::<T>::try_mutate(who, &class, |voting| {
|
||||
if let Voting::Casting(Casting { ref mut votes, delegations, .. }) = voting {
|
||||
match votes.binary_search_by_key(&poll_index, |i| i.0) {
|
||||
Ok(i) => {
|
||||
// Shouldn't be possible to fail, but we handle it gracefully.
|
||||
tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?;
|
||||
if let Some(approve) = votes[i].1.as_standard() {
|
||||
tally.reduce(approve, *delegations);
|
||||
}
|
||||
votes[i].1 = vote;
|
||||
},
|
||||
Err(i) => {
|
||||
votes
|
||||
.try_insert(i, (poll_index, vote))
|
||||
.map_err(|()| Error::<T>::MaxVotesReached)?;
|
||||
},
|
||||
}
|
||||
// Shouldn't be possible to fail, but we handle it gracefully.
|
||||
tally.add(vote).ok_or(ArithmeticError::Overflow)?;
|
||||
if let Some(approve) = vote.as_standard() {
|
||||
tally.increase(approve, *delegations);
|
||||
}
|
||||
} else {
|
||||
return 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.
|
||||
Self::extend_lock(who, &class, vote.balance());
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove the account's vote for the given poll if possible. This is possible when:
|
||||
/// - The poll has not finished.
|
||||
/// - The poll has finished and the voter lost their direction.
|
||||
/// - The poll 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,
|
||||
poll_index: PollIndexOf<T>,
|
||||
class_hint: Option<ClassOf<T>>,
|
||||
scope: UnvoteScope,
|
||||
) -> DispatchResult {
|
||||
let class = class_hint
|
||||
.or_else(|| Some(T::Polls::as_ongoing(poll_index)?.1))
|
||||
.ok_or(Error::<T>::ClassNeeded)?;
|
||||
VotingFor::<T>::try_mutate(who, class, |voting| {
|
||||
if let Voting::Casting(Casting { ref mut votes, delegations, ref mut prior }) = voting {
|
||||
let i = votes
|
||||
.binary_search_by_key(&poll_index, |i| i.0)
|
||||
.map_err(|_| Error::<T>::NotVoter)?;
|
||||
let v = votes.remove(i);
|
||||
|
||||
T::Polls::try_access_poll(poll_index, |poll_status| match poll_status {
|
||||
PollStatus::Ongoing(tally, _) => {
|
||||
ensure!(matches!(scope, UnvoteScope::Any), Error::<T>::NoPermission);
|
||||
// Shouldn't be possible to fail, but we handle it gracefully.
|
||||
tally.remove(v.1).ok_or(ArithmeticError::Underflow)?;
|
||||
if let Some(approve) = v.1.as_standard() {
|
||||
tally.reduce(approve, *delegations);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
PollStatus::Completed(end, approved) => {
|
||||
if let Some((lock_periods, balance)) = v.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>::NoPermissionYet
|
||||
);
|
||||
prior.accumulate(unlock_at, balance)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
PollStatus::None => Ok(()), // Poll was cancelled.
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the number of votes for `who`
|
||||
fn increase_upstream_delegation(
|
||||
who: &T::AccountId,
|
||||
class: &ClassOf<T>,
|
||||
amount: Delegations<BalanceOf<T>>,
|
||||
) -> u32 {
|
||||
VotingFor::<T>::mutate(who, class, |voting| match voting {
|
||||
Voting::Delegating(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::Casting(Casting { votes, delegations, .. }) => {
|
||||
*delegations = delegations.saturating_add(amount);
|
||||
for &(poll_index, account_vote) in votes.iter() {
|
||||
if let AccountVote::Standard { vote, .. } = account_vote {
|
||||
T::Polls::access_poll(poll_index, |poll_status| {
|
||||
if let PollStatus::Ongoing(tally, _) = poll_status {
|
||||
tally.increase(vote.aye, amount);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
votes.len() as u32
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the number of votes for `who`
|
||||
fn reduce_upstream_delegation(
|
||||
who: &T::AccountId,
|
||||
class: &ClassOf<T>,
|
||||
amount: Delegations<BalanceOf<T>>,
|
||||
) -> u32 {
|
||||
VotingFor::<T>::mutate(who, class, |voting| match voting {
|
||||
Voting::Delegating(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::Casting(Casting { votes, delegations, .. }) => {
|
||||
*delegations = delegations.saturating_sub(amount);
|
||||
for &(poll_index, account_vote) in votes.iter() {
|
||||
if let AccountVote::Standard { vote, .. } = account_vote {
|
||||
T::Polls::access_poll(poll_index, |poll_status| {
|
||||
if let PollStatus::Ongoing(tally, _) = poll_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,
|
||||
class: ClassOf<T>,
|
||||
target: T::AccountId,
|
||||
conviction: Conviction,
|
||||
balance: BalanceOf<T>,
|
||||
) -> Result<u32, DispatchError> {
|
||||
ensure!(who != target, Error::<T>::Nonsense);
|
||||
T::Polls::classes().binary_search(&class).map_err(|_| Error::<T>::BadClass)?;
|
||||
ensure!(balance <= T::Currency::free_balance(&who), Error::<T>::InsufficientFunds);
|
||||
let votes =
|
||||
VotingFor::<T>::try_mutate(&who, &class, |voting| -> Result<u32, DispatchError> {
|
||||
let old = sp_std::mem::replace(
|
||||
voting,
|
||||
Voting::Delegating(Delegating {
|
||||
balance,
|
||||
target: target.clone(),
|
||||
conviction,
|
||||
delegations: Default::default(),
|
||||
prior: Default::default(),
|
||||
}),
|
||||
);
|
||||
match old {
|
||||
Voting::Delegating(Delegating { .. }) => Err(Error::<T>::AlreadyDelegating)?,
|
||||
Voting::Casting(Casting { votes, delegations, prior }) => {
|
||||
// here we just ensure that we're currently idling with no votes recorded.
|
||||
ensure!(votes.is_empty(), Error::<T>::AlreadyVoting);
|
||||
voting.set_common(delegations, prior);
|
||||
},
|
||||
}
|
||||
|
||||
let votes =
|
||||
Self::increase_upstream_delegation(&target, &class, conviction.votes(balance));
|
||||
// Extend the lock to `balance` (rather than setting it) since we don't know what
|
||||
// other votes are in place.
|
||||
Self::extend_lock(&who, &class, balance);
|
||||
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, class: ClassOf<T>) -> Result<u32, DispatchError> {
|
||||
let votes =
|
||||
VotingFor::<T>::try_mutate(&who, &class, |voting| -> Result<u32, DispatchError> {
|
||||
match sp_std::mem::replace(voting, Voting::default()) {
|
||||
Voting::Delegating(Delegating {
|
||||
balance,
|
||||
target,
|
||||
conviction,
|
||||
delegations,
|
||||
mut prior,
|
||||
}) => {
|
||||
// remove any delegation votes to our current target.
|
||||
let votes = Self::reduce_upstream_delegation(
|
||||
&target,
|
||||
&class,
|
||||
conviction.votes(balance),
|
||||
);
|
||||
let now = frame_system::Pallet::<T>::block_number();
|
||||
let lock_periods = conviction.lock_periods().into();
|
||||
prior.accumulate(
|
||||
now.saturating_add(
|
||||
T::VoteLockingPeriod::get().saturating_mul(lock_periods),
|
||||
),
|
||||
balance,
|
||||
);
|
||||
voting.set_common(delegations, prior);
|
||||
|
||||
Ok(votes)
|
||||
},
|
||||
Voting::Casting(_) => Err(Error::<T>::NotDelegating.into()),
|
||||
}
|
||||
})?;
|
||||
Self::deposit_event(Event::<T>::Undelegated(who));
|
||||
Ok(votes)
|
||||
}
|
||||
|
||||
fn extend_lock(who: &T::AccountId, class: &ClassOf<T>, amount: BalanceOf<T>) {
|
||||
ClassLocksFor::<T>::mutate(who, |locks| match locks.iter().position(|x| &x.0 == class) {
|
||||
Some(i) => locks[i].1 = locks[i].1.max(amount),
|
||||
None => locks.push((class.clone(), amount)),
|
||||
});
|
||||
T::Currency::extend_lock(CONVICTION_VOTING_ID, who, amount, WithdrawReasons::TRANSFER);
|
||||
}
|
||||
|
||||
/// 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(class: &ClassOf<T>, who: &T::AccountId) {
|
||||
let class_lock_needed = VotingFor::<T>::mutate(who, class, |voting| {
|
||||
voting.rejig(frame_system::Pallet::<T>::block_number());
|
||||
voting.locked_balance()
|
||||
});
|
||||
let lock_needed = ClassLocksFor::<T>::mutate(who, |locks| {
|
||||
locks.retain(|x| &x.0 != class);
|
||||
if !class_lock_needed.is_zero() {
|
||||
locks.push((class.clone(), class_lock_needed));
|
||||
}
|
||||
locks.iter().map(|x| x.1).max().unwrap_or(Zero::zero())
|
||||
});
|
||||
if lock_needed.is_zero() {
|
||||
T::Currency::remove_lock(CONVICTION_VOTING_ID, who);
|
||||
} else {
|
||||
T::Currency::set_lock(
|
||||
CONVICTION_VOTING_ID,
|
||||
who,
|
||||
lock_needed,
|
||||
WithdrawReasons::TRANSFER,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,820 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The crate's tests.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::*;
|
||||
use crate as pallet_conviction_voting;
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok, parameter_types,
|
||||
traits::{ConstU32, ConstU64, Contains, Polling},
|
||||
};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
Voting: pallet_conviction_voting::{Pallet, Call, Storage, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
// Test that a fitlered call can be dispatched.
|
||||
pub struct BaseFilter;
|
||||
impl Contains<Call> for BaseFilter {
|
||||
fn contains(call: &Call) -> bool {
|
||||
!matches!(call, &Call::Balances(pallet_balances::Call::set_balance { .. }))
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::simple_max(1_000_000);
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = BaseFilter;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = Call;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = Event;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Test {
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type MaxLocks = ConstU32<10>;
|
||||
type Balance = u64;
|
||||
type Event = Event;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum TestPollState {
|
||||
Ongoing(TallyOf<Test>, u8),
|
||||
Completed(u64, bool),
|
||||
}
|
||||
use TestPollState::*;
|
||||
|
||||
parameter_types! {
|
||||
pub static Polls: BTreeMap<u8, TestPollState> = vec![
|
||||
(1, Completed(1, true)),
|
||||
(2, Completed(2, false)),
|
||||
(3, Ongoing(Tally::from_parts(0, 0, 0), 0)),
|
||||
].into_iter().collect();
|
||||
}
|
||||
|
||||
pub struct TestPolls;
|
||||
impl Polling<TallyOf<Test>> for TestPolls {
|
||||
type Index = u8;
|
||||
type Votes = u64;
|
||||
type Moment = u64;
|
||||
type Class = u8;
|
||||
fn classes() -> Vec<u8> {
|
||||
vec![0, 1, 2]
|
||||
}
|
||||
fn as_ongoing(index: u8) -> Option<(TallyOf<Test>, Self::Class)> {
|
||||
Polls::get().remove(&index).and_then(|x| {
|
||||
if let TestPollState::Ongoing(t, c) = x {
|
||||
Some((t, c))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
fn access_poll<R>(
|
||||
index: Self::Index,
|
||||
f: impl FnOnce(PollStatus<&mut TallyOf<Test>, u64, u8>) -> R,
|
||||
) -> R {
|
||||
let mut polls = Polls::get();
|
||||
let entry = polls.get_mut(&index);
|
||||
let r = match entry {
|
||||
Some(Ongoing(ref mut tally_mut_ref, class)) =>
|
||||
f(PollStatus::Ongoing(tally_mut_ref, *class)),
|
||||
Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)),
|
||||
None => f(PollStatus::None),
|
||||
};
|
||||
Polls::set(polls);
|
||||
r
|
||||
}
|
||||
fn try_access_poll<R>(
|
||||
index: Self::Index,
|
||||
f: impl FnOnce(PollStatus<&mut TallyOf<Test>, u64, u8>) -> Result<R, DispatchError>,
|
||||
) -> Result<R, DispatchError> {
|
||||
let mut polls = Polls::get();
|
||||
let entry = polls.get_mut(&index);
|
||||
let r = match entry {
|
||||
Some(Ongoing(ref mut tally_mut_ref, class)) =>
|
||||
f(PollStatus::Ongoing(tally_mut_ref, *class)),
|
||||
Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)),
|
||||
None => f(PollStatus::None),
|
||||
}?;
|
||||
Polls::set(polls);
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn create_ongoing(class: Self::Class) -> Result<Self::Index, ()> {
|
||||
let mut polls = Polls::get();
|
||||
let i = polls.keys().rev().next().map_or(0, |x| x + 1);
|
||||
polls.insert(i, Ongoing(Tally::default(), class));
|
||||
Polls::set(polls);
|
||||
Ok(i)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> {
|
||||
let mut polls = Polls::get();
|
||||
match polls.get(&index) {
|
||||
Some(Ongoing(..)) => {},
|
||||
_ => return Err(()),
|
||||
}
|
||||
let now = frame_system::Pallet::<Test>::block_number();
|
||||
polls.insert(index, Completed(now, approved));
|
||||
Polls::set(polls);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type Event = Event;
|
||||
type Currency = pallet_balances::Pallet<Self>;
|
||||
type VoteLockingPeriod = ConstU64<3>;
|
||||
type MaxVotes = ConstU32<3>;
|
||||
type WeightInfo = ();
|
||||
type MaxTurnout = frame_support::traits::TotalIssuanceOf<Balances, Self::AccountId>;
|
||||
type Polls = TestPolls;
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn params_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Balances::free_balance(42), 0);
|
||||
assert_eq!(Balances::total_issuance(), 210);
|
||||
});
|
||||
}
|
||||
|
||||
fn next_block() {
|
||||
System::set_block_number(System::block_number() + 1);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn run_to(n: u64) {
|
||||
while System::block_number() < n {
|
||||
next_block();
|
||||
}
|
||||
}
|
||||
|
||||
fn aye(amount: u64, conviction: u8) -> AccountVote<u64> {
|
||||
let vote = Vote { aye: true, conviction: conviction.try_into().unwrap() };
|
||||
AccountVote::Standard { vote, balance: amount }
|
||||
}
|
||||
|
||||
fn nay(amount: u64, conviction: u8) -> AccountVote<u64> {
|
||||
let vote = Vote { aye: false, conviction: conviction.try_into().unwrap() };
|
||||
AccountVote::Standard { vote, balance: amount }
|
||||
}
|
||||
|
||||
fn tally(index: u8) -> TallyOf<Test> {
|
||||
<TestPolls as Polling<TallyOf<Test>>>::as_ongoing(index).expect("No poll").0
|
||||
}
|
||||
|
||||
fn class(index: u8) -> u8 {
|
||||
<TestPolls as Polling<TallyOf<Test>>>::as_ongoing(index).expect("No poll").1
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[should_panic(expected = "No poll")]
|
||||
fn unknown_poll_should_panic() {
|
||||
let _ = tally(0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[should_panic(expected = "No poll")]
|
||||
fn completed_poll_should_panic() {
|
||||
let _ = tally(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_stuff() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 0, 0));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_voting_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(2, 5)));
|
||||
assert_eq!(tally(3), Tally::from_parts(10, 0, 2));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(2, 5)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 10, 2));
|
||||
assert_eq!(Balances::usable_balance(1), 8);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(5, 1)));
|
||||
assert_eq!(tally(3), Tally::from_parts(5, 0, 5));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(5, 1)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 5, 5));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(10, 0)));
|
||||
assert_eq!(tally(3), Tally::from_parts(1, 0, 10));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(10, 0)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 1, 10));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), None, 3));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 0, 0));
|
||||
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), class(3), 1));
|
||||
assert_eq!(Balances::usable_balance(1), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voting_balance_gets_locked() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(2, 5)));
|
||||
assert_eq!(tally(3), Tally::from_parts(10, 0, 2));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(2, 5)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 10, 2));
|
||||
assert_eq!(Balances::usable_balance(1), 8);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(5, 1)));
|
||||
assert_eq!(tally(3), Tally::from_parts(5, 0, 5));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(5, 1)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 5, 5));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(10, 0)));
|
||||
assert_eq!(tally(3), Tally::from_parts(1, 0, 10));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(10, 0)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 1, 10));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), None, 3));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 0, 0));
|
||||
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), class(3), 1));
|
||||
assert_eq!(Balances::usable_balance(1), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_but_zero_conviction_vote_balance_can_be_unlocked() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(1, 1)));
|
||||
assert_ok!(Voting::vote(Origin::signed(2), 3, nay(20, 0)));
|
||||
let c = class(3);
|
||||
Polls::set(vec![(3, Completed(3, false))].into_iter().collect());
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(2), Some(c), 3));
|
||||
assert_ok!(Voting::unlock(Origin::signed(2), c, 2));
|
||||
assert_eq!(Balances::usable_balance(2), 20);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsuccessful_conviction_vote_balance_can_be_unlocked() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(1, 1)));
|
||||
assert_ok!(Voting::vote(Origin::signed(2), 3, nay(20, 0)));
|
||||
let c = class(3);
|
||||
Polls::set(vec![(3, Completed(3, false))].into_iter().collect());
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(c), 3));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), c, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_conviction_vote_balance_stays_locked_for_correct_time() {
|
||||
new_test_ext().execute_with(|| {
|
||||
for i in 1..=5 {
|
||||
assert_ok!(Voting::vote(Origin::signed(i), 3, aye(10, i as u8)));
|
||||
}
|
||||
let c = class(3);
|
||||
Polls::set(vec![(3, Completed(3, true))].into_iter().collect());
|
||||
for i in 1..=5 {
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(i), Some(c), 3));
|
||||
}
|
||||
for block in 1..=(3 + 5 * 3) {
|
||||
run_to(block);
|
||||
for i in 1..=5 {
|
||||
assert_ok!(Voting::unlock(Origin::signed(i), c, i));
|
||||
let expired = block >= (3 << (i - 1)) + 3;
|
||||
assert_eq!(Balances::usable_balance(i), i * 10 - if expired { 0 } else { 10 });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classwise_delegation_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(
|
||||
vec![
|
||||
(0, Ongoing(Tally::default(), 0)),
|
||||
(1, Ongoing(Tally::default(), 1)),
|
||||
(2, Ongoing(Tally::default(), 2)),
|
||||
(3, Ongoing(Tally::default(), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 5));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 1, 3, Conviction::Locked1x, 5));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 2, 4, Conviction::Locked1x, 5));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(2), 0, aye(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(2), 1, nay(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(2), 2, nay(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(3), 0, nay(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(3), 1, aye(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(3), 2, nay(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(4), 0, nay(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(4), 1, nay(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(4), 2, aye(10, 0)));
|
||||
// 4 hasn't voted yet
|
||||
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(6, 2, 35), 0)),
|
||||
(1, Ongoing(Tally::from_parts(6, 2, 35), 1)),
|
||||
(2, Ongoing(Tally::from_parts(6, 2, 35), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 0, 0), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
|
||||
// 4 votes nay to 3.
|
||||
assert_ok!(Voting::vote(Origin::signed(4), 3, nay(10, 0)));
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(6, 2, 35), 0)),
|
||||
(1, Ongoing(Tally::from_parts(6, 2, 35), 1)),
|
||||
(2, Ongoing(Tally::from_parts(6, 2, 35), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 6, 15), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
|
||||
// Redelegate for class 2 to account 3.
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 2));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 2, 3, Conviction::Locked1x, 5));
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(6, 2, 35), 0)),
|
||||
(1, Ongoing(Tally::from_parts(6, 2, 35), 1)),
|
||||
(2, Ongoing(Tally::from_parts(1, 7, 35), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 1, 10), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
|
||||
// Redelegating with a lower lock does not forget previous lock and updates correctly.
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 1));
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 2));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 3));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 1, 3, Conviction::Locked1x, 3));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 2, 4, Conviction::Locked1x, 3));
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(4, 2, 33), 0)),
|
||||
(1, Ongoing(Tally::from_parts(4, 2, 33), 1)),
|
||||
(2, Ongoing(Tally::from_parts(4, 2, 33), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 4, 13), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 1, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 2, 1));
|
||||
// unlock does nothing since the delegation already took place.
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
// Redelegating with higher amount extends previous lock.
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 6));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 4);
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 1));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 1, 3, Conviction::Locked1x, 7));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 1, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 3);
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 2));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 2, 4, Conviction::Locked1x, 8));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 2, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 2);
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(7, 2, 36), 0)),
|
||||
(1, Ongoing(Tally::from_parts(8, 2, 37), 1)),
|
||||
(2, Ongoing(Tally::from_parts(9, 2, 38), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 9, 18), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redelegation_after_vote_ending_should_keep_lock() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(vec![(0, Ongoing(Tally::default(), 0))].into_iter().collect());
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 5));
|
||||
assert_ok!(Voting::vote(Origin::signed(2), 0, aye(10, 1)));
|
||||
Polls::set(vec![(0, Completed(1, true))].into_iter().collect());
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 3, Conviction::Locked1x, 3));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_amalgamation_valid_with_multiple_removed_votes() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(
|
||||
vec![
|
||||
(0, Ongoing(Tally::default(), 0)),
|
||||
(1, Ongoing(Tally::default(), 0)),
|
||||
(2, Ongoing(Tally::default(), 0)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 0, aye(5, 1)));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 1, aye(10, 1)));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 2, aye(5, 2)));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
Polls::set(
|
||||
vec![(0, Completed(1, true)), (1, Completed(1, true)), (2, Completed(1, true))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 0));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 2));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
run_to(3);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
run_to(6);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert!(Balances::usable_balance(1) <= 5);
|
||||
|
||||
run_to(7);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_amalgamation_valid_with_multiple_delegations() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 5));
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 10));
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked2x, 5));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
|
||||
run_to(3);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
run_to(6);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert!(Balances::usable_balance(1) <= 5);
|
||||
|
||||
run_to(7);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_amalgamation_valid_with_move_roundtrip_to_delegation() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(vec![(0, Ongoing(Tally::default(), 0))].into_iter().collect());
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 0, aye(5, 1)));
|
||||
Polls::set(vec![(0, Completed(1, true))].into_iter().collect());
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 0));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 10));
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
Polls::set(vec![(1, Ongoing(Tally::default(), 0))].into_iter().collect());
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 1, aye(5, 2)));
|
||||
Polls::set(vec![(1, Completed(1, true))].into_iter().collect());
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 1));
|
||||
|
||||
run_to(3);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
run_to(6);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert!(Balances::usable_balance(1) <= 5);
|
||||
|
||||
run_to(7);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_amalgamation_valid_with_move_roundtrip_to_casting() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 5));
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
Polls::set(vec![(0, Ongoing(Tally::default(), 0))].into_iter().collect());
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 0, aye(10, 1)));
|
||||
Polls::set(vec![(0, Completed(1, true))].into_iter().collect());
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 0));
|
||||
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked2x, 10));
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
|
||||
run_to(3);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
run_to(6);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert!(Balances::usable_balance(1) <= 5);
|
||||
|
||||
run_to(7);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_aggregation_over_different_classes_with_delegation_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 5));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 1, 2, Conviction::Locked2x, 5));
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 2, 2, Conviction::Locked1x, 10));
|
||||
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 1));
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 2));
|
||||
|
||||
run_to(3);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 1, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 2, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
run_to(6);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 1, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 2, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
run_to(7);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 1, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 2, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_aggregation_over_different_classes_with_casting_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(
|
||||
vec![
|
||||
(0, Ongoing(Tally::default(), 0)),
|
||||
(1, Ongoing(Tally::default(), 1)),
|
||||
(2, Ongoing(Tally::default(), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 0, aye(5, 1)));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 1, aye(10, 1)));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 2, aye(5, 2)));
|
||||
Polls::set(
|
||||
vec![(0, Completed(1, true)), (1, Completed(1, true)), (2, Completed(1, true))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 0));
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(1), 1));
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(2), 2));
|
||||
|
||||
run_to(3);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 1, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 2, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
run_to(6);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 1, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 2, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
run_to(7);
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 1, 1));
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 2, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_with_vote_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(Voting::vote(Origin::signed(1), 0, aye(10, 0)), Error::<Test>::NotOngoing);
|
||||
assert_noop!(Voting::vote(Origin::signed(1), 1, aye(10, 0)), Error::<Test>::NotOngoing);
|
||||
assert_noop!(Voting::vote(Origin::signed(1), 2, aye(10, 0)), Error::<Test>::NotOngoing);
|
||||
assert_noop!(
|
||||
Voting::vote(Origin::signed(1), 3, aye(11, 0)),
|
||||
Error::<Test>::InsufficientFunds
|
||||
);
|
||||
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::None, 10));
|
||||
assert_noop!(
|
||||
Voting::vote(Origin::signed(1), 3, aye(10, 0)),
|
||||
Error::<Test>::AlreadyDelegating
|
||||
);
|
||||
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
Polls::set(
|
||||
vec![
|
||||
(0, Ongoing(Tally::default(), 0)),
|
||||
(1, Ongoing(Tally::default(), 0)),
|
||||
(2, Ongoing(Tally::default(), 0)),
|
||||
(3, Ongoing(Tally::default(), 0)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 0, aye(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 1, aye(10, 0)));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 2, aye(10, 0)));
|
||||
assert_noop!(
|
||||
Voting::vote(Origin::signed(1), 3, aye(10, 0)),
|
||||
Error::<Test>::MaxVotesReached
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_with_delegating_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
Voting::delegate(Origin::signed(1), 0, 2, Conviction::None, 11),
|
||||
Error::<Test>::InsufficientFunds
|
||||
);
|
||||
assert_noop!(
|
||||
Voting::delegate(Origin::signed(1), 3, 2, Conviction::None, 10),
|
||||
Error::<Test>::BadClass
|
||||
);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(10, 0)));
|
||||
assert_noop!(
|
||||
Voting::delegate(Origin::signed(1), 0, 2, Conviction::None, 10),
|
||||
Error::<Test>::AlreadyVoting
|
||||
);
|
||||
|
||||
assert_noop!(Voting::undelegate(Origin::signed(1), 0), Error::<Test>::NotDelegating);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_other_vote_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
Voting::remove_other_vote(Origin::signed(2), 1, 0, 3),
|
||||
Error::<Test>::NotVoter
|
||||
);
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(10, 2)));
|
||||
assert_noop!(
|
||||
Voting::remove_other_vote(Origin::signed(2), 1, 0, 3),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
Polls::set(vec![(3, Completed(1, true))].into_iter().collect());
|
||||
run_to(6);
|
||||
assert_noop!(
|
||||
Voting::remove_other_vote(Origin::signed(2), 1, 0, 3),
|
||||
Error::<Test>::NoPermissionYet
|
||||
);
|
||||
run_to(7);
|
||||
assert_ok!(Voting::remove_other_vote(Origin::signed(2), 1, 0, 3));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_with_remove_vote_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(Voting::remove_vote(Origin::signed(1), Some(0), 3), Error::<Test>::NotVoter);
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(10, 2)));
|
||||
Polls::set(vec![(3, Completed(1, true))].into_iter().collect());
|
||||
assert_noop!(Voting::remove_vote(Origin::signed(1), None, 3), Error::<Test>::ClassNeeded);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 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.
|
||||
|
||||
//! Miscellaneous additional datatypes.
|
||||
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
use super::*;
|
||||
use crate::{AccountVote, Conviction, Vote};
|
||||
use codec::{Codec, Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
traits::VoteTally, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound,
|
||||
RuntimeDebugNoBound,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
|
||||
/// Info regarding an ongoing referendum.
|
||||
#[derive(
|
||||
CloneNoBound,
|
||||
DefaultNoBound,
|
||||
PartialEqNoBound,
|
||||
EqNoBound,
|
||||
RuntimeDebugNoBound,
|
||||
TypeInfo,
|
||||
Encode,
|
||||
Decode,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
#[scale_info(skip_type_params(Total))]
|
||||
pub struct Tally<
|
||||
Votes: Clone + Default + PartialEq + Eq + sp_std::fmt::Debug + TypeInfo + Codec,
|
||||
Total,
|
||||
> {
|
||||
/// The number of aye votes, expressed in terms of post-conviction lock-vote.
|
||||
pub ayes: Votes,
|
||||
/// The number of nay votes, expressed in terms of post-conviction lock-vote.
|
||||
pub nays: Votes,
|
||||
/// The amount of funds currently expressing its opinion. Pre-conviction.
|
||||
pub turnout: Votes,
|
||||
/// Dummy.
|
||||
dummy: PhantomData<Total>,
|
||||
}
|
||||
|
||||
impl<
|
||||
Votes: Clone
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ sp_std::fmt::Debug
|
||||
+ Copy
|
||||
+ AtLeast32BitUnsigned
|
||||
+ TypeInfo
|
||||
+ Codec,
|
||||
Total: Get<Votes>,
|
||||
> VoteTally<Votes> for Tally<Votes, Total>
|
||||
{
|
||||
fn ayes(&self) -> Votes {
|
||||
self.ayes
|
||||
}
|
||||
|
||||
fn turnout(&self) -> Perbill {
|
||||
Perbill::from_rational(self.turnout, Total::get())
|
||||
}
|
||||
|
||||
fn approval(&self) -> Perbill {
|
||||
Perbill::from_rational(self.ayes, self.ayes.saturating_add(self.nays))
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn unanimity() -> Self {
|
||||
Self { ayes: Total::get(), nays: Zero::zero(), turnout: Total::get(), dummy: PhantomData }
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn from_requirements(turnout: Perbill, approval: Perbill) -> Self {
|
||||
let turnout = turnout.mul_ceil(Total::get());
|
||||
let ayes = approval.mul_ceil(turnout);
|
||||
Self { ayes, nays: turnout - ayes, turnout, dummy: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Votes: Clone
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ sp_std::fmt::Debug
|
||||
+ Copy
|
||||
+ AtLeast32BitUnsigned
|
||||
+ TypeInfo
|
||||
+ Codec,
|
||||
Total: Get<Votes>,
|
||||
> Tally<Votes, Total>
|
||||
{
|
||||
/// Create a new tally.
|
||||
pub fn new(vote: Vote, balance: Votes) -> Self {
|
||||
let Delegations { votes, capital } = vote.conviction.votes(balance);
|
||||
Self {
|
||||
ayes: if vote.aye { votes } else { Zero::zero() },
|
||||
nays: if vote.aye { Zero::zero() } else { votes },
|
||||
turnout: capital,
|
||||
dummy: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_parts(ayes: Votes, nays: Votes, turnout: Votes) -> Self {
|
||||
Self { ayes, nays, turnout, dummy: PhantomData }
|
||||
}
|
||||
|
||||
/// Add an account's vote into the tally.
|
||||
pub fn add(&mut self, vote: AccountVote<Votes>) -> Option<()> {
|
||||
match vote {
|
||||
AccountVote::Standard { vote, balance } => {
|
||||
let Delegations { votes, capital } = vote.conviction.votes(balance);
|
||||
self.turnout = self.turnout.checked_add(&capital)?;
|
||||
match vote.aye {
|
||||
true => self.ayes = self.ayes.checked_add(&votes)?,
|
||||
false => self.nays = self.nays.checked_add(&votes)?,
|
||||
}
|
||||
},
|
||||
AccountVote::Split { aye, nay } => {
|
||||
let aye = Conviction::None.votes(aye);
|
||||
let nay = Conviction::None.votes(nay);
|
||||
self.turnout = self.turnout.checked_add(&aye.capital)?.checked_add(&nay.capital)?;
|
||||
self.ayes = self.ayes.checked_add(&aye.votes)?;
|
||||
self.nays = self.nays.checked_add(&nay.votes)?;
|
||||
},
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Remove an account's vote from the tally.
|
||||
pub fn remove(&mut self, vote: AccountVote<Votes>) -> Option<()> {
|
||||
match vote {
|
||||
AccountVote::Standard { vote, balance } => {
|
||||
let Delegations { votes, capital } = vote.conviction.votes(balance);
|
||||
self.turnout = self.turnout.checked_sub(&capital)?;
|
||||
match vote.aye {
|
||||
true => self.ayes = self.ayes.checked_sub(&votes)?,
|
||||
false => self.nays = self.nays.checked_sub(&votes)?,
|
||||
}
|
||||
},
|
||||
AccountVote::Split { aye, nay } => {
|
||||
let aye = Conviction::None.votes(aye);
|
||||
let nay = Conviction::None.votes(nay);
|
||||
self.turnout = self.turnout.checked_sub(&aye.capital)?.checked_sub(&nay.capital)?;
|
||||
self.ayes = self.ayes.checked_sub(&aye.votes)?;
|
||||
self.nays = self.nays.checked_sub(&nay.votes)?;
|
||||
},
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Increment some amount of votes.
|
||||
pub fn increase(&mut self, approve: bool, delegations: Delegations<Votes>) {
|
||||
self.turnout = self.turnout.saturating_add(delegations.capital);
|
||||
match approve {
|
||||
true => self.ayes = self.ayes.saturating_add(delegations.votes),
|
||||
false => self.nays = self.nays.saturating_add(delegations.votes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrement some amount of votes.
|
||||
pub fn reduce(&mut self, approve: bool, delegations: Delegations<Votes>) {
|
||||
self.turnout = self.turnout.saturating_sub(delegations.capital);
|
||||
match approve {
|
||||
true => self.ayes = self.ayes.saturating_sub(delegations.votes),
|
||||
false => self.nays = self.nays.saturating_sub(delegations.votes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Amount of votes and capital placed in delegation for an account.
|
||||
#[derive(
|
||||
Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen,
|
||||
)]
|
||||
pub struct Delegations<Balance> {
|
||||
/// The number of votes (this is post-conviction).
|
||||
pub votes: Balance,
|
||||
/// The amount of raw capital, used for the turnout.
|
||||
pub capital: Balance,
|
||||
}
|
||||
|
||||
impl<Balance: Saturating> Saturating for Delegations<Balance> {
|
||||
fn saturating_add(self, o: Self) -> Self {
|
||||
Self {
|
||||
votes: self.votes.saturating_add(o.votes),
|
||||
capital: self.capital.saturating_add(o.capital),
|
||||
}
|
||||
}
|
||||
|
||||
fn saturating_sub(self, o: Self) -> Self {
|
||||
Self {
|
||||
votes: self.votes.saturating_sub(o.votes),
|
||||
capital: self.capital.saturating_sub(o.capital),
|
||||
}
|
||||
}
|
||||
|
||||
fn saturating_mul(self, o: Self) -> Self {
|
||||
Self {
|
||||
votes: self.votes.saturating_mul(o.votes),
|
||||
capital: self.capital.saturating_mul(o.capital),
|
||||
}
|
||||
}
|
||||
|
||||
fn saturating_pow(self, exp: usize) -> Self {
|
||||
Self { votes: self.votes.saturating_pow(exp), capital: self.capital.saturating_pow(exp) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an `unvote` operation is able to make actions that are not strictly always in the
|
||||
/// interest of an account.
|
||||
pub enum UnvoteScope {
|
||||
/// Permitted to do everything.
|
||||
Any,
|
||||
/// Permitted to do only the changes that do not need the owner's permission.
|
||||
OnlyExpired,
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The vote datatype.
|
||||
|
||||
use crate::{Conviction, Delegations};
|
||||
use codec::{Decode, Encode, EncodeLike, Input, MaxEncodedLen, Output};
|
||||
use frame_support::{pallet_prelude::Get, BoundedVec};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::{convert::TryFrom, prelude::*, result::Result};
|
||||
|
||||
/// A number of lock periods, plus a vote, one way or the other.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen)]
|
||||
pub struct Vote {
|
||||
pub aye: bool,
|
||||
pub conviction: Conviction,
|
||||
}
|
||||
|
||||
impl Encode for Vote {
|
||||
fn encode_to<T: Output + ?Sized>(&self, output: &mut T) {
|
||||
output.push_byte(u8::from(self.conviction) | if self.aye { 0b1000_0000 } else { 0 });
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeLike for Vote {}
|
||||
|
||||
impl Decode for Vote {
|
||||
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
let b = input.read_byte()?;
|
||||
Ok(Vote {
|
||||
aye: (b & 0b1000_0000) == 0b1000_0000,
|
||||
conviction: Conviction::try_from(b & 0b0111_1111)
|
||||
.map_err(|_| codec::Error::from("Invalid conviction"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeInfo for Vote {
|
||||
type Identity = Self;
|
||||
|
||||
fn type_info() -> scale_info::Type {
|
||||
scale_info::Type::builder()
|
||||
.path(scale_info::Path::new("Vote", module_path!()))
|
||||
.composite(
|
||||
scale_info::build::Fields::unnamed()
|
||||
.field(|f| f.ty::<u8>().docs(&["Raw vote byte, encodes aye + conviction"])),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A vote for a referendum of a particular account.
|
||||
#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub enum AccountVote<Balance> {
|
||||
/// A standard vote, one-way (approve or reject) with a given amount of conviction.
|
||||
Standard { vote: Vote, balance: Balance },
|
||||
/// A split vote with balances given for both ways, and with no conviction, useful for
|
||||
/// parachains when voting.
|
||||
Split { aye: Balance, nay: Balance },
|
||||
}
|
||||
|
||||
impl<Balance: Saturating> AccountVote<Balance> {
|
||||
/// Returns `Some` of the lock periods that the account is locked for, assuming that the
|
||||
/// referendum passed iff `approved` is `true`.
|
||||
pub fn locked_if(self, approved: bool) -> Option<(u32, Balance)> {
|
||||
// winning side: can only be removed after the lock period ends.
|
||||
match self {
|
||||
AccountVote::Standard { vote: Vote { conviction: Conviction::None, .. }, .. } => None,
|
||||
AccountVote::Standard { vote, balance } if vote.aye == approved =>
|
||||
Some((vote.conviction.lock_periods(), balance)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The total balance involved in this vote.
|
||||
pub fn balance(self) -> Balance {
|
||||
match self {
|
||||
AccountVote::Standard { balance, .. } => balance,
|
||||
AccountVote::Split { aye, nay } => aye.saturating_add(nay),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some` with whether the vote is an aye vote if it is standard, otherwise `None` if
|
||||
/// it is split.
|
||||
pub fn as_standard(self) -> Option<bool> {
|
||||
match self {
|
||||
AccountVote::Standard { vote, .. } => Some(vote.aye),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A "prior" lock, i.e. a lock for some now-forgotten reason.
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct PriorLock<BlockNumber, Balance>(BlockNumber, Balance);
|
||||
|
||||
impl<BlockNumber: Ord + Copy + Zero, Balance: Ord + Copy + Zero> PriorLock<BlockNumber, Balance> {
|
||||
/// Accumulates an additional lock.
|
||||
pub fn accumulate(&mut self, until: BlockNumber, amount: Balance) {
|
||||
self.0 = self.0.max(until);
|
||||
self.1 = self.1.max(amount);
|
||||
}
|
||||
|
||||
pub fn locked(&self) -> Balance {
|
||||
self.1
|
||||
}
|
||||
|
||||
pub fn rejig(&mut self, now: BlockNumber) {
|
||||
if now >= self.0 {
|
||||
self.0 = Zero::zero();
|
||||
self.1 = Zero::zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information concerning the delegation of some voting power.
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct Delegating<Balance, AccountId, BlockNumber> {
|
||||
/// The amount of balance delegated.
|
||||
pub balance: Balance,
|
||||
/// The account to which the voting power is delegated.
|
||||
pub target: AccountId,
|
||||
/// The conviction with which the voting power is delegated. When this gets undelegated, the
|
||||
/// relevant lock begins.
|
||||
pub conviction: Conviction,
|
||||
/// The total amount of delegations that this account has received, post-conviction-weighting.
|
||||
pub delegations: Delegations<Balance>,
|
||||
/// Any pre-existing locks from past voting/delegating activity.
|
||||
pub prior: PriorLock<BlockNumber, Balance>,
|
||||
}
|
||||
|
||||
/// Information concerning the direct vote-casting of some voting power.
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(MaxVotes))]
|
||||
pub struct Casting<Balance, BlockNumber, PollIndex, MaxVotes>
|
||||
where
|
||||
MaxVotes: Get<u32>,
|
||||
{
|
||||
/// The current votes of the account.
|
||||
pub votes: BoundedVec<(PollIndex, AccountVote<Balance>), MaxVotes>,
|
||||
/// The total amount of delegations that this account has received, post-conviction-weighting.
|
||||
pub delegations: Delegations<Balance>,
|
||||
/// Any pre-existing locks from past voting/delegating activity.
|
||||
pub prior: PriorLock<BlockNumber, Balance>,
|
||||
}
|
||||
|
||||
/// An indicator for what an account is doing; it can either be delegating or voting.
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(MaxVotes))]
|
||||
pub enum Voting<Balance, AccountId, BlockNumber, PollIndex, MaxVotes>
|
||||
where
|
||||
MaxVotes: Get<u32>,
|
||||
{
|
||||
/// The account is voting directly.
|
||||
Casting(Casting<Balance, BlockNumber, PollIndex, MaxVotes>),
|
||||
/// The account is delegating `balance` of its balance to a `target` account with `conviction`.
|
||||
Delegating(Delegating<Balance, AccountId, BlockNumber>),
|
||||
}
|
||||
|
||||
impl<Balance: Default, AccountId, BlockNumber: Zero, PollIndex, MaxVotes> Default
|
||||
for Voting<Balance, AccountId, BlockNumber, PollIndex, MaxVotes>
|
||||
where
|
||||
MaxVotes: Get<u32>,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Voting::Casting(Casting {
|
||||
votes: Default::default(),
|
||||
delegations: Default::default(),
|
||||
prior: PriorLock(Zero::zero(), Default::default()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Balance, AccountId, BlockNumber, PollIndex, MaxVotes> AsMut<PriorLock<BlockNumber, Balance>>
|
||||
for Voting<Balance, AccountId, BlockNumber, PollIndex, MaxVotes>
|
||||
where
|
||||
MaxVotes: Get<u32>,
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut PriorLock<BlockNumber, Balance> {
|
||||
match self {
|
||||
Voting::Casting(Casting { prior, .. }) => prior,
|
||||
Voting::Delegating(Delegating { prior, .. }) => prior,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Balance: Saturating + Ord + Zero + Copy,
|
||||
BlockNumber: Ord + Copy + Zero,
|
||||
AccountId,
|
||||
PollIndex,
|
||||
MaxVotes,
|
||||
> Voting<Balance, AccountId, BlockNumber, PollIndex, MaxVotes>
|
||||
where
|
||||
MaxVotes: Get<u32>,
|
||||
{
|
||||
pub fn rejig(&mut self, now: BlockNumber) {
|
||||
AsMut::<PriorLock<BlockNumber, Balance>>::as_mut(self).rejig(now);
|
||||
}
|
||||
|
||||
/// The amount of this account's balance that much currently be locked due to voting.
|
||||
pub fn locked_balance(&self) -> Balance {
|
||||
match self {
|
||||
Voting::Casting(Casting { votes, prior, .. }) =>
|
||||
votes.iter().map(|i| i.1.balance()).fold(prior.locked(), |a, i| a.max(i)),
|
||||
Voting::Delegating(Delegating { balance, prior, .. }) => *balance.max(&prior.locked()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_common(
|
||||
&mut self,
|
||||
delegations: Delegations<Balance>,
|
||||
prior: PriorLock<BlockNumber, Balance>,
|
||||
) {
|
||||
let (d, p) = match self {
|
||||
Voting::Casting(Casting { ref mut delegations, ref mut prior, .. }) =>
|
||||
(delegations, prior),
|
||||
Voting::Delegating(Delegating { ref mut delegations, ref mut prior, .. }) =>
|
||||
(delegations, prior),
|
||||
};
|
||||
*d = delegations;
|
||||
*p = prior;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 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.
|
||||
|
||||
//! Autogenerated weights for pallet_conviction_voting
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2022-01-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
|
||||
|
||||
// Executed Command:
|
||||
// target/release/substrate
|
||||
// benchmark
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pallet=pallet_conviction_voting
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./frame/conviction-voting/src/weights.rs
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pallet_conviction_voting.
|
||||
pub trait WeightInfo {
|
||||
fn vote_new() -> Weight;
|
||||
fn vote_existing() -> Weight;
|
||||
fn remove_vote() -> Weight;
|
||||
fn remove_other_vote() -> Weight;
|
||||
fn delegate(r: u32, ) -> Weight;
|
||||
fn undelegate(r: u32, ) -> Weight;
|
||||
fn unlock() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for pallet_conviction_voting using the Substrate node and recommended hardware.
|
||||
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting ClassLocksFor (r:1 w:1)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn vote_new() -> Weight {
|
||||
(159_647_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(6 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(6 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting ClassLocksFor (r:1 w:1)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn vote_existing() -> Weight {
|
||||
(339_851_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(6 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(6 as Weight))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn remove_vote() -> Weight {
|
||||
(317_673_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:0)
|
||||
fn remove_other_vote() -> Weight {
|
||||
(52_222_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:2 w:2)
|
||||
// Storage: ConvictionVoting ClassLocksFor (r:1 w:1)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn delegate(r: u32, ) -> Weight {
|
||||
(61_553_000 as Weight)
|
||||
// Standard Error: 123_000
|
||||
.saturating_add((33_092_000 as Weight).saturating_mul(r as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight)))
|
||||
.saturating_add(T::DbWeight::get().writes(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(r as Weight)))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:2 w:2)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn undelegate(r: u32, ) -> Weight {
|
||||
(42_037_000 as Weight)
|
||||
// Standard Error: 582_000
|
||||
.saturating_add((32_296_000 as Weight).saturating_mul(r as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight)))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(r as Weight)))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting ClassLocksFor (r:1 w:1)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
fn unlock() -> Weight {
|
||||
(69_017_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting ClassLocksFor (r:1 w:1)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn vote_new() -> Weight {
|
||||
(159_647_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(6 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(6 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting ClassLocksFor (r:1 w:1)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn vote_existing() -> Weight {
|
||||
(339_851_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(6 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(6 as Weight))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn remove_vote() -> Weight {
|
||||
(317_673_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:0)
|
||||
fn remove_other_vote() -> Weight {
|
||||
(52_222_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:2 w:2)
|
||||
// Storage: ConvictionVoting ClassLocksFor (r:1 w:1)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn delegate(r: u32, ) -> Weight {
|
||||
(61_553_000 as Weight)
|
||||
// Standard Error: 123_000
|
||||
.saturating_add((33_092_000 as Weight).saturating_mul(r as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(r as Weight)))
|
||||
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(r as Weight)))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:2 w:2)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn undelegate(r: u32, ) -> Weight {
|
||||
(42_037_000 as Weight)
|
||||
// Standard Error: 582_000
|
||||
.saturating_add((32_296_000 as Weight).saturating_mul(r as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(r as Weight)))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(r as Weight)))
|
||||
}
|
||||
// Storage: ConvictionVoting VotingFor (r:1 w:1)
|
||||
// Storage: ConvictionVoting ClassLocksFor (r:1 w:1)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
fn unlock() -> Weight {
|
||||
(69_017_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
}
|
||||
@@ -439,14 +439,6 @@ pub mod pallet {
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
/// Accounts for which there are locks in action which may be removed at some point in the
|
||||
/// future. The value is the block number at which the lock expires and may be removed.
|
||||
///
|
||||
/// TWOX-NOTE: OK ― `AccountId` is a secure hash.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn locks)]
|
||||
pub type Locks<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber>;
|
||||
|
||||
/// True if the last referendum tabled was submitted externally. False if it was a public
|
||||
/// proposal.
|
||||
// TODO: There should be any number of tabling origins, not just public and "external"
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
[package]
|
||||
name = "pallet-referenda"
|
||||
version = "4.0.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME pallet for inclusive on-chain decisions"
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.126", optional = true, features = ["derive"] }
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [
|
||||
"derive",
|
||||
] }
|
||||
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" }
|
||||
sp-io = { version = "5.0.0", default-features = false, path = "../../primitives/io" }
|
||||
sp-runtime = { version = "5.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true }
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
|
||||
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
|
||||
assert_matches = { version = "1.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-core = { version = "5.0.0", path = "../../primitives/core" }
|
||||
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
|
||||
pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" }
|
||||
pallet-preimage = { version = "4.0.0-dev", path = "../preimage" }
|
||||
assert_matches = { version = "1.5" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"sp-std/std",
|
||||
"sp-io/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"sp-runtime/std",
|
||||
"frame-system/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"assert_matches",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
@@ -0,0 +1,8 @@
|
||||
# Referenda Pallet
|
||||
|
||||
- [`assembly::Config`](https://docs.rs/pallet-assembly/latest/pallet_assembly/trait.Config.html)
|
||||
- [`Call`](https://docs.rs/pallet-assembly/latest/pallet_assembly/enum.Call.html)
|
||||
|
||||
## Overview
|
||||
|
||||
The Assembly pallet handles the administration of general stakeholder voting.
|
||||
@@ -0,0 +1,520 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020-2021 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 benchmarking.
|
||||
|
||||
use super::*;
|
||||
use crate::Pallet as Referenda;
|
||||
use assert_matches::assert_matches;
|
||||
use frame_benchmarking::{account, benchmarks, whitelist_account};
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
traits::{Currency, EnsureOrigin},
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
use sp_runtime::traits::{Bounded, Hash};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) {
|
||||
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
fn funded_account<T: Config>(name: &'static str, index: u32) -> T::AccountId {
|
||||
let caller: T::AccountId = account(name, index, SEED);
|
||||
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
|
||||
caller
|
||||
}
|
||||
|
||||
fn create_referendum<T: Config>() -> (T::AccountId, ReferendumIndex) {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
assert_ok!(Referenda::<T>::submit(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
RawOrigin::Root.into(),
|
||||
T::Hashing::hash_of(&0),
|
||||
AtOrAfter::After(0u32.into())
|
||||
));
|
||||
let index = ReferendumCount::<T>::get() - 1;
|
||||
(caller, index)
|
||||
}
|
||||
|
||||
fn place_deposit<T: Config>(index: ReferendumIndex) {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
assert_ok!(Referenda::<T>::place_decision_deposit(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
index,
|
||||
));
|
||||
}
|
||||
|
||||
fn nudge<T: Config>(index: ReferendumIndex) {
|
||||
assert_ok!(Referenda::<T>::nudge_referendum(RawOrigin::Root.into(), index));
|
||||
}
|
||||
|
||||
fn fill_queue<T: Config>(
|
||||
index: ReferendumIndex,
|
||||
spaces: u32,
|
||||
pass_after: u32,
|
||||
) -> Vec<ReferendumIndex> {
|
||||
// First, create enough other referendums to fill the track.
|
||||
let mut others = vec![];
|
||||
for _ in 0..info::<T>(index).max_deciding {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
others.push(index);
|
||||
}
|
||||
|
||||
// We will also need enough referenda which are queued and passing, we want `MaxQueued - 1`
|
||||
// in order to force the maximum amount of work to insert ours into the queue.
|
||||
for _ in spaces..T::MaxQueued::get() {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
make_passing_after::<T>(index, Perbill::from_percent(pass_after));
|
||||
others.push(index);
|
||||
}
|
||||
|
||||
// Skip to when they can start being decided.
|
||||
skip_prepare_period::<T>(index);
|
||||
|
||||
// Manually nudge the other referenda first to ensure that they begin.
|
||||
others.iter().for_each(|&i| nudge::<T>(i));
|
||||
|
||||
others
|
||||
}
|
||||
|
||||
fn info<T: Config>(index: ReferendumIndex) -> &'static TrackInfoOf<T> {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
T::Tracks::info(status.track).expect("Id value returned from T::Tracks")
|
||||
}
|
||||
|
||||
fn make_passing_after<T: Config>(index: ReferendumIndex, period_portion: Perbill) {
|
||||
let turnout = info::<T>(index).min_turnout.threshold(period_portion);
|
||||
let approval = info::<T>(index).min_approval.threshold(period_portion);
|
||||
Referenda::<T>::access_poll(index, |status| {
|
||||
if let PollStatus::Ongoing(tally, ..) = status {
|
||||
*tally = T::Tally::from_requirements(turnout, approval);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn make_passing<T: Config>(index: ReferendumIndex) {
|
||||
Referenda::<T>::access_poll(index, |status| {
|
||||
if let PollStatus::Ongoing(tally, ..) = status {
|
||||
*tally = T::Tally::unanimity();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn make_failing<T: Config>(index: ReferendumIndex) {
|
||||
Referenda::<T>::access_poll(index, |status| {
|
||||
if let PollStatus::Ongoing(tally, ..) = status {
|
||||
*tally = T::Tally::default();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn skip_prepare_period<T: Config>(index: ReferendumIndex) {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
let prepare_period_over = status.submitted + info::<T>(index).prepare_period;
|
||||
frame_system::Pallet::<T>::set_block_number(prepare_period_over);
|
||||
}
|
||||
|
||||
fn skip_decision_period<T: Config>(index: ReferendumIndex) {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
let decision_period_over = status.deciding.unwrap().since + info::<T>(index).decision_period;
|
||||
frame_system::Pallet::<T>::set_block_number(decision_period_over);
|
||||
}
|
||||
|
||||
fn skip_confirm_period<T: Config>(index: ReferendumIndex) {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
let confirm_period_over = status.deciding.unwrap().confirming.unwrap();
|
||||
frame_system::Pallet::<T>::set_block_number(confirm_period_over);
|
||||
}
|
||||
|
||||
fn skip_timeout_period<T: Config>(index: ReferendumIndex) {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
let timeout_period_over = status.submitted + T::UndecidingTimeout::get();
|
||||
frame_system::Pallet::<T>::set_block_number(timeout_period_over);
|
||||
}
|
||||
|
||||
fn alarm_time<T: Config>(index: ReferendumIndex) -> T::BlockNumber {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
status.alarm.unwrap().0
|
||||
}
|
||||
|
||||
fn is_confirming<T: Config>(index: ReferendumIndex) -> bool {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
matches!(
|
||||
status,
|
||||
ReferendumStatus { deciding: Some(DecidingStatus { confirming: Some(_), .. }), .. }
|
||||
)
|
||||
}
|
||||
|
||||
fn is_not_confirming<T: Config>(index: ReferendumIndex) -> bool {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
matches!(
|
||||
status,
|
||||
ReferendumStatus { deciding: Some(DecidingStatus { confirming: None, .. }), .. }
|
||||
)
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
submit {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
}: _(
|
||||
RawOrigin::Signed(caller),
|
||||
RawOrigin::Root.into(),
|
||||
T::Hashing::hash_of(&0),
|
||||
AtOrAfter::After(0u32.into())
|
||||
) verify {
|
||||
let index = ReferendumCount::<T>::get().checked_sub(1).unwrap();
|
||||
assert_matches!(ReferendumInfoFor::<T>::get(index), Some(ReferendumInfo::Ongoing(_)));
|
||||
}
|
||||
|
||||
place_decision_deposit_preparing {
|
||||
let (caller, index) = create_referendum::<T>();
|
||||
}: place_decision_deposit(RawOrigin::Signed(caller), index)
|
||||
verify {
|
||||
assert!(Referenda::<T>::ensure_ongoing(index).unwrap().decision_deposit.is_some());
|
||||
}
|
||||
|
||||
place_decision_deposit_queued {
|
||||
let (caller, index) = create_referendum::<T>();
|
||||
fill_queue::<T>(index, 1, 90);
|
||||
}: place_decision_deposit(RawOrigin::Signed(caller), index)
|
||||
verify {
|
||||
let track = Referenda::<T>::ensure_ongoing(index).unwrap().track;
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get());
|
||||
assert_eq!(TrackQueue::<T>::get(&track)[0], (index, 0u32.into()));
|
||||
}
|
||||
|
||||
place_decision_deposit_not_queued {
|
||||
let (caller, index) = create_referendum::<T>();
|
||||
fill_queue::<T>(index, 0, 90);
|
||||
}: place_decision_deposit(RawOrigin::Signed(caller), index)
|
||||
verify {
|
||||
let track = Referenda::<T>::ensure_ongoing(index).unwrap().track;
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get());
|
||||
assert!(TrackQueue::<T>::get(&track).into_iter().all(|(i, _)| i != index));
|
||||
}
|
||||
|
||||
place_decision_deposit_passing {
|
||||
let (caller, index) = create_referendum::<T>();
|
||||
skip_prepare_period::<T>(index);
|
||||
make_passing::<T>(index);
|
||||
}: place_decision_deposit(RawOrigin::Signed(caller), index)
|
||||
verify {
|
||||
assert!(is_confirming::<T>(index));
|
||||
}
|
||||
|
||||
place_decision_deposit_failing {
|
||||
let (caller, index) = create_referendum::<T>();
|
||||
skip_prepare_period::<T>(index);
|
||||
}: place_decision_deposit(RawOrigin::Signed(caller), index)
|
||||
verify {
|
||||
assert!(is_not_confirming::<T>(index));
|
||||
}
|
||||
|
||||
refund_decision_deposit {
|
||||
let (caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
assert_ok!(Referenda::<T>::cancel(T::CancelOrigin::successful_origin(), index));
|
||||
}: _(RawOrigin::Signed(caller), index)
|
||||
verify {
|
||||
assert_matches!(ReferendumInfoFor::<T>::get(index), Some(ReferendumInfo::Cancelled(_, _, None)));
|
||||
}
|
||||
|
||||
cancel {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
}: _<T::Origin>(T::CancelOrigin::successful_origin(), index)
|
||||
verify {
|
||||
assert_matches!(ReferendumInfoFor::<T>::get(index), Some(ReferendumInfo::Cancelled(..)));
|
||||
}
|
||||
|
||||
kill {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
}: _<T::Origin>(T::KillOrigin::successful_origin(), index)
|
||||
verify {
|
||||
assert_matches!(ReferendumInfoFor::<T>::get(index), Some(ReferendumInfo::Killed(..)));
|
||||
}
|
||||
|
||||
one_fewer_deciding_queue_empty {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
nudge::<T>(index);
|
||||
let track = Referenda::<T>::ensure_ongoing(index).unwrap().track;
|
||||
assert_ok!(Referenda::<T>::cancel(T::CancelOrigin::successful_origin(), index));
|
||||
assert_eq!(DecidingCount::<T>::get(&track), 1);
|
||||
}: one_fewer_deciding(RawOrigin::Root, track.clone())
|
||||
verify {
|
||||
assert_eq!(DecidingCount::<T>::get(&track), 0);
|
||||
}
|
||||
|
||||
one_fewer_deciding_failing {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
// No spaces free in the queue.
|
||||
let queued = fill_queue::<T>(index, 0, 90);
|
||||
let track = Referenda::<T>::ensure_ongoing(index).unwrap().track;
|
||||
assert_ok!(Referenda::<T>::cancel(T::CancelOrigin::successful_origin(), queued[0]));
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get());
|
||||
let deciding_count = DecidingCount::<T>::get(&track);
|
||||
}: one_fewer_deciding(RawOrigin::Root, track.clone())
|
||||
verify {
|
||||
assert_eq!(DecidingCount::<T>::get(&track), deciding_count);
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get() - 1);
|
||||
assert!(queued.into_iter().skip(1).all(|i| Referenda::<T>::ensure_ongoing(i)
|
||||
.unwrap()
|
||||
.deciding
|
||||
.map_or(true, |d| d.confirming.is_none())
|
||||
));
|
||||
}
|
||||
|
||||
one_fewer_deciding_passing {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
// No spaces free in the queue.
|
||||
let queued = fill_queue::<T>(index, 0, 0);
|
||||
let track = Referenda::<T>::ensure_ongoing(index).unwrap().track;
|
||||
assert_ok!(Referenda::<T>::cancel(T::CancelOrigin::successful_origin(), queued[0]));
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get());
|
||||
let deciding_count = DecidingCount::<T>::get(&track);
|
||||
}: one_fewer_deciding(RawOrigin::Root, track.clone())
|
||||
verify {
|
||||
assert_eq!(DecidingCount::<T>::get(&track), deciding_count);
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get() - 1);
|
||||
assert!(queued.into_iter().skip(1).all(|i| Referenda::<T>::ensure_ongoing(i)
|
||||
.unwrap()
|
||||
.deciding
|
||||
.map_or(true, |d| d.confirming.is_some())
|
||||
));
|
||||
}
|
||||
|
||||
nudge_referendum_requeued_insertion {
|
||||
// First create our referendum and place the deposit. It will be failing.
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
fill_queue::<T>(index, 0, 90);
|
||||
|
||||
// Now nudge ours, with the track now full and the queue full of referenda with votes,
|
||||
// ours will not be in the queue.
|
||||
nudge::<T>(index);
|
||||
let track = Referenda::<T>::ensure_ongoing(index).unwrap().track;
|
||||
assert!(TrackQueue::<T>::get(&track).into_iter().all(|(i, _)| i != index));
|
||||
|
||||
// Now alter the voting, so that ours goes into pole-position and shifts others down.
|
||||
make_passing::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
let t = TrackQueue::<T>::get(&track);
|
||||
assert_eq!(t.len() as u32, T::MaxQueued::get());
|
||||
assert_eq!(t[t.len() - 1].0, index);
|
||||
}
|
||||
|
||||
nudge_referendum_requeued_slide {
|
||||
// First create our referendum and place the deposit. It will be failing.
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
fill_queue::<T>(index, 1, 90);
|
||||
|
||||
// Now nudge ours, with the track now full, ours will be queued, but with no votes, it
|
||||
// will have the worst position.
|
||||
nudge::<T>(index);
|
||||
let track = Referenda::<T>::ensure_ongoing(index).unwrap().track;
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get());
|
||||
assert_eq!(TrackQueue::<T>::get(&track)[0], (index, 0u32.into()));
|
||||
|
||||
// Now alter the voting, so that ours leap-frogs all into the best position.
|
||||
make_passing::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
let t = TrackQueue::<T>::get(&track);
|
||||
assert_eq!(t.len() as u32, T::MaxQueued::get());
|
||||
assert_eq!(t[t.len() - 1].0, index);
|
||||
}
|
||||
|
||||
nudge_referendum_queued {
|
||||
// NOTE: worst possible queue situation is with a queue full of passing refs with one slot
|
||||
// free and this failing. It would result in `QUEUE_SIZE - 1` items being shifted for the
|
||||
// insertion at the beginning.
|
||||
|
||||
// First create our referendum and place the deposit. It will be failing.
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
fill_queue::<T>(index, 1, 0);
|
||||
|
||||
let track = Referenda::<T>::ensure_ongoing(index).unwrap().track;
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get() - 1);
|
||||
assert!(TrackQueue::<T>::get(&track).into_iter().all(|(_, v)| v > 0u32.into()));
|
||||
|
||||
// Then nudge ours, with the track now full, ours will be queued.
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get());
|
||||
assert_eq!(TrackQueue::<T>::get(&track)[0], (index, 0u32.into()));
|
||||
}
|
||||
|
||||
nudge_referendum_not_queued {
|
||||
// First create our referendum and place the deposit. It will be failing.
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
fill_queue::<T>(index, 0, 0);
|
||||
|
||||
let track = Referenda::<T>::ensure_ongoing(index).unwrap().track;
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get());
|
||||
assert!(TrackQueue::<T>::get(&track).into_iter().all(|(_, v)| v > 0u32.into()));
|
||||
|
||||
// Then nudge ours, with the track now full, ours will be queued.
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
assert_eq!(TrackQueue::<T>::get(&track).len() as u32, T::MaxQueued::get());
|
||||
assert!(TrackQueue::<T>::get(&track).into_iter().all(|(i, _)| i != index));
|
||||
}
|
||||
|
||||
nudge_referendum_no_deposit {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
skip_prepare_period::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
assert_matches!(status, ReferendumStatus { deciding: None, .. });
|
||||
}
|
||||
|
||||
nudge_referendum_preparing {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
let status = Referenda::<T>::ensure_ongoing(index).unwrap();
|
||||
assert_matches!(status, ReferendumStatus { deciding: None, .. });
|
||||
}
|
||||
|
||||
nudge_referendum_timed_out {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
skip_timeout_period::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
let info = ReferendumInfoFor::<T>::get(index).unwrap();
|
||||
assert_matches!(info, ReferendumInfo::TimedOut(..));
|
||||
}
|
||||
|
||||
nudge_referendum_begin_deciding_failing {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
assert!(is_not_confirming::<T>(index));
|
||||
}
|
||||
|
||||
nudge_referendum_begin_deciding_passing {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
make_passing::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
assert!(is_confirming::<T>(index));
|
||||
}
|
||||
|
||||
nudge_referendum_begin_confirming {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
nudge::<T>(index);
|
||||
assert!(!is_confirming::<T>(index));
|
||||
make_passing::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
assert!(is_confirming::<T>(index));
|
||||
}
|
||||
|
||||
nudge_referendum_end_confirming {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
make_passing::<T>(index);
|
||||
nudge::<T>(index);
|
||||
assert!(is_confirming::<T>(index));
|
||||
make_failing::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
assert!(!is_confirming::<T>(index));
|
||||
}
|
||||
|
||||
nudge_referendum_continue_not_confirming {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
nudge::<T>(index);
|
||||
assert!(!is_confirming::<T>(index));
|
||||
let old_alarm = alarm_time::<T>(index);
|
||||
make_passing_after::<T>(index, Perbill::from_percent(50));
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
assert_ne!(old_alarm, alarm_time::<T>(index));
|
||||
assert!(!is_confirming::<T>(index));
|
||||
}
|
||||
|
||||
nudge_referendum_continue_confirming {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
make_passing::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
nudge::<T>(index);
|
||||
assert!(is_confirming::<T>(index));
|
||||
let old_alarm = alarm_time::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
assert!(is_confirming::<T>(index));
|
||||
}
|
||||
|
||||
nudge_referendum_approved {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
make_passing::<T>(index);
|
||||
nudge::<T>(index);
|
||||
skip_confirm_period::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
let info = ReferendumInfoFor::<T>::get(index).unwrap();
|
||||
assert_matches!(info, ReferendumInfo::Approved(..));
|
||||
}
|
||||
|
||||
nudge_referendum_rejected {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
nudge::<T>(index);
|
||||
skip_decision_period::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
verify {
|
||||
let info = ReferendumInfoFor::<T>::get(index).unwrap();
|
||||
assert_matches!(info, ReferendumInfo::Rejected(..));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Referenda,
|
||||
crate::mock::new_test_ext(),
|
||||
crate::mock::Test
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 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.
|
||||
|
||||
//! Helpers for managing the different weights in various algorithmic branches.
|
||||
|
||||
use super::Config;
|
||||
use crate::weights::WeightInfo;
|
||||
|
||||
/// Branches within the `begin_deciding` function.
|
||||
pub enum BeginDecidingBranch {
|
||||
Passing,
|
||||
Failing,
|
||||
}
|
||||
|
||||
/// Branches within the `service_referendum` function.
|
||||
pub enum ServiceBranch {
|
||||
Fail,
|
||||
NoDeposit,
|
||||
Preparing,
|
||||
Queued,
|
||||
NotQueued,
|
||||
RequeuedInsertion,
|
||||
RequeuedSlide,
|
||||
BeginDecidingPassing,
|
||||
BeginDecidingFailing,
|
||||
BeginConfirming,
|
||||
ContinueConfirming,
|
||||
EndConfirming,
|
||||
ContinueNotConfirming,
|
||||
Approved,
|
||||
Rejected,
|
||||
TimedOut,
|
||||
}
|
||||
|
||||
impl From<BeginDecidingBranch> for ServiceBranch {
|
||||
fn from(x: BeginDecidingBranch) -> Self {
|
||||
use BeginDecidingBranch::*;
|
||||
use ServiceBranch::*;
|
||||
match x {
|
||||
Passing => BeginDecidingPassing,
|
||||
Failing => BeginDecidingFailing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceBranch {
|
||||
/// Return the weight of the `nudge` function when it takes the branch denoted by `self`.
|
||||
pub fn weight_of_nudge<T: Config>(self) -> frame_support::weights::Weight {
|
||||
use ServiceBranch::*;
|
||||
match self {
|
||||
NoDeposit => T::WeightInfo::nudge_referendum_no_deposit(),
|
||||
Preparing => T::WeightInfo::nudge_referendum_preparing(),
|
||||
Queued => T::WeightInfo::nudge_referendum_queued(),
|
||||
NotQueued => T::WeightInfo::nudge_referendum_not_queued(),
|
||||
RequeuedInsertion => T::WeightInfo::nudge_referendum_requeued_insertion(),
|
||||
RequeuedSlide => T::WeightInfo::nudge_referendum_requeued_slide(),
|
||||
BeginDecidingPassing => T::WeightInfo::nudge_referendum_begin_deciding_passing(),
|
||||
BeginDecidingFailing => T::WeightInfo::nudge_referendum_begin_deciding_failing(),
|
||||
BeginConfirming => T::WeightInfo::nudge_referendum_begin_confirming(),
|
||||
ContinueConfirming => T::WeightInfo::nudge_referendum_continue_confirming(),
|
||||
EndConfirming => T::WeightInfo::nudge_referendum_end_confirming(),
|
||||
ContinueNotConfirming => T::WeightInfo::nudge_referendum_continue_not_confirming(),
|
||||
Approved => T::WeightInfo::nudge_referendum_approved(),
|
||||
Rejected => T::WeightInfo::nudge_referendum_rejected(),
|
||||
TimedOut | Fail => T::WeightInfo::nudge_referendum_timed_out(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the maximum possible weight of the `nudge` function.
|
||||
pub fn max_weight_of_nudge<T: Config>() -> frame_support::weights::Weight {
|
||||
0.max(T::WeightInfo::nudge_referendum_no_deposit())
|
||||
.max(T::WeightInfo::nudge_referendum_preparing())
|
||||
.max(T::WeightInfo::nudge_referendum_queued())
|
||||
.max(T::WeightInfo::nudge_referendum_not_queued())
|
||||
.max(T::WeightInfo::nudge_referendum_requeued_insertion())
|
||||
.max(T::WeightInfo::nudge_referendum_requeued_slide())
|
||||
.max(T::WeightInfo::nudge_referendum_begin_deciding_passing())
|
||||
.max(T::WeightInfo::nudge_referendum_begin_deciding_failing())
|
||||
.max(T::WeightInfo::nudge_referendum_begin_confirming())
|
||||
.max(T::WeightInfo::nudge_referendum_continue_confirming())
|
||||
.max(T::WeightInfo::nudge_referendum_end_confirming())
|
||||
.max(T::WeightInfo::nudge_referendum_continue_not_confirming())
|
||||
.max(T::WeightInfo::nudge_referendum_approved())
|
||||
.max(T::WeightInfo::nudge_referendum_rejected())
|
||||
.max(T::WeightInfo::nudge_referendum_timed_out())
|
||||
}
|
||||
|
||||
/// Return the weight of the `place_decision_deposit` function when it takes the branch denoted
|
||||
/// by `self`.
|
||||
pub fn weight_of_deposit<T: Config>(self) -> Option<frame_support::weights::Weight> {
|
||||
use ServiceBranch::*;
|
||||
Some(match self {
|
||||
Preparing => T::WeightInfo::place_decision_deposit_preparing(),
|
||||
Queued => T::WeightInfo::place_decision_deposit_queued(),
|
||||
NotQueued => T::WeightInfo::place_decision_deposit_not_queued(),
|
||||
BeginDecidingPassing => T::WeightInfo::place_decision_deposit_passing(),
|
||||
BeginDecidingFailing => T::WeightInfo::place_decision_deposit_failing(),
|
||||
BeginConfirming |
|
||||
ContinueConfirming |
|
||||
EndConfirming |
|
||||
ContinueNotConfirming |
|
||||
Approved |
|
||||
Rejected |
|
||||
RequeuedInsertion |
|
||||
RequeuedSlide |
|
||||
TimedOut |
|
||||
Fail |
|
||||
NoDeposit => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the maximum possible weight of the `place_decision_deposit` function.
|
||||
pub fn max_weight_of_deposit<T: Config>() -> frame_support::weights::Weight {
|
||||
0.max(T::WeightInfo::place_decision_deposit_preparing())
|
||||
.max(T::WeightInfo::place_decision_deposit_queued())
|
||||
.max(T::WeightInfo::place_decision_deposit_not_queued())
|
||||
.max(T::WeightInfo::place_decision_deposit_passing())
|
||||
.max(T::WeightInfo::place_decision_deposit_failing())
|
||||
}
|
||||
}
|
||||
|
||||
/// Branches that the `one_fewer_deciding` function may take.
|
||||
pub enum OneFewerDecidingBranch {
|
||||
QueueEmpty,
|
||||
BeginDecidingPassing,
|
||||
BeginDecidingFailing,
|
||||
}
|
||||
|
||||
impl From<BeginDecidingBranch> for OneFewerDecidingBranch {
|
||||
fn from(x: BeginDecidingBranch) -> Self {
|
||||
use BeginDecidingBranch::*;
|
||||
use OneFewerDecidingBranch::*;
|
||||
match x {
|
||||
Passing => BeginDecidingPassing,
|
||||
Failing => BeginDecidingFailing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OneFewerDecidingBranch {
|
||||
/// Return the weight of the `one_fewer_deciding` function when it takes the branch denoted
|
||||
/// by `self`.
|
||||
pub fn weight<T: Config>(self) -> frame_support::weights::Weight {
|
||||
use OneFewerDecidingBranch::*;
|
||||
match self {
|
||||
QueueEmpty => T::WeightInfo::one_fewer_deciding_queue_empty(),
|
||||
BeginDecidingPassing => T::WeightInfo::one_fewer_deciding_passing(),
|
||||
BeginDecidingFailing => T::WeightInfo::one_fewer_deciding_failing(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the maximum possible weight of the `one_fewer_deciding` function.
|
||||
pub fn max_weight<T: Config>() -> frame_support::weights::Weight {
|
||||
0.max(T::WeightInfo::one_fewer_deciding_queue_empty())
|
||||
.max(T::WeightInfo::one_fewer_deciding_passing())
|
||||
.max(T::WeightInfo::one_fewer_deciding_failing())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,460 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The crate's tests.
|
||||
|
||||
use super::*;
|
||||
use crate as pallet_referenda;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
assert_ok, ord_parameter_types, parameter_types,
|
||||
traits::{
|
||||
ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, Polling,
|
||||
PreimageRecipient, SortedMembers,
|
||||
},
|
||||
weights::Weight,
|
||||
};
|
||||
use frame_system::{EnsureRoot, EnsureSignedBy};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, Hash, IdentityLookup},
|
||||
DispatchResult, Perbill,
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system,
|
||||
Balances: pallet_balances,
|
||||
Preimage: pallet_preimage,
|
||||
Scheduler: pallet_scheduler,
|
||||
Referenda: pallet_referenda,
|
||||
}
|
||||
);
|
||||
|
||||
// Test that a fitlered call can be dispatched.
|
||||
pub struct BaseFilter;
|
||||
impl Contains<Call> for BaseFilter {
|
||||
fn contains(call: &Call) -> bool {
|
||||
!matches!(call, &Call::Balances(pallet_balances::Call::set_balance { .. }))
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::simple_max(1_000_000);
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = BaseFilter;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = Call;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
}
|
||||
impl pallet_preimage::Config for Test {
|
||||
type Event = Event;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type ManagerOrigin = EnsureRoot<u64>;
|
||||
type MaxSize = ConstU32<4096>;
|
||||
type BaseDeposit = ();
|
||||
type ByteDeposit = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub MaximumSchedulerWeight: Weight = 2_000_000_000_000;
|
||||
}
|
||||
impl pallet_scheduler::Config for Test {
|
||||
type Event = Event;
|
||||
type Origin = Origin;
|
||||
type PalletsOrigin = OriginCaller;
|
||||
type Call = Call;
|
||||
type MaximumWeight = MaximumSchedulerWeight;
|
||||
type ScheduleOrigin = EnsureRoot<u64>;
|
||||
type MaxScheduledPerBlock = ConstU32<100>;
|
||||
type WeightInfo = ();
|
||||
type OriginPrivilegeCmp = EqualPrivilegeOnly;
|
||||
type PreimageProvider = Preimage;
|
||||
type NoPreimagePostponement = ConstU64<10>;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
pub const MaxLocks: u32 = 10;
|
||||
}
|
||||
impl pallet_balances::Config for Test {
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type MaxLocks = MaxLocks;
|
||||
type Balance = u64;
|
||||
type Event = Event;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub static AlarmInterval: u64 = 1;
|
||||
pub const SubmissionDeposit: u64 = 2;
|
||||
pub const MaxQueued: u32 = 3;
|
||||
pub const UndecidingTimeout: u64 = 20;
|
||||
}
|
||||
ord_parameter_types! {
|
||||
pub const One: u64 = 1;
|
||||
pub const Two: u64 = 2;
|
||||
pub const Three: u64 = 3;
|
||||
pub const Four: u64 = 4;
|
||||
pub const Five: u64 = 5;
|
||||
pub const Six: u64 = 6;
|
||||
}
|
||||
pub struct OneToFive;
|
||||
impl SortedMembers<u64> for OneToFive {
|
||||
fn sorted_members() -> Vec<u64> {
|
||||
vec![1, 2, 3, 4, 5]
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn add(_m: &u64) {}
|
||||
}
|
||||
|
||||
pub struct TestTracksInfo;
|
||||
impl TracksInfo<u64, u64> for TestTracksInfo {
|
||||
type Id = u8;
|
||||
type Origin = <Origin as OriginTrait>::PalletsOrigin;
|
||||
fn tracks() -> &'static [(Self::Id, TrackInfo<u64, u64>)] {
|
||||
static DATA: [(u8, TrackInfo<u64, u64>); 2] = [
|
||||
(
|
||||
0u8,
|
||||
TrackInfo {
|
||||
name: "root",
|
||||
max_deciding: 1,
|
||||
decision_deposit: 10,
|
||||
prepare_period: 4,
|
||||
decision_period: 4,
|
||||
confirm_period: 2,
|
||||
min_enactment_period: 4,
|
||||
min_approval: Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(100),
|
||||
delta: Perbill::from_percent(50),
|
||||
},
|
||||
min_turnout: Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(100),
|
||||
delta: Perbill::from_percent(100),
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
1u8,
|
||||
TrackInfo {
|
||||
name: "none",
|
||||
max_deciding: 3,
|
||||
decision_deposit: 1,
|
||||
prepare_period: 2,
|
||||
decision_period: 2,
|
||||
confirm_period: 1,
|
||||
min_enactment_period: 2,
|
||||
min_approval: Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(55),
|
||||
delta: Perbill::from_percent(5),
|
||||
},
|
||||
min_turnout: Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(10),
|
||||
delta: Perbill::from_percent(10),
|
||||
},
|
||||
},
|
||||
),
|
||||
];
|
||||
&DATA[..]
|
||||
}
|
||||
fn track_for(id: &Self::Origin) -> Result<Self::Id, ()> {
|
||||
if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) {
|
||||
match system_origin {
|
||||
frame_system::RawOrigin::Root => Ok(0),
|
||||
frame_system::RawOrigin::None => Ok(1),
|
||||
_ => Err(()),
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type WeightInfo = ();
|
||||
type Call = Call;
|
||||
type Event = Event;
|
||||
type Scheduler = Scheduler;
|
||||
type Currency = pallet_balances::Pallet<Self>;
|
||||
type CancelOrigin = EnsureSignedBy<Four, u64>;
|
||||
type KillOrigin = EnsureRoot<u64>;
|
||||
type Slash = ();
|
||||
type Votes = u32;
|
||||
type Tally = Tally;
|
||||
type SubmissionDeposit = SubmissionDeposit;
|
||||
type MaxQueued = MaxQueued;
|
||||
type UndecidingTimeout = UndecidingTimeout;
|
||||
type AlarmInterval = AlarmInterval;
|
||||
type Tracks = TestTracksInfo;
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)];
|
||||
pallet_balances::GenesisConfig::<Test> { balances }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
/// Execute the function two times, with `true` and with `false`.
|
||||
#[allow(dead_code)]
|
||||
pub fn new_test_ext_execute_with_cond(execute: impl FnOnce(bool) -> () + Clone) {
|
||||
new_test_ext().execute_with(|| (execute.clone())(false));
|
||||
new_test_ext().execute_with(|| execute(true));
|
||||
}
|
||||
|
||||
#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, Default, MaxEncodedLen)]
|
||||
pub struct Tally {
|
||||
pub ayes: u32,
|
||||
pub nays: u32,
|
||||
}
|
||||
|
||||
impl VoteTally<u32> for Tally {
|
||||
fn ayes(&self) -> u32 {
|
||||
self.ayes
|
||||
}
|
||||
|
||||
fn turnout(&self) -> Perbill {
|
||||
Perbill::from_percent(self.ayes + self.nays)
|
||||
}
|
||||
|
||||
fn approval(&self) -> Perbill {
|
||||
Perbill::from_rational(self.ayes, self.ayes + self.nays)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn unanimity() -> Self {
|
||||
Self { ayes: 100, nays: 0 }
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn from_requirements(turnout: Perbill, approval: Perbill) -> Self {
|
||||
let turnout = turnout.mul_ceil(100u32);
|
||||
let ayes = approval.mul_ceil(turnout);
|
||||
Self { ayes, nays: turnout - ayes }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_balance_proposal(value: u64) -> Vec<u8> {
|
||||
Call::Balances(pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 })
|
||||
.encode()
|
||||
}
|
||||
|
||||
pub fn set_balance_proposal_hash(value: u64) -> H256 {
|
||||
let c = Call::Balances(pallet_balances::Call::set_balance {
|
||||
who: 42,
|
||||
new_free: value,
|
||||
new_reserved: 0,
|
||||
});
|
||||
<Preimage as PreimageRecipient<_>>::note_preimage(c.encode().try_into().unwrap());
|
||||
BlakeTwo256::hash_of(&c)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult {
|
||||
Referenda::submit(
|
||||
Origin::signed(who),
|
||||
frame_system::RawOrigin::Root.into(),
|
||||
set_balance_proposal_hash(value),
|
||||
AtOrAfter::After(delay),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn next_block() {
|
||||
System::set_block_number(System::block_number() + 1);
|
||||
Scheduler::on_initialize(System::block_number());
|
||||
}
|
||||
|
||||
pub fn run_to(n: u64) {
|
||||
while System::block_number() < n {
|
||||
next_block();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn begin_referendum() -> ReferendumIndex {
|
||||
System::set_block_number(0);
|
||||
assert_ok!(propose_set_balance(1, 2, 1));
|
||||
run_to(2);
|
||||
0
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn tally(r: ReferendumIndex) -> Tally {
|
||||
Referenda::ensure_ongoing(r).unwrap().tally
|
||||
}
|
||||
|
||||
pub fn set_tally(index: ReferendumIndex, ayes: u32, nays: u32) {
|
||||
<Referenda as Polling<Tally>>::access_poll(index, |status| {
|
||||
let tally = status.ensure_ongoing().unwrap().0;
|
||||
tally.ayes = ayes;
|
||||
tally.nays = nays;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn waiting_since(i: ReferendumIndex) -> u64 {
|
||||
match ReferendumInfoFor::<Test>::get(i).unwrap() {
|
||||
ReferendumInfo::Ongoing(ReferendumStatus { submitted, deciding: None, .. }) => submitted,
|
||||
_ => panic!("Not waiting"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deciding_since(i: ReferendumIndex) -> u64 {
|
||||
match ReferendumInfoFor::<Test>::get(i).unwrap() {
|
||||
ReferendumInfo::Ongoing(ReferendumStatus {
|
||||
deciding: Some(DecidingStatus { since, .. }),
|
||||
..
|
||||
}) => since,
|
||||
_ => panic!("Not deciding"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deciding_and_failing_since(i: ReferendumIndex) -> u64 {
|
||||
match ReferendumInfoFor::<Test>::get(i).unwrap() {
|
||||
ReferendumInfo::Ongoing(ReferendumStatus {
|
||||
deciding: Some(DecidingStatus { since, confirming: None, .. }),
|
||||
..
|
||||
}) => since,
|
||||
_ => panic!("Not deciding"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn confirming_until(i: ReferendumIndex) -> u64 {
|
||||
match ReferendumInfoFor::<Test>::get(i).unwrap() {
|
||||
ReferendumInfo::Ongoing(ReferendumStatus {
|
||||
deciding: Some(DecidingStatus { confirming: Some(until), .. }),
|
||||
..
|
||||
}) => until,
|
||||
_ => panic!("Not confirming"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn approved_since(i: ReferendumIndex) -> u64 {
|
||||
match ReferendumInfoFor::<Test>::get(i).unwrap() {
|
||||
ReferendumInfo::Approved(since, ..) => since,
|
||||
_ => panic!("Not approved"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rejected_since(i: ReferendumIndex) -> u64 {
|
||||
match ReferendumInfoFor::<Test>::get(i).unwrap() {
|
||||
ReferendumInfo::Rejected(since, ..) => since,
|
||||
_ => panic!("Not rejected"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cancelled_since(i: ReferendumIndex) -> u64 {
|
||||
match ReferendumInfoFor::<Test>::get(i).unwrap() {
|
||||
ReferendumInfo::Cancelled(since, ..) => since,
|
||||
_ => panic!("Not cancelled"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn killed_since(i: ReferendumIndex) -> u64 {
|
||||
match ReferendumInfoFor::<Test>::get(i).unwrap() {
|
||||
ReferendumInfo::Killed(since, ..) => since,
|
||||
_ => panic!("Not killed"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timed_out_since(i: ReferendumIndex) -> u64 {
|
||||
match ReferendumInfoFor::<Test>::get(i).unwrap() {
|
||||
ReferendumInfo::TimedOut(since, ..) => since,
|
||||
_ => panic!("Not timed out"),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_deciding(i: ReferendumIndex) -> bool {
|
||||
matches!(
|
||||
ReferendumInfoFor::<Test>::get(i),
|
||||
Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. }))
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RefState {
|
||||
Failing,
|
||||
Passing,
|
||||
Confirming { immediate: bool },
|
||||
}
|
||||
|
||||
impl RefState {
|
||||
pub fn create(self) -> ReferendumIndex {
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
frame_support::dispatch::RawOrigin::Root.into(),
|
||||
set_balance_proposal_hash(1),
|
||||
AtOrAfter::At(10),
|
||||
));
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0));
|
||||
if matches!(self, RefState::Confirming { immediate: true }) {
|
||||
set_tally(0, 100, 0);
|
||||
}
|
||||
let index = ReferendumCount::<Test>::get() - 1;
|
||||
while !is_deciding(index) {
|
||||
run_to(System::block_number() + 1);
|
||||
}
|
||||
if matches!(self, RefState::Confirming { immediate: false }) {
|
||||
set_tally(0, 100, 0);
|
||||
run_to(System::block_number() + 1);
|
||||
}
|
||||
if matches!(self, RefState::Confirming { .. }) {
|
||||
assert_eq!(confirming_until(index), System::block_number() + 2);
|
||||
}
|
||||
if matches!(self, RefState::Passing) {
|
||||
set_tally(0, 100, 99);
|
||||
run_to(System::block_number() + 1);
|
||||
}
|
||||
index
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The crate's tests.
|
||||
|
||||
use super::*;
|
||||
use crate::mock::{RefState::*, *};
|
||||
use assert_matches::assert_matches;
|
||||
use codec::Decode;
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok,
|
||||
dispatch::{DispatchError::BadOrigin, RawOrigin},
|
||||
traits::Contains,
|
||||
};
|
||||
use pallet_balances::Error as BalancesError;
|
||||
|
||||
// TODO: Scheduler should re-use `None` items in its `Agenda`.
|
||||
|
||||
#[test]
|
||||
fn params_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(ReferendumCount::<Test>::get(), 0);
|
||||
assert_eq!(Balances::free_balance(42), 0);
|
||||
assert_eq!(Balances::total_issuance(), 600);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_happy_path_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// #1: submit
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
RawOrigin::Root.into(),
|
||||
set_balance_proposal_hash(1),
|
||||
AtOrAfter::At(10),
|
||||
));
|
||||
assert_eq!(Balances::reserved_balance(&1), 2);
|
||||
assert_eq!(ReferendumCount::<Test>::get(), 1);
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0));
|
||||
run_to(4);
|
||||
assert_eq!(DecidingCount::<Test>::get(0), 0);
|
||||
run_to(5);
|
||||
// #5: 4 blocks after submit - vote should now be deciding.
|
||||
assert_eq!(DecidingCount::<Test>::get(0), 1);
|
||||
run_to(6);
|
||||
// #6: Lots of ayes. Should now be confirming.
|
||||
set_tally(0, 100, 0);
|
||||
run_to(7);
|
||||
assert_eq!(confirming_until(0), 9);
|
||||
run_to(9);
|
||||
// #8: Should be confirmed & ended.
|
||||
assert_eq!(approved_since(0), 9);
|
||||
assert_ok!(Referenda::refund_decision_deposit(Origin::signed(2), 0));
|
||||
run_to(12);
|
||||
// #9: Should not yet be enacted.
|
||||
assert_eq!(Balances::free_balance(&42), 0);
|
||||
run_to(13);
|
||||
// #10: Proposal should be executed.
|
||||
assert_eq!(Balances::free_balance(&42), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insta_confirm_then_kill_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = Confirming { immediate: true }.create();
|
||||
run_to(6);
|
||||
assert_ok!(Referenda::kill(Origin::root(), r));
|
||||
assert_eq!(killed_since(r), 6);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_then_reconfirm_with_elapsed_trigger_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = Confirming { immediate: false }.create();
|
||||
assert_eq!(confirming_until(r), 8);
|
||||
run_to(7);
|
||||
set_tally(r, 100, 99);
|
||||
run_to(8);
|
||||
assert_eq!(deciding_and_failing_since(r), 5);
|
||||
run_to(11);
|
||||
assert_eq!(approved_since(r), 11);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instaconfirm_then_reconfirm_with_elapsed_trigger_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = Confirming { immediate: true }.create();
|
||||
run_to(6);
|
||||
assert_eq!(confirming_until(r), 7);
|
||||
set_tally(r, 100, 99);
|
||||
run_to(7);
|
||||
assert_eq!(deciding_and_failing_since(r), 5);
|
||||
run_to(11);
|
||||
assert_eq!(approved_since(r), 11);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instaconfirm_then_reconfirm_with_voting_trigger_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = Confirming { immediate: true }.create();
|
||||
run_to(6);
|
||||
assert_eq!(confirming_until(r), 7);
|
||||
set_tally(r, 100, 99);
|
||||
run_to(7);
|
||||
assert_eq!(deciding_and_failing_since(r), 5);
|
||||
run_to(8);
|
||||
set_tally(r, 100, 0);
|
||||
run_to(9);
|
||||
assert_eq!(confirming_until(r), 11);
|
||||
run_to(11);
|
||||
assert_eq!(approved_since(r), 11);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voting_should_extend_for_late_confirmation() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = Passing.create();
|
||||
run_to(10);
|
||||
assert_eq!(confirming_until(r), 11);
|
||||
run_to(11);
|
||||
assert_eq!(approved_since(r), 11);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_instafail_during_extension_confirmation() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = Passing.create();
|
||||
run_to(10);
|
||||
assert_eq!(confirming_until(r), 11);
|
||||
// Should insta-fail since it's now past the normal voting time.
|
||||
set_tally(r, 100, 101);
|
||||
run_to(11);
|
||||
assert_eq!(rejected_since(r), 11);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirming_then_fail_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = Failing.create();
|
||||
// Normally ends at 5 + 4 (voting period) = 9.
|
||||
assert_eq!(deciding_and_failing_since(r), 5);
|
||||
set_tally(r, 100, 0);
|
||||
run_to(6);
|
||||
assert_eq!(confirming_until(r), 8);
|
||||
set_tally(r, 100, 101);
|
||||
run_to(9);
|
||||
assert_eq!(rejected_since(r), 9);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queueing_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Submit a proposal into a track with a queue len of 1.
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(5),
|
||||
RawOrigin::Root.into(),
|
||||
set_balance_proposal_hash(0),
|
||||
AtOrAfter::After(0),
|
||||
));
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(5), 0));
|
||||
|
||||
run_to(2);
|
||||
|
||||
// Submit 3 more proposals into the same queue.
|
||||
for i in 1..=4 {
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(i),
|
||||
RawOrigin::Root.into(),
|
||||
set_balance_proposal_hash(i),
|
||||
AtOrAfter::After(0),
|
||||
));
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(i), i as u32));
|
||||
// TODO: decision deposit after some initial votes with a non-highest voted coming
|
||||
// first.
|
||||
}
|
||||
assert_eq!(ReferendumCount::<Test>::get(), 5);
|
||||
|
||||
run_to(5);
|
||||
// One should be being decided.
|
||||
assert_eq!(DecidingCount::<Test>::get(0), 1);
|
||||
assert_eq!(deciding_and_failing_since(0), 5);
|
||||
for i in 1..=4 {
|
||||
assert_eq!(waiting_since(i), 2);
|
||||
}
|
||||
|
||||
// Vote to set order.
|
||||
set_tally(1, 1, 10);
|
||||
set_tally(2, 2, 20);
|
||||
set_tally(3, 3, 30);
|
||||
set_tally(4, 100, 0);
|
||||
println!("Agenda #6: {:?}", pallet_scheduler::Agenda::<Test>::get(6));
|
||||
run_to(6);
|
||||
println!("{:?}", Vec::<_>::from(TrackQueue::<Test>::get(0)));
|
||||
|
||||
// Cancel the first.
|
||||
assert_ok!(Referenda::cancel(Origin::signed(4), 0));
|
||||
assert_eq!(cancelled_since(0), 6);
|
||||
|
||||
// The other with the most approvals (#4) should be being decided.
|
||||
run_to(7);
|
||||
assert_eq!(DecidingCount::<Test>::get(0), 1);
|
||||
assert_eq!(deciding_since(4), 7);
|
||||
assert_eq!(confirming_until(4), 9);
|
||||
|
||||
// Vote on the remaining two to change order.
|
||||
println!("Set tally #1");
|
||||
set_tally(1, 30, 31);
|
||||
println!("{:?}", Vec::<_>::from(TrackQueue::<Test>::get(0)));
|
||||
println!("Set tally #2");
|
||||
set_tally(2, 20, 20);
|
||||
println!("{:?}", Vec::<_>::from(TrackQueue::<Test>::get(0)));
|
||||
|
||||
// Let confirmation period end.
|
||||
run_to(9);
|
||||
|
||||
// #4 should have been confirmed.
|
||||
assert_eq!(approved_since(4), 9);
|
||||
|
||||
// On to the next block to select the new referendum
|
||||
run_to(10);
|
||||
// #1 (the one with the most approvals) should now be being decided.
|
||||
assert_eq!(deciding_since(1), 10);
|
||||
|
||||
// Let it end unsuccessfully.
|
||||
run_to(14);
|
||||
assert_eq!(rejected_since(1), 14);
|
||||
|
||||
// Service queue.
|
||||
run_to(15);
|
||||
// #2 should now be being decided. It will (barely) pass.
|
||||
assert_eq!(deciding_and_failing_since(2), 15);
|
||||
|
||||
// #2 moves into confirming at the last moment with a 50% approval.
|
||||
run_to(19);
|
||||
assert_eq!(confirming_until(2), 21);
|
||||
|
||||
// #2 gets approved.
|
||||
run_to(21);
|
||||
assert_eq!(approved_since(2), 21);
|
||||
|
||||
// The final one has since timed out.
|
||||
run_to(22);
|
||||
assert_eq!(timed_out_since(3), 22);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_timeout_should_happen_with_nothing_but_submit() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// #1: submit
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
RawOrigin::Root.into(),
|
||||
set_balance_proposal_hash(1),
|
||||
AtOrAfter::At(20),
|
||||
));
|
||||
run_to(20);
|
||||
assert_matches!(ReferendumInfoFor::<Test>::get(0), Some(ReferendumInfo::Ongoing(..)));
|
||||
run_to(21);
|
||||
// #11: Timed out - ended.
|
||||
assert_matches!(
|
||||
ReferendumInfoFor::<Test>::get(0),
|
||||
Some(ReferendumInfo::TimedOut(21, _, None))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracks_are_distinguished() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
RawOrigin::Root.into(),
|
||||
set_balance_proposal_hash(1),
|
||||
AtOrAfter::At(10),
|
||||
));
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(2),
|
||||
RawOrigin::None.into(),
|
||||
set_balance_proposal_hash(2),
|
||||
AtOrAfter::At(20),
|
||||
));
|
||||
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(3), 0));
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(4), 1));
|
||||
|
||||
let mut i = ReferendumInfoFor::<Test>::iter().collect::<Vec<_>>();
|
||||
i.sort_by_key(|x| x.0);
|
||||
assert_eq!(
|
||||
i,
|
||||
vec![
|
||||
(
|
||||
0,
|
||||
ReferendumInfo::Ongoing(ReferendumStatus {
|
||||
track: 0,
|
||||
origin: OriginCaller::system(RawOrigin::Root),
|
||||
proposal_hash: set_balance_proposal_hash(1),
|
||||
enactment: AtOrAfter::At(10),
|
||||
submitted: 1,
|
||||
submission_deposit: Deposit { who: 1, amount: 2 },
|
||||
decision_deposit: Some(Deposit { who: 3, amount: 10 }),
|
||||
deciding: None,
|
||||
tally: Tally { ayes: 0, nays: 0 },
|
||||
in_queue: false,
|
||||
alarm: Some((5, (5, 0))),
|
||||
})
|
||||
),
|
||||
(
|
||||
1,
|
||||
ReferendumInfo::Ongoing(ReferendumStatus {
|
||||
track: 1,
|
||||
origin: OriginCaller::system(RawOrigin::None),
|
||||
proposal_hash: set_balance_proposal_hash(2),
|
||||
enactment: AtOrAfter::At(20),
|
||||
submitted: 1,
|
||||
submission_deposit: Deposit { who: 2, amount: 2 },
|
||||
decision_deposit: Some(Deposit { who: 4, amount: 1 }),
|
||||
deciding: None,
|
||||
tally: Tally { ayes: 0, nays: 0 },
|
||||
in_queue: false,
|
||||
alarm: Some((3, (3, 0))),
|
||||
})
|
||||
),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_errors_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let h = set_balance_proposal_hash(1);
|
||||
// No track for Signed origins.
|
||||
assert_noop!(
|
||||
Referenda::submit(Origin::signed(1), RawOrigin::Signed(2).into(), h, AtOrAfter::At(10),),
|
||||
Error::<Test>::NoTrack
|
||||
);
|
||||
|
||||
// No funds for deposit
|
||||
assert_noop!(
|
||||
Referenda::submit(Origin::signed(10), RawOrigin::Root.into(), h, AtOrAfter::At(10),),
|
||||
BalancesError::<Test>::InsufficientBalance
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decision_deposit_errors_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let e = Error::<Test>::NotOngoing;
|
||||
assert_noop!(Referenda::place_decision_deposit(Origin::signed(2), 0), e);
|
||||
|
||||
let h = set_balance_proposal_hash(1);
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
RawOrigin::Root.into(),
|
||||
h,
|
||||
AtOrAfter::At(10),
|
||||
));
|
||||
let e = BalancesError::<Test>::InsufficientBalance;
|
||||
assert_noop!(Referenda::place_decision_deposit(Origin::signed(10), 0), e);
|
||||
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0));
|
||||
let e = Error::<Test>::HaveDeposit;
|
||||
assert_noop!(Referenda::place_decision_deposit(Origin::signed(2), 0), e);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refund_deposit_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let e = Error::<Test>::BadReferendum;
|
||||
assert_noop!(Referenda::refund_decision_deposit(Origin::signed(1), 0), e);
|
||||
|
||||
let h = set_balance_proposal_hash(1);
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
RawOrigin::Root.into(),
|
||||
h,
|
||||
AtOrAfter::At(10),
|
||||
));
|
||||
let e = Error::<Test>::NoDeposit;
|
||||
assert_noop!(Referenda::refund_decision_deposit(Origin::signed(2), 0), e);
|
||||
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0));
|
||||
let e = Error::<Test>::Unfinished;
|
||||
assert_noop!(Referenda::refund_decision_deposit(Origin::signed(3), 0), e);
|
||||
|
||||
run_to(11);
|
||||
assert_ok!(Referenda::refund_decision_deposit(Origin::signed(3), 0));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let h = set_balance_proposal_hash(1);
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
RawOrigin::Root.into(),
|
||||
h,
|
||||
AtOrAfter::At(10),
|
||||
));
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0));
|
||||
|
||||
run_to(8);
|
||||
assert_ok!(Referenda::cancel(Origin::signed(4), 0));
|
||||
assert_ok!(Referenda::refund_decision_deposit(Origin::signed(3), 0));
|
||||
assert_eq!(cancelled_since(0), 8);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_errors_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let h = set_balance_proposal_hash(1);
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
RawOrigin::Root.into(),
|
||||
h,
|
||||
AtOrAfter::At(10),
|
||||
));
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0));
|
||||
assert_noop!(Referenda::cancel(Origin::signed(1), 0), BadOrigin);
|
||||
|
||||
run_to(11);
|
||||
assert_noop!(Referenda::cancel(Origin::signed(4), 0), Error::<Test>::NotOngoing);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kill_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let h = set_balance_proposal_hash(1);
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
RawOrigin::Root.into(),
|
||||
h,
|
||||
AtOrAfter::At(10),
|
||||
));
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0));
|
||||
|
||||
run_to(8);
|
||||
assert_ok!(Referenda::kill(Origin::root(), 0));
|
||||
let e = Error::<Test>::NoDeposit;
|
||||
assert_noop!(Referenda::refund_decision_deposit(Origin::signed(3), 0), e);
|
||||
assert_eq!(killed_since(0), 8);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kill_errors_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let h = set_balance_proposal_hash(1);
|
||||
assert_ok!(Referenda::submit(
|
||||
Origin::signed(1),
|
||||
RawOrigin::Root.into(),
|
||||
h,
|
||||
AtOrAfter::At(10),
|
||||
));
|
||||
assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0));
|
||||
assert_noop!(Referenda::kill(Origin::signed(4), 0), BadOrigin);
|
||||
|
||||
run_to(11);
|
||||
assert_noop!(Referenda::kill(Origin::root(), 0), Error::<Test>::NotOngoing);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_balance_proposal_is_correctly_filtered_out() {
|
||||
for i in 0..10 {
|
||||
let call = crate::mock::Call::decode(&mut &set_balance_proposal(i)[..]).unwrap();
|
||||
assert!(!<Test as frame_system::Config>::BaseCallFilter::contains(&call));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn curve_handles_all_inputs() {
|
||||
let test_curve = Curve::LinearDecreasing { begin: Perbill::zero(), delta: Perbill::zero() };
|
||||
|
||||
let delay = test_curve.delay(Perbill::zero());
|
||||
assert_eq!(delay, Perbill::zero());
|
||||
|
||||
let test_curve = Curve::LinearDecreasing { begin: Perbill::zero(), delta: Perbill::one() };
|
||||
|
||||
let threshold = test_curve.threshold(Perbill::one());
|
||||
assert_eq!(threshold, Perbill::zero());
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 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.
|
||||
|
||||
//! Miscellaneous additional datatypes.
|
||||
|
||||
use super::*;
|
||||
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
|
||||
use frame_support::{traits::schedule::Anon, Parameter};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::fmt::Debug;
|
||||
|
||||
pub type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
>>::NegativeImbalance;
|
||||
pub type CallOf<T> = <T as Config>::Call;
|
||||
pub type VotesOf<T> = <T as Config>::Votes;
|
||||
pub type TallyOf<T> = <T as Config>::Tally;
|
||||
pub type PalletsOriginOf<T> = <<T as frame_system::Config>::Origin as OriginTrait>::PalletsOrigin;
|
||||
pub type ReferendumInfoOf<T> = ReferendumInfo<
|
||||
TrackIdOf<T>,
|
||||
PalletsOriginOf<T>,
|
||||
<T as frame_system::Config>::BlockNumber,
|
||||
<T as frame_system::Config>::Hash,
|
||||
BalanceOf<T>,
|
||||
TallyOf<T>,
|
||||
<T as frame_system::Config>::AccountId,
|
||||
ScheduleAddressOf<T>,
|
||||
>;
|
||||
pub type ReferendumStatusOf<T> = ReferendumStatus<
|
||||
TrackIdOf<T>,
|
||||
PalletsOriginOf<T>,
|
||||
<T as frame_system::Config>::BlockNumber,
|
||||
<T as frame_system::Config>::Hash,
|
||||
BalanceOf<T>,
|
||||
TallyOf<T>,
|
||||
<T as frame_system::Config>::AccountId,
|
||||
ScheduleAddressOf<T>,
|
||||
>;
|
||||
pub type DecidingStatusOf<T> = DecidingStatus<<T as frame_system::Config>::BlockNumber>;
|
||||
pub type TrackInfoOf<T> = TrackInfo<BalanceOf<T>, <T as frame_system::Config>::BlockNumber>;
|
||||
pub type TrackIdOf<T> = <<T as Config>::Tracks as TracksInfo<
|
||||
BalanceOf<T>,
|
||||
<T as frame_system::Config>::BlockNumber,
|
||||
>>::Id;
|
||||
pub type ScheduleAddressOf<T> = <<T as Config>::Scheduler as Anon<
|
||||
<T as frame_system::Config>::BlockNumber,
|
||||
CallOf<T>,
|
||||
PalletsOriginOf<T>,
|
||||
>>::Address;
|
||||
|
||||
/// A referendum index.
|
||||
pub type ReferendumIndex = u32;
|
||||
|
||||
pub trait InsertSorted<T> {
|
||||
/// Inserts an item into a sorted series.
|
||||
///
|
||||
/// Returns `true` if it was inserted, `false` if it would belong beyond the bound of the
|
||||
/// series.
|
||||
fn insert_sorted_by_key<F: FnMut(&T) -> K, K: PartialOrd<K> + Ord>(
|
||||
&mut self,
|
||||
t: T,
|
||||
f: F,
|
||||
) -> bool;
|
||||
}
|
||||
impl<T: Ord, S: Get<u32>> InsertSorted<T> for BoundedVec<T, S> {
|
||||
fn insert_sorted_by_key<F: FnMut(&T) -> K, K: PartialOrd<K> + Ord>(
|
||||
&mut self,
|
||||
t: T,
|
||||
mut f: F,
|
||||
) -> bool {
|
||||
let index = self.binary_search_by_key::<K, F>(&f(&t), f).unwrap_or_else(|x| x);
|
||||
self.force_insert_keep_right(index, t)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_support::traits::ConstU32;
|
||||
|
||||
#[test]
|
||||
fn insert_sorted_works() {
|
||||
let mut b: BoundedVec<u32, ConstU32<6>> = vec![20, 30, 40].try_into().unwrap();
|
||||
assert!(b.insert_sorted_by_key(10, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(60, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(50, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(!b.insert_sorted_by_key(9, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(11, |&x| x));
|
||||
assert_eq!(&b[..], &[11, 20, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(21, |&x| x));
|
||||
assert_eq!(&b[..], &[20, 21, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(61, |&x| x));
|
||||
assert_eq!(&b[..], &[21, 30, 40, 50, 60, 61][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(51, |&x| x));
|
||||
assert_eq!(&b[..], &[30, 40, 50, 51, 60, 61][..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct DecidingStatus<BlockNumber> {
|
||||
/// When this referendum began being "decided". If confirming, then the
|
||||
/// end will actually be delayed until the end of the confirmation period.
|
||||
pub(crate) since: BlockNumber,
|
||||
/// If `Some`, then the referendum has entered confirmation stage and will end at
|
||||
/// the block number as long as it doesn't lose its approval in the meantime.
|
||||
pub(crate) confirming: Option<BlockNumber>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct Deposit<AccountId, Balance> {
|
||||
pub(crate) who: AccountId,
|
||||
pub(crate) amount: Balance,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
pub struct TrackInfo<Balance, Moment> {
|
||||
/// Name of this track. TODO was &'static str
|
||||
pub name: &'static str,
|
||||
/// A limit for the number of referenda on this track that can be being decided at once.
|
||||
/// For Root origin this should generally be just one.
|
||||
pub max_deciding: u32,
|
||||
/// Amount that must be placed on deposit before a decision can be made.
|
||||
pub decision_deposit: Balance,
|
||||
/// Amount of time this must be submitted for before a decision can be made.
|
||||
pub prepare_period: Moment,
|
||||
/// Amount of time that a decision may take to be approved prior to cancellation.
|
||||
pub decision_period: Moment,
|
||||
/// Amount of time that the approval criteria must hold before it can be approved.
|
||||
pub confirm_period: Moment,
|
||||
/// Minimum amount of time that an approved proposal must be in the dispatch queue.
|
||||
pub min_enactment_period: Moment,
|
||||
/// Minimum aye votes as percentage of overall conviction-weighted votes needed for
|
||||
/// approval as a function of time into decision period.
|
||||
pub min_approval: Curve,
|
||||
/// Minimum turnout as percentage of overall population that is needed for
|
||||
/// approval as a function of time into decision period.
|
||||
pub min_turnout: Curve,
|
||||
}
|
||||
|
||||
/// Information on the voting tracks.
|
||||
pub trait TracksInfo<Balance, Moment> {
|
||||
/// The identifier for a track.
|
||||
type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static;
|
||||
|
||||
/// The origin type from which a track is implied.
|
||||
type Origin;
|
||||
|
||||
/// Return the array of known tracks and their information.
|
||||
fn tracks() -> &'static [(Self::Id, TrackInfo<Balance, Moment>)];
|
||||
|
||||
/// Determine the voting track for the given `origin`.
|
||||
fn track_for(origin: &Self::Origin) -> Result<Self::Id, ()>;
|
||||
|
||||
/// Return the track info for track `id`, by default this just looks it up in `Self::tracks()`.
|
||||
fn info(id: Self::Id) -> Option<&'static TrackInfo<Balance, Moment>> {
|
||||
Self::tracks().iter().find(|x| &x.0 == &id).map(|x| &x.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Indication of either a specific moment or a delay from a implicitly defined moment.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub enum AtOrAfter<Moment: Parameter> {
|
||||
/// Indiciates that the event should occur at the moment given.
|
||||
At(Moment),
|
||||
/// Indiciates that the event should occur some period of time (defined by the parameter) after
|
||||
/// a prior event. The prior event is defined by the context, but for the purposes of
|
||||
/// referendum proposals, the "prior event" is the passing of the referendum.
|
||||
After(Moment),
|
||||
}
|
||||
|
||||
impl<Moment: AtLeast32BitUnsigned + Copy + Parameter> AtOrAfter<Moment> {
|
||||
pub fn evaluate(&self, since: Moment) -> Moment {
|
||||
match &self {
|
||||
Self::At(m) => *m,
|
||||
Self::After(m) => m.saturating_add(since),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Info regarding an ongoing referendum.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct ReferendumStatus<
|
||||
TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Origin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Moment: Parameter + Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
|
||||
Hash: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
> {
|
||||
/// The track of this referendum.
|
||||
pub(crate) track: TrackId,
|
||||
/// The origin for this referendum.
|
||||
pub(crate) origin: Origin,
|
||||
/// The hash of the proposal up for referendum.
|
||||
pub(crate) proposal_hash: Hash,
|
||||
/// The time the proposal should be scheduled for enactment.
|
||||
pub(crate) enactment: AtOrAfter<Moment>,
|
||||
/// The time of submission. Once `UndecidingTimeout` passes, it may be closed by anyone if it
|
||||
/// `deciding` is `None`.
|
||||
pub(crate) submitted: Moment,
|
||||
/// The deposit reserved for the submission of this referendum.
|
||||
pub(crate) submission_deposit: Deposit<AccountId, Balance>,
|
||||
/// The deposit reserved for this referendum to be decided.
|
||||
pub(crate) decision_deposit: Option<Deposit<AccountId, Balance>>,
|
||||
/// The status of a decision being made. If `None`, it has not entered the deciding period.
|
||||
pub(crate) deciding: Option<DecidingStatus<Moment>>,
|
||||
/// The current tally of votes in this referendum.
|
||||
pub(crate) tally: Tally,
|
||||
/// Whether we have been placed in the queue for being decided or not.
|
||||
pub(crate) in_queue: bool,
|
||||
/// The next scheduled wake-up, if `Some`.
|
||||
pub(crate) alarm: Option<(Moment, ScheduleAddress)>,
|
||||
}
|
||||
|
||||
/// Info regarding a referendum, present or past.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub enum ReferendumInfo<
|
||||
TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Origin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
|
||||
Hash: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
> {
|
||||
/// Referendum has been submitted and is being voted on.
|
||||
Ongoing(
|
||||
ReferendumStatus<TrackId, Origin, Moment, Hash, Balance, Tally, AccountId, ScheduleAddress>,
|
||||
),
|
||||
/// Referendum finished with approval. Submission deposit is held.
|
||||
Approved(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
|
||||
/// Referendum finished with rejection. Submission deposit is held.
|
||||
Rejected(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
|
||||
/// Referendum finished with cancelation. Submission deposit is held.
|
||||
Cancelled(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
|
||||
/// Referendum finished and was never decided. Submission deposit is held.
|
||||
TimedOut(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
|
||||
/// Referendum finished with a kill.
|
||||
Killed(Moment),
|
||||
}
|
||||
|
||||
impl<
|
||||
TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Origin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Moment: Parameter + Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
|
||||
Hash: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
|
||||
> ReferendumInfo<TrackId, Origin, Moment, Hash, Balance, Tally, AccountId, ScheduleAddress>
|
||||
{
|
||||
/// Take the Decision Deposit from `self`, if there is one. Returns an `Err` if `self` is not
|
||||
/// in a valid state for the Decision Deposit to be refunded.
|
||||
pub fn take_decision_deposit(&mut self) -> Result<Option<Deposit<AccountId, Balance>>, ()> {
|
||||
use ReferendumInfo::*;
|
||||
match self {
|
||||
Ongoing(x) if x.decision_deposit.is_none() => Ok(None),
|
||||
// Cannot refund deposit if Ongoing as this breaks assumptions.
|
||||
Ongoing(_) => Err(()),
|
||||
Approved(_, _, d) | Rejected(_, _, d) | TimedOut(_, _, d) | Cancelled(_, _, d) =>
|
||||
Ok(d.take()),
|
||||
Killed(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for describing a curve over the 2-dimensional space of axes between 0-1, as represented
|
||||
/// by `(Perbill, Perbill)`.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
#[cfg_attr(not(feature = "std"), derive(RuntimeDebug))]
|
||||
pub enum Curve {
|
||||
/// Linear curve starting at `(0, begin)`, ending at `(period, begin - delta)`.
|
||||
LinearDecreasing { begin: Perbill, delta: Perbill },
|
||||
}
|
||||
|
||||
impl Curve {
|
||||
/// Determine the `y` value for the given `x` value.
|
||||
pub(crate) fn threshold(&self, x: Perbill) -> Perbill {
|
||||
match self {
|
||||
Self::LinearDecreasing { begin, delta } => *begin - (*delta * x).min(*begin),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the smallest `x` value such that `passing` returns `true` when passed along with
|
||||
/// the given `y` value.
|
||||
///
|
||||
/// ```nocompile
|
||||
/// let c = Curve::LinearDecreasing { begin: Perbill::one(), delta: Perbill::one() };
|
||||
/// // ^^^ Can be any curve.
|
||||
/// let y = Perbill::from_percent(50);
|
||||
/// // ^^^ Can be any value.
|
||||
/// let x = c.delay(y);
|
||||
/// assert!(c.passing(x, y));
|
||||
/// ```
|
||||
pub fn delay(&self, y: Perbill) -> Perbill {
|
||||
match self {
|
||||
Self::LinearDecreasing { begin, delta } =>
|
||||
if delta.is_zero() {
|
||||
return *delta
|
||||
} else {
|
||||
return (*begin - y.min(*begin)).min(*delta) / *delta
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` iff the `y` value is greater than the curve at the `x`.
|
||||
pub fn passing(&self, x: Perbill, y: Perbill) -> bool {
|
||||
y >= self.threshold(x)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Debug for Curve {
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
|
||||
match self {
|
||||
Self::LinearDecreasing { begin, delta } => {
|
||||
write!(
|
||||
f,
|
||||
"Linear[(0%, {}%) -> (100%, {}%)]",
|
||||
*begin * 100u32,
|
||||
(*begin - *delta) * 100u32,
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2021 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.
|
||||
|
||||
//! Autogenerated weights for pallet_referenda
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2021-12-19, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
|
||||
|
||||
// Executed Command:
|
||||
// target/release/substrate
|
||||
// benchmark
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pallet=pallet_referenda
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./frame/referenda/src/weights.rs
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pallet_referenda.
|
||||
pub trait WeightInfo {
|
||||
fn submit() -> Weight;
|
||||
fn place_decision_deposit_preparing() -> Weight;
|
||||
fn place_decision_deposit_queued() -> Weight;
|
||||
fn place_decision_deposit_not_queued() -> Weight;
|
||||
fn place_decision_deposit_passing() -> Weight;
|
||||
fn place_decision_deposit_failing() -> Weight;
|
||||
fn refund_decision_deposit() -> Weight;
|
||||
fn cancel() -> Weight;
|
||||
fn kill() -> Weight;
|
||||
fn one_fewer_deciding_queue_empty() -> Weight;
|
||||
fn one_fewer_deciding_failing() -> Weight;
|
||||
fn one_fewer_deciding_passing() -> Weight;
|
||||
fn nudge_referendum_requeued_insertion() -> Weight;
|
||||
fn nudge_referendum_requeued_slide() -> Weight;
|
||||
fn nudge_referendum_queued() -> Weight;
|
||||
fn nudge_referendum_not_queued() -> Weight;
|
||||
fn nudge_referendum_no_deposit() -> Weight;
|
||||
fn nudge_referendum_preparing() -> Weight;
|
||||
fn nudge_referendum_timed_out() -> Weight;
|
||||
fn nudge_referendum_begin_deciding_failing() -> Weight;
|
||||
fn nudge_referendum_begin_deciding_passing() -> Weight;
|
||||
fn nudge_referendum_begin_confirming() -> Weight;
|
||||
fn nudge_referendum_end_confirming() -> Weight;
|
||||
fn nudge_referendum_continue_not_confirming() -> Weight;
|
||||
fn nudge_referendum_continue_confirming() -> Weight;
|
||||
fn nudge_referendum_approved() -> Weight;
|
||||
fn nudge_referendum_rejected() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for pallet_referenda using the Substrate node and recommended hardware.
|
||||
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
// Storage: Referenda ReferendumCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:0 w:1)
|
||||
fn submit() -> Weight {
|
||||
(42_395_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn place_decision_deposit_preparing() -> Weight {
|
||||
(48_113_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:0)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
fn place_decision_deposit_queued() -> Weight {
|
||||
(53_624_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:0)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
fn place_decision_deposit_not_queued() -> Weight {
|
||||
(52_560_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn place_decision_deposit_passing() -> Weight {
|
||||
(54_067_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn place_decision_deposit_failing() -> Weight {
|
||||
(52_457_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
fn refund_decision_deposit() -> Weight {
|
||||
(28_504_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn cancel() -> Weight {
|
||||
(40_425_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn kill() -> Weight {
|
||||
(65_974_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda TrackQueue (r:1 w:0)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
fn one_fewer_deciding_queue_empty() -> Weight {
|
||||
(8_904_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn one_fewer_deciding_failing() -> Weight {
|
||||
(181_387_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn one_fewer_deciding_passing() -> Weight {
|
||||
(179_753_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_requeued_insertion() -> Weight {
|
||||
(53_592_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_requeued_slide() -> Weight {
|
||||
(53_173_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:0)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_queued() -> Weight {
|
||||
(55_770_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:0)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_not_queued() -> Weight {
|
||||
(53_922_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_no_deposit() -> Weight {
|
||||
(26_906_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_preparing() -> Weight {
|
||||
(27_943_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
fn nudge_referendum_timed_out() -> Weight {
|
||||
(20_256_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_begin_deciding_failing() -> Weight {
|
||||
(30_964_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_begin_deciding_passing() -> Weight {
|
||||
(31_763_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_begin_confirming() -> Weight {
|
||||
(28_892_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_end_confirming() -> Weight {
|
||||
(29_666_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_continue_not_confirming() -> Weight {
|
||||
(29_740_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_continue_confirming() -> Weight {
|
||||
(29_661_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
// Storage: Scheduler Lookup (r:1 w:1)
|
||||
// Storage: Preimage StatusFor (r:1 w:1)
|
||||
fn nudge_referendum_approved() -> Weight {
|
||||
(55_736_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(5 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(5 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_rejected() -> Weight {
|
||||
(32_726_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
// Storage: Referenda ReferendumCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:0 w:1)
|
||||
fn submit() -> Weight {
|
||||
(42_395_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn place_decision_deposit_preparing() -> Weight {
|
||||
(48_113_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:0)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
fn place_decision_deposit_queued() -> Weight {
|
||||
(53_624_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:0)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
fn place_decision_deposit_not_queued() -> Weight {
|
||||
(52_560_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn place_decision_deposit_passing() -> Weight {
|
||||
(54_067_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn place_decision_deposit_failing() -> Weight {
|
||||
(52_457_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
fn refund_decision_deposit() -> Weight {
|
||||
(28_504_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn cancel() -> Weight {
|
||||
(40_425_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn kill() -> Weight {
|
||||
(65_974_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda TrackQueue (r:1 w:0)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
fn one_fewer_deciding_queue_empty() -> Weight {
|
||||
(8_904_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn one_fewer_deciding_failing() -> Weight {
|
||||
(181_387_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
fn one_fewer_deciding_passing() -> Weight {
|
||||
(179_753_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_requeued_insertion() -> Weight {
|
||||
(53_592_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_requeued_slide() -> Weight {
|
||||
(53_173_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:0)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_queued() -> Weight {
|
||||
(55_770_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:0)
|
||||
// Storage: Referenda TrackQueue (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_not_queued() -> Weight {
|
||||
(53_922_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_no_deposit() -> Weight {
|
||||
(26_906_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_preparing() -> Weight {
|
||||
(27_943_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
fn nudge_referendum_timed_out() -> Weight {
|
||||
(20_256_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_begin_deciding_failing() -> Weight {
|
||||
(30_964_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Referenda DecidingCount (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_begin_deciding_passing() -> Weight {
|
||||
(31_763_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_begin_confirming() -> Weight {
|
||||
(28_892_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_end_confirming() -> Weight {
|
||||
(29_666_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_continue_not_confirming() -> Weight {
|
||||
(29_740_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_continue_confirming() -> Weight {
|
||||
(29_661_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:2 w:2)
|
||||
// Storage: Scheduler Lookup (r:1 w:1)
|
||||
// Storage: Preimage StatusFor (r:1 w:1)
|
||||
fn nudge_referendum_approved() -> Weight {
|
||||
(55_736_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(5 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
|
||||
}
|
||||
// Storage: Referenda ReferendumInfoFor (r:1 w:1)
|
||||
// Storage: Scheduler Agenda (r:1 w:1)
|
||||
fn nudge_referendum_rejected() -> Weight {
|
||||
(32_726_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
}
|
||||
@@ -694,13 +694,6 @@ impl<T: Config> Pallet<T> {
|
||||
});
|
||||
Agenda::<T>::append(when, s);
|
||||
let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;
|
||||
if index > T::MaxScheduledPerBlock::get() {
|
||||
log::warn!(
|
||||
target: "runtime::scheduler",
|
||||
"Warning: There are more items queued in the Scheduler than \
|
||||
expected from the runtime configuration. An update might be needed.",
|
||||
);
|
||||
}
|
||||
Self::deposit_event(Event::Scheduled { when, index });
|
||||
|
||||
Ok((when, index))
|
||||
@@ -795,13 +788,6 @@ impl<T: Config> Pallet<T> {
|
||||
};
|
||||
Agenda::<T>::append(when, Some(s));
|
||||
let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;
|
||||
if index > T::MaxScheduledPerBlock::get() {
|
||||
log::warn!(
|
||||
target: "runtime::scheduler",
|
||||
"Warning: There are more items queued in the Scheduler than \
|
||||
expected from the runtime configuration. An update might be needed.",
|
||||
);
|
||||
}
|
||||
let address = (when, index);
|
||||
Lookup::<T>::insert(&id, &address);
|
||||
Self::deposit_event(Event::Scheduled { when, index });
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
pub use crate::{
|
||||
codec::{Codec, Decode, Encode, EncodeAsRef, EncodeLike, HasCompact, Input, Output},
|
||||
scale_info::TypeInfo,
|
||||
sp_std::{
|
||||
fmt, marker,
|
||||
prelude::{Clone, Eq, PartialEq, Vec},
|
||||
@@ -33,7 +34,7 @@ pub use crate::{
|
||||
TransactionPriority, WeighData, Weight, WithPostDispatchInfo,
|
||||
},
|
||||
};
|
||||
pub use sp_runtime::{traits::Dispatchable, DispatchError};
|
||||
pub use sp_runtime::{traits::Dispatchable, DispatchError, RuntimeDebug};
|
||||
|
||||
/// The return type of a `Dispatchable` in frame. When returned explicitly from
|
||||
/// a dispatchable function it allows overriding the default `PostDispatchInfo`
|
||||
@@ -60,6 +61,28 @@ pub trait Callable<T> {
|
||||
// https://github.com/rust-lang/rust/issues/51331
|
||||
pub type CallableCallFor<A, R> = <A as Callable<R>>::Call;
|
||||
|
||||
/// Origin for the System pallet.
|
||||
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo)]
|
||||
pub enum RawOrigin<AccountId> {
|
||||
/// The system itself ordained this dispatch to happen: this is the highest privilege level.
|
||||
Root,
|
||||
/// It is signed by some public key and we provide the `AccountId`.
|
||||
Signed(AccountId),
|
||||
/// It is signed by nobody, can be either:
|
||||
/// * included and agreed upon by the validators anyway,
|
||||
/// * or unsigned transaction validated by a pallet.
|
||||
None,
|
||||
}
|
||||
|
||||
impl<AccountId> From<Option<AccountId>> for RawOrigin<AccountId> {
|
||||
fn from(s: Option<AccountId>) -> RawOrigin<AccountId> {
|
||||
match s {
|
||||
Some(who) => RawOrigin::Signed(who),
|
||||
None => RawOrigin::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that can be used as a parameter in a dispatchable function.
|
||||
///
|
||||
/// When using `decl_module` all arguments for call functions must implement this trait.
|
||||
@@ -2582,21 +2605,7 @@ mod tests {
|
||||
type DbWeight: Get<RuntimeDbWeight>;
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, scale_info::TypeInfo)]
|
||||
pub enum RawOrigin<AccountId> {
|
||||
Root,
|
||||
Signed(AccountId),
|
||||
None,
|
||||
}
|
||||
|
||||
impl<AccountId> From<Option<AccountId>> for RawOrigin<AccountId> {
|
||||
fn from(s: Option<AccountId>) -> RawOrigin<AccountId> {
|
||||
match s {
|
||||
Some(who) => RawOrigin::Signed(who),
|
||||
None => RawOrigin::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use super::super::RawOrigin;
|
||||
|
||||
pub type Origin<T> = RawOrigin<<T as Config>::AccountId>;
|
||||
}
|
||||
@@ -2638,7 +2647,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(scale_info::TypeInfo)]
|
||||
#[derive(Eq, PartialEq, Clone, crate::RuntimeDebug, scale_info::TypeInfo)]
|
||||
pub struct TraitImpl {}
|
||||
impl Config for TraitImpl {}
|
||||
|
||||
@@ -2679,8 +2688,15 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(TypeInfo, crate::RuntimeDebug, Eq, PartialEq, Clone, Encode, Decode)]
|
||||
pub struct OuterOrigin;
|
||||
|
||||
impl From<RawOrigin<<TraitImpl as system::Config>::AccountId>> for OuterOrigin {
|
||||
fn from(_: RawOrigin<<TraitImpl as system::Config>::AccountId>) -> Self {
|
||||
unimplemented!("Not required in tests!")
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::traits::OriginTrait for OuterOrigin {
|
||||
type Call = <TraitImpl as system::Config>::Call;
|
||||
type PalletsOrigin = OuterOrigin;
|
||||
|
||||
@@ -23,7 +23,7 @@ pub mod tokens;
|
||||
pub use tokens::{
|
||||
currency::{
|
||||
Currency, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency,
|
||||
VestingSchedule,
|
||||
TotalIssuanceOf, VestingSchedule,
|
||||
},
|
||||
fungible, fungibles,
|
||||
imbalance::{Imbalance, OnUnbalanced, SignedImbalance},
|
||||
@@ -90,4 +90,6 @@ mod dispatch;
|
||||
pub use dispatch::{EnsureOneOf, EnsureOrigin, OriginTrait, UnfilteredDispatchable};
|
||||
|
||||
mod voting;
|
||||
pub use voting::{CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote};
|
||||
pub use voting::{
|
||||
CurrencyToVote, PollStatus, Polling, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally,
|
||||
};
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
|
||||
//! Traits for dealing with dispatching calls and the origin from which they are dispatched.
|
||||
|
||||
use crate::dispatch::DispatchResultWithPostInfo;
|
||||
use sp_runtime::{traits::BadOrigin, Either};
|
||||
use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin};
|
||||
use sp_runtime::{
|
||||
traits::{BadOrigin, Member},
|
||||
Either,
|
||||
};
|
||||
|
||||
/// Some sort of check on the origin is performed by this object.
|
||||
pub trait EnsureOrigin<OuterOrigin> {
|
||||
@@ -56,7 +59,7 @@ pub trait OriginTrait: Sized {
|
||||
type Call;
|
||||
|
||||
/// The caller origin, overarching type of all pallets origins.
|
||||
type PalletsOrigin;
|
||||
type PalletsOrigin: Parameter + Member + Into<Self> + From<RawOrigin<Self::AccountId>>;
|
||||
|
||||
/// The AccountId used across the system.
|
||||
type AccountId;
|
||||
|
||||
@@ -125,7 +125,7 @@ pub mod v1 {
|
||||
/// A type that can be used as a scheduler.
|
||||
pub trait Anon<BlockNumber, Call, Origin> {
|
||||
/// An address which can be used for removing a scheduled task.
|
||||
type Address: Codec + Clone + Eq + EncodeLike + Debug;
|
||||
type Address: Codec + Clone + Eq + EncodeLike + Debug + TypeInfo;
|
||||
|
||||
/// Schedule a dispatch to happen at the beginning of some block in the future.
|
||||
///
|
||||
@@ -280,7 +280,7 @@ pub mod v2 {
|
||||
/// A type that can be used as a scheduler.
|
||||
pub trait Anon<BlockNumber, Call, Origin> {
|
||||
/// An address which can be used for removing a scheduled task.
|
||||
type Address: Codec + Clone + Eq + EncodeLike + Debug;
|
||||
type Address: Codec + Clone + Eq + EncodeLike + Debug + TypeInfo;
|
||||
/// A means of expressing a call by the hash of its encoded data.
|
||||
type Hash;
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ use super::{
|
||||
imbalance::{Imbalance, SignedImbalance},
|
||||
misc::{Balance, ExistenceRequirement, WithdrawReasons},
|
||||
};
|
||||
use crate::dispatch::{DispatchError, DispatchResult};
|
||||
use crate::{
|
||||
dispatch::{DispatchError, DispatchResult},
|
||||
traits::Get,
|
||||
};
|
||||
use codec::MaxEncodedLen;
|
||||
use sp_runtime::traits::MaybeSerializeDeserialize;
|
||||
use sp_std::fmt::Debug;
|
||||
@@ -200,6 +203,15 @@ pub trait Currency<AccountId> {
|
||||
) -> SignedImbalance<Self::Balance, Self::PositiveImbalance>;
|
||||
}
|
||||
|
||||
/// A non-const `Get` implementation parameterised by a `Currency` impl which provides the result
|
||||
/// of `total_issuance`.
|
||||
pub struct TotalIssuanceOf<C: Currency<A>, A>(sp_std::marker::PhantomData<(C, A)>);
|
||||
impl<C: Currency<A>, A> Get<C::Balance> for TotalIssuanceOf<C, A> {
|
||||
fn get() -> C::Balance {
|
||||
C::total_issuance()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<AccountId> Currency<AccountId> for () {
|
||||
type Balance = u32;
|
||||
|
||||
@@ -18,7 +18,14 @@
|
||||
//! Traits and associated data structures concerned with voting, and moving between tokens and
|
||||
//! votes.
|
||||
|
||||
use sp_arithmetic::traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto};
|
||||
use crate::dispatch::{DispatchError, Parameter};
|
||||
use codec::HasCompact;
|
||||
use sp_arithmetic::{
|
||||
traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto},
|
||||
Perbill,
|
||||
};
|
||||
use sp_runtime::traits::Member;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// A trait similar to `Convert` to convert values from `B` an abstract balance type
|
||||
/// into u64 and back from u128. (This conversion is used in election and other places where complex
|
||||
@@ -87,3 +94,74 @@ impl<B: UniqueSaturatedInto<u64> + UniqueSaturatedFrom<u128>> CurrencyToVote<B>
|
||||
B::unique_saturated_from(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VoteTally<Votes> {
|
||||
fn ayes(&self) -> Votes;
|
||||
fn turnout(&self) -> Perbill;
|
||||
fn approval(&self) -> Perbill;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn unanimity() -> Self;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn from_requirements(turnout: Perbill, approval: Perbill) -> Self;
|
||||
}
|
||||
|
||||
pub enum PollStatus<Tally, Moment, Class> {
|
||||
None,
|
||||
Ongoing(Tally, Class),
|
||||
Completed(Moment, bool),
|
||||
}
|
||||
|
||||
impl<Tally, Moment, Class> PollStatus<Tally, Moment, Class> {
|
||||
pub fn ensure_ongoing(self) -> Option<(Tally, Class)> {
|
||||
match self {
|
||||
Self::Ongoing(t, c) => Some((t, c)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Polling<Tally> {
|
||||
type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact;
|
||||
type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact;
|
||||
type Class: Parameter + Member + Ord + PartialOrd;
|
||||
type Moment;
|
||||
|
||||
/// Provides a vec of values that `T` may take.
|
||||
fn classes() -> Vec<Self::Class>;
|
||||
|
||||
/// `Some` if the referendum `index` can be voted on, along with the tally and class of
|
||||
/// referendum.
|
||||
///
|
||||
/// Don't use this if you might mutate - use `try_access_poll` instead.
|
||||
fn as_ongoing(index: Self::Index) -> Option<(Tally, Self::Class)>;
|
||||
|
||||
fn access_poll<R>(
|
||||
index: Self::Index,
|
||||
f: impl FnOnce(PollStatus<&mut Tally, Self::Moment, Self::Class>) -> R,
|
||||
) -> R;
|
||||
|
||||
fn try_access_poll<R>(
|
||||
index: Self::Index,
|
||||
f: impl FnOnce(PollStatus<&mut Tally, Self::Moment, Self::Class>) -> Result<R, DispatchError>,
|
||||
) -> Result<R, DispatchError>;
|
||||
|
||||
/// Create an ongoing majority-carries poll of given class lasting given period for the purpose
|
||||
/// of benchmarking.
|
||||
///
|
||||
/// May return `Err` if it is impossible.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn create_ongoing(class: Self::Class) -> Result<Self::Index, ()>;
|
||||
|
||||
/// End the given ongoing poll and return the result.
|
||||
///
|
||||
/// Returns `Err` if `index` is not an ongoing poll.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()>;
|
||||
|
||||
/// The maximum amount of ongoing polls within any single class. By default it practically
|
||||
/// unlimited (`u32::max_value()`).
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn max_ongoing() -> (Self::Class, u32) {
|
||||
(Self::classes().into_iter().next().expect("Always one class"), u32::max_value())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,23 +69,7 @@ frame_support::decl_error! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Origin for the system module.
|
||||
#[derive(PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, scale_info::TypeInfo)]
|
||||
pub enum RawOrigin<AccountId> {
|
||||
Root,
|
||||
Signed(AccountId),
|
||||
None,
|
||||
}
|
||||
|
||||
impl<AccountId> From<Option<AccountId>> for RawOrigin<AccountId> {
|
||||
fn from(s: Option<AccountId>) -> RawOrigin<AccountId> {
|
||||
match s {
|
||||
Some(who) => RawOrigin::Signed(who),
|
||||
None => RawOrigin::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use frame_support::dispatch::RawOrigin;
|
||||
pub type Origin<T> = RawOrigin<<T as Config>::AccountId>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -124,6 +124,7 @@ pub use extensions::{
|
||||
};
|
||||
// Backward compatible re-export.
|
||||
pub use extensions::check_mortality::CheckMortality as CheckEra;
|
||||
pub use frame_support::dispatch::RawOrigin;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
/// Compute the trie root of a list of extrinsics.
|
||||
@@ -708,28 +709,6 @@ pub struct EventRecord<E: Parameter + Member, T> {
|
||||
pub topics: Vec<T>,
|
||||
}
|
||||
|
||||
/// Origin for the System pallet.
|
||||
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo)]
|
||||
pub enum RawOrigin<AccountId> {
|
||||
/// The system itself ordained this dispatch to happen: this is the highest privilege level.
|
||||
Root,
|
||||
/// It is signed by some public key and we provide the `AccountId`.
|
||||
Signed(AccountId),
|
||||
/// It is signed by nobody, can be either:
|
||||
/// * included and agreed upon by the validators anyway,
|
||||
/// * or unsigned transaction validated by a pallet.
|
||||
None,
|
||||
}
|
||||
|
||||
impl<AccountId> From<Option<AccountId>> for RawOrigin<AccountId> {
|
||||
fn from(s: Option<AccountId>) -> RawOrigin<AccountId> {
|
||||
match s {
|
||||
Some(who) => RawOrigin::Signed(who),
|
||||
None => RawOrigin::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a Hash with 69 for each byte,
|
||||
// only used to build genesis config.
|
||||
#[cfg(feature = "std")]
|
||||
@@ -1641,7 +1620,7 @@ impl<T: Config> Lookup for ChainContext<T> {
|
||||
|
||||
/// Prelude to be used alongside pallet macro, for ease of use.
|
||||
pub mod pallet_prelude {
|
||||
pub use crate::{ensure_none, ensure_root, ensure_signed};
|
||||
pub use crate::{ensure_none, ensure_root, ensure_signed, ensure_signed_or_root};
|
||||
|
||||
/// Type alias for the `Origin` associated type of system config.
|
||||
pub type OriginFor<T> = <T as crate::Config>::Origin;
|
||||
|
||||
@@ -426,7 +426,7 @@ impl GetRuntimeBlockType for Runtime {
|
||||
type RuntimeBlock = Block;
|
||||
}
|
||||
|
||||
#[derive(Clone, RuntimeDebug)]
|
||||
#[derive(Clone, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo)]
|
||||
pub struct Origin;
|
||||
|
||||
impl From<frame_system::Origin<Runtime>> for Origin {
|
||||
|
||||
Reference in New Issue
Block a user