From b7103be9f506257aadd9e930a4ae3e98761c8d45 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 5 Nov 2021 12:59:02 +0100 Subject: [PATCH 01/66] Initial draft of new referendum state machine. --- Cargo.lock | 18 + Cargo.toml | 1 + frame/referenda/Cargo.toml | 52 + frame/referenda/README.md | 8 + frame/referenda/src/benchmarking.rs | 811 ++++++++++++ frame/referenda/src/conviction.rs | 119 ++ frame/referenda/src/lib.rs | 1173 +++++++++++++++++ frame/referenda/src/tests.rs | 302 +++++ frame/referenda/src/tests/cancellation.rs | 96 ++ frame/referenda/src/tests/decoders.rs | 85 ++ frame/referenda/src/tests/delegation.rs | 179 +++ .../referenda/src/tests/external_proposing.rs | 303 +++++ frame/referenda/src/tests/fast_tracking.rs | 98 ++ frame/referenda/src/tests/lock_voting.rs | 377 ++++++ frame/referenda/src/tests/preimage.rs | 219 +++ frame/referenda/src/tests/public_proposals.rs | 149 +++ frame/referenda/src/tests/scheduling.rs | 156 +++ frame/referenda/src/tests/voting.rs | 169 +++ frame/referenda/src/types.rs | 348 +++++ frame/referenda/src/vote.rs | 202 +++ frame/referenda/src/weights.rs | 545 ++++++++ frame/system/src/lib.rs | 17 +- 22 files changed, 5426 insertions(+), 1 deletion(-) create mode 100644 frame/referenda/Cargo.toml create mode 100644 frame/referenda/README.md create mode 100644 frame/referenda/src/benchmarking.rs create mode 100644 frame/referenda/src/conviction.rs create mode 100644 frame/referenda/src/lib.rs create mode 100644 frame/referenda/src/tests.rs create mode 100644 frame/referenda/src/tests/cancellation.rs create mode 100644 frame/referenda/src/tests/decoders.rs create mode 100644 frame/referenda/src/tests/delegation.rs create mode 100644 frame/referenda/src/tests/external_proposing.rs create mode 100644 frame/referenda/src/tests/fast_tracking.rs create mode 100644 frame/referenda/src/tests/lock_voting.rs create mode 100644 frame/referenda/src/tests/preimage.rs create mode 100644 frame/referenda/src/tests/public_proposals.rs create mode 100644 frame/referenda/src/tests/scheduling.rs create mode 100644 frame/referenda/src/tests/voting.rs create mode 100644 frame/referenda/src/types.rs create mode 100644 frame/referenda/src/vote.rs create mode 100644 frame/referenda/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 20499facb5b71..6ac784717f0f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5854,6 +5854,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-referenda" +version = "4.0.0-dev" +dependencies = [ + "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-scheduler" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 32d10ca8978dd..c609f9df545d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,7 @@ members = [ "frame/proxy", "frame/randomness-collective-flip", "frame/recovery", + "frame/referenda", "frame/scheduler", "frame/scored-pool", "frame/session", diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml new file mode 100644 index 0000000000000..e53a71da7acd1 --- /dev/null +++ b/frame/referenda/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "pallet-referenda" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +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 = "4.0.0-dev", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "4.0.0-dev", 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" } + +[dev-dependencies] +sp-core = { version = "4.0.0-dev", 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"] diff --git a/frame/referenda/README.md b/frame/referenda/README.md new file mode 100644 index 0000000000000..f07602c4a7e99 --- /dev/null +++ b/frame/referenda/README.md @@ -0,0 +1,8 @@ +# Assembly 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. diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs new file mode 100644 index 0000000000000..34bcb0da301e6 --- /dev/null +++ b/frame/referenda/src/benchmarking.rs @@ -0,0 +1,811 @@ +// 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 frame_benchmarking::{account, benchmarks, whitelist_account}; +use frame_support::{ + assert_noop, assert_ok, + codec::Decode, + traits::{ + schedule::DispatchTime, Currency, EnsureOrigin, Get, OnInitialize, UnfilteredDispatchable, + }, +}; +use frame_system::{Pallet as System, RawOrigin}; +use sp_runtime::traits::{BadOrigin, Bounded, One}; + +use crate::Pallet as Democracy; + +const SEED: u32 = 0; +const MAX_REFERENDUMS: u32 = 99; +const MAX_SECONDERS: u32 = 100; +const MAX_BYTES: u32 = 16_384; + +fn assert_last_event(generic_event: ::Event) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn funded_account(name: &'static str, index: u32) -> T::AccountId { + let caller: T::AccountId = account(name, index, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + caller +} + +fn add_proposal(n: u32) -> Result { + let other = funded_account::("proposer", n); + let value = T::MinimumDeposit::get(); + let proposal_hash: T::Hash = T::Hashing::hash_of(&n); + + Democracy::::propose(RawOrigin::Signed(other).into(), proposal_hash, value.into())?; + + Ok(proposal_hash) +} + +fn add_referendum(n: u32) -> Result { + let proposal_hash: T::Hash = T::Hashing::hash_of(&n); + let vote_threshold = VoteThreshold::SimpleMajority; + + Democracy::::inject_referendum( + T::LaunchPeriod::get(), + proposal_hash, + vote_threshold, + 0u32.into(), + ); + let referendum_index: ReferendumIndex = ReferendumCount::::get() - 1; + T::Scheduler::schedule_named( + (DEMOCRACY_ID, referendum_index).encode(), + DispatchTime::At(2u32.into()), + None, + 63, + frame_system::RawOrigin::Root.into(), + Call::enact_proposal { proposal_hash, index: referendum_index }.into(), + ) + .map_err(|_| "failed to schedule named")?; + Ok(referendum_index) +} + +fn account_vote(b: BalanceOf) -> AccountVote> { + let v = Vote { aye: true, conviction: Conviction::Locked1x }; + + AccountVote::Standard { vote: v, balance: b } +} + +benchmarks! { + propose { + let p = T::MaxProposals::get(); + + for i in 0 .. (p - 1) { + add_proposal::(i)?; + } + + let caller = funded_account::("caller", 0); + let proposal_hash: T::Hash = T::Hashing::hash_of(&0); + let value = T::MinimumDeposit::get(); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), proposal_hash, value.into()) + verify { + assert_eq!(Democracy::::public_props().len(), p as usize, "Proposals not created."); + } + + second { + let s in 0 .. MAX_SECONDERS; + + let caller = funded_account::("caller", 0); + let proposal_hash = add_proposal::(s)?; + + // Create s existing "seconds" + for i in 0 .. s { + let seconder = funded_account::("seconder", i); + Democracy::::second(RawOrigin::Signed(seconder).into(), 0, u32::MAX)?; + } + + let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; + assert_eq!(deposits.0.len(), (s + 1) as usize, "Seconds not recorded"); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), 0, u32::MAX) + verify { + let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; + assert_eq!(deposits.0.len(), (s + 2) as usize, "`second` benchmark did not work"); + } + + vote_new { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", 0); + let account_vote = account_vote::(100u32.into()); + + // We need to create existing direct votes + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes were not recorded."); + + let referendum_index = add_referendum::(r)?; + whitelist_account!(caller); + }: vote(RawOrigin::Signed(caller.clone()), referendum_index, account_vote) + verify { + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r + 1) as usize, "Vote was not recorded."); + } + + vote_existing { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", 0); + let account_vote = account_vote::(100u32.into()); + + // We need to create existing direct votes + for i in 0 ..=r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded."); + + // Change vote from aye to nay + let nay = Vote { aye: false, conviction: Conviction::Locked1x }; + let new_vote = AccountVote::Standard { vote: nay, balance: 1000u32.into() }; + let referendum_index = Democracy::::referendum_count() - 1; + + // This tests when a user changes a vote + whitelist_account!(caller); + }: vote(RawOrigin::Signed(caller.clone()), referendum_index, new_vote) + verify { + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r + 1) as usize, "Vote was incorrectly added"); + let referendum_info = Democracy::::referendum_info(referendum_index) + .ok_or("referendum doesn't exist")?; + let tally = match referendum_info { + ReferendumInfo::Ongoing(r) => r.tally, + _ => return Err("referendum not ongoing".into()), + }; + assert_eq!(tally.nays, 1000u32.into(), "changed vote was not recorded"); + } + + emergency_cancel { + let origin = T::CancellationOrigin::successful_origin(); + let referendum_index = add_referendum::(0)?; + assert_ok!(Democracy::::referendum_status(referendum_index)); + }: _(origin, referendum_index) + verify { + // Referendum has been canceled + assert_noop!( + Democracy::::referendum_status(referendum_index), + Error::::ReferendumInvalid, + ); + } + + blacklist { + let p in 1 .. T::MaxProposals::get(); + + // Place our proposal at the end to make sure it's worst case. + for i in 0 .. p - 1 { + add_proposal::(i)?; + } + // We should really add a lot of seconds here, but we're not doing it elsewhere. + + // Place our proposal in the external queue, too. + let hash = T::Hashing::hash_of(&0); + assert_ok!( + Democracy::::external_propose(T::ExternalOrigin::successful_origin(), hash.clone()) + ); + let origin = T::BlacklistOrigin::successful_origin(); + // Add a referendum of our proposal. + let referendum_index = add_referendum::(0)?; + assert_ok!(Democracy::::referendum_status(referendum_index)); + }: _(origin, hash, Some(referendum_index)) + verify { + // Referendum has been canceled + assert_noop!( + Democracy::::referendum_status(referendum_index), + Error::::ReferendumInvalid + ); + } + + // Worst case scenario, we external propose a previously blacklisted proposal + external_propose { + let v in 1 .. MAX_VETOERS as u32; + + let origin = T::ExternalOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&0); + // Add proposal to blacklist with block number 0 + Blacklist::::insert( + proposal_hash, + (T::BlockNumber::zero(), vec![T::AccountId::default(); v as usize]) + ); + }: _(origin, proposal_hash) + verify { + // External proposal created + ensure!(>::exists(), "External proposal didn't work"); + } + + external_propose_majority { + let origin = T::ExternalMajorityOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&0); + }: _(origin, proposal_hash) + verify { + // External proposal created + ensure!(>::exists(), "External proposal didn't work"); + } + + external_propose_default { + let origin = T::ExternalDefaultOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&0); + }: _(origin, proposal_hash) + verify { + // External proposal created + ensure!(>::exists(), "External proposal didn't work"); + } + + fast_track { + let origin_propose = T::ExternalDefaultOrigin::successful_origin(); + let proposal_hash: T::Hash = T::Hashing::hash_of(&0); + Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; + + // NOTE: Instant origin may invoke a little bit more logic, but may not always succeed. + let origin_fast_track = T::FastTrackOrigin::successful_origin(); + let voting_period = T::FastTrackVotingPeriod::get(); + let delay = 0u32; + }: _(origin_fast_track, proposal_hash, voting_period.into(), delay.into()) + verify { + assert_eq!(Democracy::::referendum_count(), 1, "referendum not created") + } + + veto_external { + // Existing veto-ers + let v in 0 .. MAX_VETOERS as u32; + + let proposal_hash: T::Hash = T::Hashing::hash_of(&v); + + let origin_propose = T::ExternalDefaultOrigin::successful_origin(); + Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; + + let mut vetoers: Vec = Vec::new(); + for i in 0 .. v { + vetoers.push(account("vetoer", i, SEED)); + } + vetoers.sort(); + Blacklist::::insert(proposal_hash, (T::BlockNumber::zero(), vetoers)); + + let origin = T::VetoOrigin::successful_origin(); + ensure!(NextExternal::::get().is_some(), "no external proposal"); + }: _(origin, proposal_hash) + verify { + assert!(NextExternal::::get().is_none()); + let (_, new_vetoers) = >::get(&proposal_hash).ok_or("no blacklist")?; + assert_eq!(new_vetoers.len(), (v + 1) as usize, "vetoers not added"); + } + + cancel_proposal { + let p in 1 .. T::MaxProposals::get(); + + // Place our proposal at the end to make sure it's worst case. + for i in 0 .. p { + add_proposal::(i)?; + } + }: _(RawOrigin::Root, 0) + + cancel_referendum { + let referendum_index = add_referendum::(0)?; + }: _(RawOrigin::Root, referendum_index) + + cancel_queued { + let r in 1 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; // This add one element in the scheduler + } + + let referendum_index = add_referendum::(r)?; + }: _(RawOrigin::Root, referendum_index) + + // This measures the path of `launch_next` external. Not currently used as we simply + // assume the weight is `MaxBlockWeight` when executing. + #[extra] + on_initialize_external { + let r in 0 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; + } + + assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); + + // Launch external + LastTabledWasExternal::::put(false); + + let origin = T::ExternalMajorityOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&r); + let call = Call::::external_propose_majority { proposal_hash }; + call.dispatch_bypass_filter(origin)?; + // External proposal created + ensure!(>::exists(), "External proposal didn't work"); + + let block_number = T::LaunchPeriod::get(); + + }: { Democracy::::on_initialize(block_number) } + verify { + // One extra because of next external + assert_eq!(Democracy::::referendum_count(), r + 1, "referenda not created"); + ensure!(!>::exists(), "External wasn't taken"); + + // All but the new next external should be finished + for i in 0 .. r { + if let Some(value) = ReferendumInfoOf::::get(i) { + match value { + ReferendumInfo::Finished { .. } => (), + ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()), + } + } + } + } + + // This measures the path of `launch_next` public. Not currently used as we simply + // assume the weight is `MaxBlockWeight` when executing. + #[extra] + on_initialize_public { + let r in 1 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; + } + + assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); + + // Launch public + assert!(add_proposal::(r).is_ok(), "proposal not created"); + LastTabledWasExternal::::put(true); + + let block_number = T::LaunchPeriod::get(); + + }: { Democracy::::on_initialize(block_number) } + verify { + // One extra because of next public + assert_eq!(Democracy::::referendum_count(), r + 1, "proposal not accepted"); + + // All should be finished + for i in 0 .. r { + if let Some(value) = ReferendumInfoOf::::get(i) { + match value { + ReferendumInfo::Finished { .. } => (), + ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()), + } + } + } + } + + // No launch no maturing referenda. + on_initialize_base { + let r in 1 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; + } + + for (key, mut info) in ReferendumInfoOf::::iter() { + if let ReferendumInfo::Ongoing(ref mut status) = info { + status.end += 100u32.into(); + } + ReferendumInfoOf::::insert(key, info); + } + + assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); + assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); + + }: { Democracy::::on_initialize(1u32.into()) } + verify { + // All should be on going + for i in 0 .. r { + if let Some(value) = ReferendumInfoOf::::get(i) { + match value { + ReferendumInfo::Finished { .. } => return Err("Referendum has been finished".into()), + ReferendumInfo::Ongoing(_) => (), + } + } + } + } + + on_initialize_base_with_launch_period { + let r in 1 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; + } + + for (key, mut info) in ReferendumInfoOf::::iter() { + if let ReferendumInfo::Ongoing(ref mut status) = info { + status.end += 100u32.into(); + } + ReferendumInfoOf::::insert(key, info); + } + + assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); + assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); + + let block_number = T::LaunchPeriod::get(); + + }: { Democracy::::on_initialize(block_number) } + verify { + // All should be on going + for i in 0 .. r { + if let Some(value) = ReferendumInfoOf::::get(i) { + match value { + ReferendumInfo::Finished { .. } => return Err("Referendum has been finished".into()), + ReferendumInfo::Ongoing(_) => (), + } + } + } + } + + delegate { + let r in 1 .. MAX_REFERENDUMS; + + let initial_balance: BalanceOf = 100u32.into(); + let delegated_balance: BalanceOf = 1000u32.into(); + + let caller = funded_account::("caller", 0); + // Caller will initially delegate to `old_delegate` + let old_delegate: T::AccountId = funded_account::("old_delegate", r); + Democracy::::delegate( + RawOrigin::Signed(caller.clone()).into(), + old_delegate.clone(), + Conviction::Locked1x, + delegated_balance, + )?; + let (target, balance) = match VotingOf::::get(&caller) { + Voting::Delegating { target, balance, .. } => (target, balance), + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(target, old_delegate, "delegation target didn't work"); + assert_eq!(balance, delegated_balance, "delegation balance didn't work"); + // Caller will now switch to `new_delegate` + let new_delegate: T::AccountId = funded_account::("new_delegate", r); + let account_vote = account_vote::(initial_balance); + // We need to create existing direct votes for the `new_delegate` + for i in 0..r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote.clone())?; + } + let votes = match VotingOf::::get(&new_delegate) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes were not recorded."); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller.clone()), new_delegate.clone(), Conviction::Locked1x, delegated_balance) + verify { + let (target, balance) = match VotingOf::::get(&caller) { + Voting::Delegating { target, balance, .. } => (target, balance), + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(target, new_delegate, "delegation target didn't work"); + assert_eq!(balance, delegated_balance, "delegation balance didn't work"); + let delegations = match VotingOf::::get(&new_delegate) { + Voting::Direct { delegations, .. } => delegations, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(delegations.capital, delegated_balance, "delegation was not recorded."); + } + + undelegate { + let r in 1 .. MAX_REFERENDUMS; + + let initial_balance: BalanceOf = 100u32.into(); + let delegated_balance: BalanceOf = 1000u32.into(); + + let caller = funded_account::("caller", 0); + // Caller will delegate + let the_delegate: T::AccountId = funded_account::("delegate", r); + Democracy::::delegate( + RawOrigin::Signed(caller.clone()).into(), + the_delegate.clone(), + Conviction::Locked1x, + delegated_balance, + )?; + let (target, balance) = match VotingOf::::get(&caller) { + Voting::Delegating { target, balance, .. } => (target, balance), + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(target, the_delegate, "delegation target didn't work"); + assert_eq!(balance, delegated_balance, "delegation balance didn't work"); + // We need to create votes direct votes for the `delegate` + let account_vote = account_vote::(initial_balance); + for i in 0..r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote( + RawOrigin::Signed(the_delegate.clone()).into(), + ref_idx, + account_vote.clone() + )?; + } + let votes = match VotingOf::::get(&the_delegate) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes were not recorded."); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller.clone())) + verify { + // Voting should now be direct + match VotingOf::::get(&caller) { + Voting::Direct { .. } => (), + _ => return Err("undelegation failed".into()), + } + } + + clear_public_proposals { + add_proposal::(0)?; + + }: _(RawOrigin::Root) + + note_preimage { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + let caller = funded_account::("caller", 0); + let encoded_proposal = vec![1; b as usize]; + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) + verify { + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + match Preimages::::get(proposal_hash) { + Some(PreimageStatus::Available { .. }) => (), + _ => return Err("preimage not available".into()) + } + } + + note_imminent_preimage { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + // d + 1 to include the one we are testing + let encoded_proposal = vec![1; b as usize]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + let block_number = T::BlockNumber::one(); + Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number)); + + let caller = funded_account::("caller", 0); + let encoded_proposal = vec![1; b as usize]; + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) + verify { + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + match Preimages::::get(proposal_hash) { + Some(PreimageStatus::Available { .. }) => (), + _ => return Err("preimage not available".into()) + } + } + + reap_preimage { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + let encoded_proposal = vec![1; b as usize]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + + let submitter = funded_account::("submitter", b); + Democracy::::note_preimage(RawOrigin::Signed(submitter.clone()).into(), encoded_proposal.clone())?; + + // We need to set this otherwise we get `Early` error. + let block_number = T::VotingPeriod::get() + T::EnactmentPeriod::get() + T::BlockNumber::one(); + System::::set_block_number(block_number.into()); + + assert!(Preimages::::contains_key(proposal_hash)); + + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), proposal_hash.clone(), u32::MAX) + verify { + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + assert!(!Preimages::::contains_key(proposal_hash)); + } + + // Test when unlock will remove locks + unlock_remove { + let r in 1 .. MAX_REFERENDUMS; + + let locker = funded_account::("locker", 0); + // Populate votes so things are locked + let base_balance: BalanceOf = 100u32.into(); + let small_vote = account_vote::(base_balance); + // Vote and immediately unvote + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; + Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_idx)?; + } + + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + }: unlock(RawOrigin::Signed(caller), locker.clone()) + verify { + // Note that we may want to add a `get_lock` api to actually verify + let voting = VotingOf::::get(&locker); + assert_eq!(voting.locked_balance(), BalanceOf::::zero()); + } + + // Test when unlock will set a new value + unlock_set { + let r in 1 .. MAX_REFERENDUMS; + + let locker = funded_account::("locker", 0); + // Populate votes so things are locked + let base_balance: BalanceOf = 100u32.into(); + let small_vote = account_vote::(base_balance); + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; + } + + // Create a big vote so lock increases + let big_vote = account_vote::(base_balance * 10u32.into()); + let referendum_index = add_referendum::(r)?; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), referendum_index, big_vote)?; + + let votes = match VotingOf::::get(&locker) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded."); + + let voting = VotingOf::::get(&locker); + assert_eq!(voting.locked_balance(), base_balance * 10u32.into()); + + Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), referendum_index)?; + + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + }: unlock(RawOrigin::Signed(caller), locker.clone()) + verify { + let votes = match VotingOf::::get(&locker) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Vote was not removed"); + + let voting = VotingOf::::get(&locker); + // Note that we may want to add a `get_lock` api to actually verify + assert_eq!(voting.locked_balance(), base_balance); + } + + remove_vote { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", 0); + let account_vote = account_vote::(100u32.into()); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes not created"); + + let referendum_index = r - 1; + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller.clone()), referendum_index) + verify { + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); + } + + // Worst case is when target == caller and referendum is ongoing + remove_other_vote { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", r); + let account_vote = account_vote::(100u32.into()); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes not created"); + + let referendum_index = r - 1; + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller.clone()), caller.clone(), referendum_index) + verify { + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); + } + + #[extra] + enact_proposal_execute { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + let proposer = funded_account::("proposer", 0); + let raw_call = Call::note_preimage { encoded_proposal: vec![1; b as usize] }; + let generic_call: T::Proposal = raw_call.into(); + let encoded_proposal = generic_call.encode(); + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; + + match Preimages::::get(proposal_hash) { + Some(PreimageStatus::Available { .. }) => (), + _ => return Err("preimage not available".into()) + } + }: enact_proposal(RawOrigin::Root, proposal_hash, 0) + verify { + // Fails due to mismatched origin + assert_last_event::(Event::::Executed(0, Err(BadOrigin.into())).into()); + } + + #[extra] + enact_proposal_slash { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + let proposer = funded_account::("proposer", 0); + // Random invalid bytes + let encoded_proposal = vec![200; b as usize]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; + + match Preimages::::get(proposal_hash) { + Some(PreimageStatus::Available { .. }) => (), + _ => return Err("preimage not available".into()) + } + let origin = RawOrigin::Root.into(); + let call = Call::::enact_proposal { proposal_hash, index: 0 }.encode(); + }: { + assert_eq!( + as Decode>::decode(&mut &*call) + .expect("call is encoded above, encoding must be correct") + .dispatch_bypass_filter(origin), + Err(Error::::PreimageInvalid.into()) + ); + } + + impl_benchmark_test_suite!( + Democracy, + crate::tests::new_test_ext(), + crate::tests::Test + ); +} diff --git a/frame/referenda/src/conviction.rs b/frame/referenda/src/conviction.rs new file mode 100644 index 0000000000000..b4f24c93bb40f --- /dev/null +++ b/frame/referenda/src/conviction.rs @@ -0,0 +1,119 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The conviction datatype. + +use crate::types::Delegations; +use codec::{Decode, Encode}; +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)] +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 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 for Conviction { + type Error = (); + fn try_from(i: u8) -> Result { + 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 + Zero + Copy + CheckedMul + CheckedDiv + Bounded>( + self, + capital: B, + ) -> Delegations { + 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 + } +} diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs new file mode 100644 index 0000000000000..e638cf7d3be45 --- /dev/null +++ b/frame/referenda/src/lib.rs @@ -0,0 +1,1173 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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.0sp_runtime::{DispatchResult, traits::One}asp_runtime::{DispatchResult, traits::AtLeast32BitUnsigned} 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. + +//! # Assembly Pallet +//! +//! - [`Config`] +//! - [`Call`] +//! +//! ## Overview +//! +//! The Assembly pallet handles the administration of general stakeholder voting. + +#![recursion_limit = "256"] +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, Input, Codec}; +use frame_support::{ + ensure, BoundedVec, weights::Weight, traits::{ + schedule::{DispatchTime, Named as ScheduleNamed}, + BalanceStatus, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, + ReservableCurrency, WithdrawReasons, + }, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Bounded, Dispatchable, Hash, Saturating, Zero, AtLeast32BitUnsigned, One}, + ArithmeticError, DispatchError, DispatchResult, RuntimeDebug, PerThing, 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, ReferendumInfo, ReferendumStatus, Tally, UnvoteScope, TrackInfo, TracksInfo, + Curve, DecidingStatus, Deposit, AtOrAfter, +}; +pub use vote::{AccountVote, Vote, Voting}; +pub use weights::WeightInfo; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +const ASSEMBLY_ID: LockIdentifier = *b"assembly"; + +/// A referendum index. +pub type ReferendumIndex = u32; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +type NegativeImbalanceOf = <::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; +type ReferendumInfoOf = ReferendumInfo< + TrackIdOf, + ::Origin, + ::BlockNumber, + ::Hash, + BalanceOf, + BalanceOf, + ::AccountId, +>; +type ReferendumStatusOf = ReferendumStatus< + TrackIdOf, + ::Origin, + ::BlockNumber, + ::Hash, + BalanceOf, + BalanceOf, + ::AccountId, +>; +type VotingOf = Voting< + BalanceOf, + ::AccountId, + ::BlockNumber, +>; +type TracksInfoOf = TracksInfo< + BalanceOf, + ::BlockNumber, +>; +type TrackInfoOf = TrackInfo< + BalanceOf, + ::BlockNumber, +>; +type TrackIdOf = TracksInfoOf::Id; + +pub trait InsertSorted { + fn insert_sorted_by_key(&mut self, t: T) -> bool; +} +impl> InsertSorted for BoundedVec { + fn insert_sorted_by_key< + F: FnMut(&T) -> K, + K: PartialOrd, + >(&mut self, t: T, f: F,) -> Result<(), ()> { + let index = self.binary_search_by_key(&t, f).unwrap_or_else(|x| x); + if index >= S::get() as usize { + return Err(()) + } + self.truncate(S::get() as usize - 1); + self.insert(index, t); + Ok(()) + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + dispatch::DispatchResultWithPostInfo, + pallet_prelude::*, + traits::EnsureOrigin, + weights::{DispatchClass, Pays}, + Parameter, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::DispatchResult; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + Sized { + // System level stuff. + type Proposal: Parameter + Dispatchable + From>; + type Event: From> + IsType<::Event>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + /// The Scheduler. + type Scheduler: ScheduleNamed; + /// Currency type for this pallet. + type Currency: ReservableCurrency + + LockableCurrency; + + // Origins and unbalances. + /// Origin from which any vote may be cancelled. + type CancelOrigin: EnsureOrigin; + /// Origin from which any vote may be killed. + type KillOrigin: EnsureOrigin; + /// Handler for the unbalanced reduction when slashing a preimage deposit. + type Slash: OnUnbalanced>; + + // Constants + /// The minimum amount to be used as a deposit for a public referendum proposal. + #[pallet::constant] + type SubmissionDeposit: Get>; + + /// 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; + + /// Maximum size of the referendum queue for a single track. + #[pallet::constant] + type MaxQueued: Get; + + /// The number of blocks after submission that a referendum must begin being decided by. + /// Once this passes, then anyone may cancel the referendum. + #[pallet::constant] + type UndecidingTimeout: Get; + + /// 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; + + /// Quantization level for the referendum wakeup scheduler. A higher number will result in + /// fewer storage reads/writes needed for smaller voters, but also result in delays to the + /// automatic referendum status changes. Explicit servicing instructions are unaffected. + #[pallet::constant] + type AlarmInterval: Get; + + // The other stuff. + /// Information concerning the different referendum tracks. + type Tracks: TracksInfo, Self::BlockNumber, Origin = Self::Origin>; + } + + /// The next free referendum index, aka the number of referenda started so far. + #[pallet::storage] + pub type ReferendumCount = StorageValue<_, ReferendumIndex, ValueQuery>; + + /// The sorted list of referenda + /// ready to be decided but not yet being decided, ordered by conviction-weighted approvals. + /// + /// This should be empty if `DecidingCount` is less than `TrackInfo::max_deciding`. + #[pallet::storage] + pub type TrackQueue = StorageMap< + _, + Twox64Concat, + TrackIdOf, + BoundedVec<(ReferendumIndex, BalanceOf), T::MaxQueued>, + ValueQuery, + >; + + /// The number of referenda being decided currently. + #[pallet::storage] + pub type DecidingCount = StorageMap< + _, + Twox64Concat, + TrackIdOf, + u32, + ValueQuery, + >; + + /// Information concerning any given referendum. + /// + /// TWOX-NOTE: SAFE as indexes are not under an attacker’s control. + #[pallet::storage] + pub type ReferendumInfoFor = StorageMap< + _, + Twox64Concat, + ReferendumIndex, + ReferendumInfoOf, + >; + + /// All votes for a particular voter. We store the balance for the number of votes that we + /// have recorded. The second item is the total amount of delegations, that will be added. + /// + /// TWOX-NOTE: SAFE as `AccountId`s are crypto hashes anyway. + #[pallet::storage] + pub type VotingFor = StorageMap< + _, + Twox64Concat, + T::AccountId, + VotingOf, + 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 = StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + _phantom: sp_std::marker::PhantomData, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { _phantom: Default::default() } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + ReferendumCount::::put(0 as ReferendumIndex); + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A referendum has being submitted. + Submitted { + /// Index of the referendum. + index: ReferendumIndex, + /// The track (and by extension proposal dispatch origin) of this referendum. + track: TrackIdOf, + /// The hash of the proposal up for referendum. + proposal_hash: T::Hash, + }, + /// A referendum has moved into the deciding phase. + StartedDeciding { + /// Index of the referendum. + index: ReferendumIndex, + /// The track (and by extension proposal dispatch origin) of this referendum. + track: TrackIdOf, + /// The hash of the proposal up for referendum. + proposal_hash: T::Hash, + /// The current tally of votes in this referendum. + tally: Tally>, + }, + /// A referendum has ended its confirmation phase and is ready for approval. + Confirmed { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: Tally>, + }, + /// A referendum has been approved and its proposal has been scheduled. + Approved { + /// Index of the referendum. + index: ReferendumIndex, + }, + /// A proposal has been rejected by referendum. + Rejected { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: Tally>, + }, + /// A referendum has been timed out without being decided. + TimedOut { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: Tally>, + }, + /// A referendum has been cancelled. + Cancelled { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: Tally>, + }, + /// A referendum has been killed. + Killed { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: Tally>, + }, + /// 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 { + /// Value too low + ValueLow, + /// Referendum is not ongoing. + NotOngoing, + /// Referendum's decision deposit is already paid. + HaveDeposit, + /// Proposal does not exist + ProposalMissing, + /// Cannot cancel the same proposal twice + AlreadyCanceled, + /// Proposal already made + DuplicateProposal, + /// Vote given for invalid referendum + ReferendumInvalid, + /// The given account did not vote on the referendum. + NotVoter, + /// The actor has no permission to conduct the action. + NoPermission, + /// The account is already delegating. + AlreadyDelegating, + /// Too high a balance was provided that the account cannot afford. + InsufficientFunds, + /// The account is not currently delegating. + NotDelegating, + /// The account currently has votes attached to it and the operation cannot succeed until + /// these are removed, either through `unvote` or `reap_vote`. + VotesExist, + /// Delegation to oneself makes no sense. + Nonsense, + /// Invalid upper bound. + WrongUpperBound, + /// Maximum number of votes reached. + MaxVotesReached, + /// The track identifier given was invalid. + BadTrack, + /// There are already a full complement of referendums in progress for this track. + Full, + /// The queue of the track is empty. + QueueEmpty, + /// The referendum index provided is invalid in this context. + BadReferendum, + /// There was nothing to do in the advancement. + NothingToDo, + } + + // TODO: bans + + #[pallet::call] + impl Pallet { + /// Propose a referendum on a privileged action. + /// + /// The dispatch origin of this call must be _Signed_ and the sender must + /// have funds to cover the deposit. + /// + /// - `proposal_origin`: The origin from which the proposal should be executed. + /// - `proposal_hash`: The hash of the proposal preimage. + /// - `enactment_moment`: The moment that the proposal should be enacted. + /// + /// Emits `Submitted`. + #[pallet::weight(T::WeightInfo::propose())] + pub fn submit( + origin: OriginFor, + proposal_origin: T::Origin, + proposal_hash: T::Hash, + enactment_moment: AtOrAfter, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let index = ReferendumCount::::mutate(|x| { let r = *x; *x += 1; r }); + let track = T::Tracks::track_for(&proposal_origin); + let status = ReferendumStatus { + track, + origin: proposal_origin, + proposal_hash: proposal_hash.clone(), + enactment: enactment_moment, + submitted: frame_system::Pallet::::block_number(), + submission_deposit: Self::take_deposit(who, T::SubmissionDeposit::get())?, + decision_deposit: None, + deciding: None, + tally: Tally::default(), + ayes_in_queue: None, + }; + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + + Self::deposit_event(Event::::Submitted { index, track, proposal_hash }); + Ok(()) + } + + #[pallet::weight(0)] + pub fn place_decision_deposit( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let status = Self::ensure_ongoing(index)?; + ensure!(status.decision_deposit.is_none(), Error::::HaveDeposit); + let track = Self::track(status.track); + status.decision_deposit = Self::take_deposit(who, track.decision_deposit)?; + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + let now = frame_system::Pallet::::block_number(); + Self::service_referendum(now, index, status); + Ok(()) + } + + #[pallet::weight(0)] + pub fn refund_decision_deposit( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResult { + ensure_signed_or_root(origin)?; + let mut info = ReferendumInfoFor::::get(index).ok_or(Error::::BadReferendum)?; + Self::refund_deposit(info.take_decision_deposit()); + ReferendumInfoFor::::insert(index, info); + Ok(()) + } + + /// + #[pallet::weight(0)] + pub fn consider_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { + ensure_signed_or_root(origin)?; + Self::do_consider_referendum(index)?; + Ok(Pays::No.into()) + } + + #[pallet::weight(0)] + pub fn advance_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { + ensure_signed_or_root(origin)?; + Self::do_advance_referendum(index)?; + Ok(Pays::No.into()) + } + + #[pallet::weight(0)] + pub fn advance_track(origin: OriginFor, track: TrackIdOf) -> DispatchResult { + ensure_signed_or_root(origin)?; + Self::do_advance_track(track)?; + Ok(Pays::No.into()) + } + + #[pallet::weight(0)] + pub fn pass_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { + ensure_signed(origin)?; + let info = ReferendumInfoFor::::get(index); + let mut status = match info { + ReferendumInfo::Confirmed(status) => status, + _ => return Err(()), + }; + Self::refund_deposit(status.decision_deposit.take()); + + // Enqueue proposal + let now = frame_system::Pallet::::block_number(); + let track = Self::track(status.track)?; + let earliest_allowed = now.saturating_add(track.min_enactment_period); + let desired = status.enactment.evaluate(now); + let enactment = desired.max(earliest_allowed); + let ok = T::Scheduler::schedule_named( + (ASSEMBLY_ID, index).encode(), + DispatchTime::At(enactment), + None, + 63, + status.origin, + Call::stub { call_hash: status.proposal_hash }.into(), + ).is_ok(); + debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed"); + + Self::deposit_event(Event::::Approved { index }); + + let info = ReferendumInfo::Approved(status.submission_deposit); + ReferendumStatusOf::::insert(index, info); + + Ok(Pays::No.into()) + } + + /// Just a stub - not meant to do anything. + #[pallet::weight(0)] + pub fn stub(_origin: OriginFor, _call_hash: T::Hash) -> DispatchResult { Ok(()) } + } +} + +impl Pallet { + pub fn ensure_ongoing(index: ReferendumIndex) -> Result, DispatchError> { + match ReferendumInfoFor::::get(index) { + ReferendumInfo::Ongoing(status) => Ok(status), + _ => Err(Error::::NotOngoing.into()), + } + } + + fn set_referendum_alarm(index: ReferendumIndex, when: T::BlockNumber) -> bool { + let scheduler_id = (ASSEMBLY_ID, "referendum", index).encode(); + Self::set_alarm(scheduler_id, Call::advance_referendum { index }, when) + } + + fn set_track_alarm(track: TrackIdOf, when: T::BlockNumber) -> bool { + let scheduler_id = (ASSEMBLY_ID, "track", track).encode(); + Self::set_alarm(scheduler_id, Call::advance_track { track }, when) + } + + /// Returns `false` if there is no change. + fn set_alarm(id: Vec, call: impl Into, when: T::BlockNumber) -> bool { + let alarm_interval = T::AlarmInterval::get(); + let when = (when / alarm_interval + 1) * alarm_interval; + if let Ok(t) = T::Scheduler::next_dispatch_time(id.clone()) { + if t == when { + return false + } + let ok = T::Scheduler::reschedule_named(id, when).is_ok(); + debug_assert!(ok, "Unable to reschedule an extant referendum?!"); + } else { + let _ = T::Scheduler::cancel_named(id.clone()); + let ok = T::Scheduler::schedule_named( + id, + when, + None, + 128u8, + frame_system::Origin::Root.into(), + call.into(), + ).is_ok(); + debug_assert!(ok, "Unable to schedule a new referendum?!"); + } + true + } + + fn ready_for_deciding( + now: T::BlockNumber, + track: &TrackInfoOf, + index: ReferendumIndex, + status: &mut ReferendumStatusOf, + ) { + let deciding_count = DecidingCount::::get(status.track); + if deciding_count < track.max_deciding { + // Begin deciding. + status.begin_deciding(now, track.decision_period); + DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); + } else { + // Add to queue. + let item = (index, status.tally.ayes); + TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)) + } + } + + /// Grab the index and status for the referendum which is the highest priority of those for the + /// given track which are ready for being decided. + fn next_for_deciding(track_queue: BoundedVec<(u32, BalanceOf), T::MaxQueued>) -> Option<(ReferendumIndex, ReferendumStatusOf)> { + loop { + let (index, _) = track_queue.pop()?; + match Self::ensure_ongoing(index) { + Ok(s) => return Some((index, s)), + Err() => debug_assert!(false, "Queued referendum not ongoing?!"), + } + } + } + + /// Advance a track - this dequeues one or more referenda from the from the `TrackQueue` of + /// referenda which are ready to be decided until the `DecidingCount` is equal to the track's + /// `max_deciding`. + fn do_advance_track(track: TrackIdOf) -> Result { + let track_info = Self::track(track).ok_or(Error::::BadTrack)?; + let deciding_count = DecidingCount::::get(track); + let track_queue = TrackQueue::::get(track); + let count = 0; + while deciding_count < track_info.max_deciding { + if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { + let now = frame_system::Pallet::::block_number(); + status.begin_deciding(now, track_info.decision_period); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + deciding_count.saturating_inc(); + count.saturating_inc(); + } else { + break + } + } + ensure!(count > 0, Error::::NothingToDo); + + DecidingCount::::insert(track, deciding_count); + TrackQueue::::insert(track, track_queue); + Ok(count) + } + + // TODO: if a vote results in `ayes_in_queue.map_or(false, |v| tally.ayes > v)` then schedule + // the referendum alarm to update `TrackQueue` - could either update entry's position or move + // index in. + + /// Applies only to referendums in the `TrackQueue`. + /// + /// Checks a referendum's aye votes and if above the lowest in its `TrackQueue` or if + /// `TrackQueue` is below capacity then inserts it into its `TrackQueue`. + fn do_consider_referendum(index: ReferendumIndex) -> DispatchResult { + let now = frame_system::Pallet::::block_number(); + let status = Self::ensure_ongoing(index)?; + if let Some(info) = Self::service_referendum(now, status) { + if let ReferendumInfo::Ongoing(&status) = info { + let when = Self::next_referendum_advance(&status).max(now + One::one()); + Self::set_referendum_alarm(index, when); + } + ReferendumInfoFor::::insert(index, info); + } + } + + // TODO: If a vote results in `ayes_in_queue.map_or(false, |v| tally.ayes < v)` then it must + // include a reorder of the queue. + + /// Applies only to referendums in the `TrackQueue`. + /// + /// Updates the referendum's aye votes into `ayes_in_queue` and into `TrackQueue` and then + /// sort the queue. + fn do_reconsider_referendum(index: ReferendumIndex) -> DispatchResult { + let now = frame_system::Pallet::::block_number(); + let status = Self::ensure_ongoing(index)?; + if let Some(info) = Self::service_referendum(now, status) { + if let ReferendumInfo::Ongoing(&status) = info { + let when = Self::next_referendum_advance(&status).max(now + One::one()); + Self::set_referendum_alarm(index, when); + } + ReferendumInfoFor::::insert(index, info); + } + } + + /// Attempts to advance the referendum. Returns `Ok` if something useful happened. + fn do_advance_referendum(index: ReferendumIndex) -> DispatchResult { + let now = frame_system::Pallet::::block_number(); + let status = Self::ensure_ongoing(index)?; + if let Some(info) = Self::service_referendum(now, status) { + if let ReferendumInfo::Ongoing(&status) = info { + let when = Self::next_referendum_advance(&status).max(now + One::one()); + Self::set_referendum_alarm(index, when); + } + ReferendumInfoFor::::insert(index, info); + } + } + + /// Advance the state of a referendum, which comes down to: + /// - If it's ready to be decided, start deciding; + /// - If it's not ready to be decided and non-deciding timeout has passed, fail; + /// - If it's ongoing and passing, ensure confirming; if at end of confirmation period, pass. + /// - If it's ongoing and not passing, stop confirning; if it has reached end time, fail. + /// + /// Weight will be a bit different depending on what it does, but if designed so as not to + /// differ dramatically. In particular there are no balance operations. + fn service_referendum( + now: T::BlockNumber, + index: ReferendumIndex, + mut status: ReferendumStatusOf, + ) -> Option> { + // Should it begin being decided? + let track = Self::track(status.track)?; + if status.deciding.is_none() && status.decision_deposit.is_some() { + if now.saturating_sub(status.submitted) >= track.prepare_period { + Self::ready_for_deciding(now, &track, &mut status); + } + } + if let Some(deciding) = &mut status.deciding { + let is_passing = status.tally.is_passing( + now, + deciding.period, + T::Currency::total_issuance(), + track.min_turnout, + track.min_approvals, + ); + if let Some(confirming) = deciding.confirming { + if is_passing && confirming >= now { + // Passed! + DecidingCount::::mutate(status.track, |x| x.saturating_dec()); + Self::deposit_event(Event::::Confirmed { index, tally: status.tally }); + return Some(ReferendumInfo::Confirmed(status)) + } else if !is_passing { + // Move back out of confirming + deciding.confirming = None; + } + } + if deciding.confirming.is_none() { + if is_passing { + // Begin confirming + deciding.confirming = Some(now.saturating_add(track.confirm_period)); + } else if now >= deciding.ending { + // Failed! + DecidingCount::::mutate(status.track, |x| x.saturating_dec()); + Self::deposit_event(Event::::Rejected { index, tally: status.tally }); + return Some(ReferendumInfo::Rejected(status.submission_deposit, status.decision_deposit)) + } + } + } else if status.submitted.saturating_add(T::UndecidingTimeout::get()) >= now { + // Too long without being decided. + Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); + return Some(ReferendumInfo::TimedOut(status.submission_deposit, status.decision_deposit)) + } + Some(ReferendumInfo::Ongoing(status)) + } + + /// Reserve a deposit and return the `Deposit` instance. + fn take_deposit(who: T::AccountId, amount: BalanceOf) + -> Result>, DispatchError> + { + T::Currency::reserve(&who, amount)?; + Ok(Deposit { who, amount }) + } + + /// Return a deposit, if `Some`. + fn refund_deposit(deposit: Option>>) { + if let Some(Deposit { who, amount }) = deposit { + T::Currency::unreserve(&who, amount); + } + } + + /// Returns the earliest block that advancing this referendum should do something useful. + pub fn next_referendum_advance(status: &ReferendumStatusOf) -> T::BlockNumber { + let now = frame_system::Pallet::::block_number(); + let track = match Self::track(status.track) { + Some(t) => t, + None => return now, + }; + if let Some(deciding) = status.deciding { + if let Some(confirming) = deciding.confirming { + confirming + } else { + let mut t = deciding.ending; + let approvals = Perbill::from_rational(status.tally.ayes, status.tally.ayes + status.tally.nays); + let turnout = Perbill::from_rational(status.tally.turnout, T::Currency::total_issuance()); + let until_approvals = track.min_approvals.delay(approvals) * deciding.period; + let until_turnout = track.min_turnout.delay(turnout) * deciding.period; + t.min(until_turnout.max(until_approvals)) + } + } else { + if status.decision_deposit.is_some() { + let prepare_end = status.submitted.saturating_add(track.prepare_period); + if now < prepare_end { + prepare_end + } else { + // Not yet bumped or too many being decided. + // Track should already be scheduled for a bump. + status.submitted + T::UndecidingTimeout::get() + } + } else { + status.submitted + T::UndecidingTimeout::get() + } + } + } + + fn track(id: TrackIdOf) -> Option<&'static TrackInfoOf> { + let tracks = T::Tracks::tracks(); + let index = tracks.binary_search_by_key(&id, |x| x.0) + .unwrap_or_else(|x| x); + tracks[index].1 + } +} + +/* + /// Vote in a referendum. If `vote.is_aye()`, the vote is to enact the proposal; + /// otherwise it is a vote to keep the status quo. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `ref_index`: The index of the referendum to vote for. + /// - `vote`: The vote configuration. + /// + /// Weight: `O(R)` where R is the number of referenda the voter has voted on. + #[pallet::weight( + T::WeightInfo::vote_new(T::MaxVotes::get()) + .max(T::WeightInfo::vote_existing(T::MaxVotes::get())) + )] + pub fn vote( + origin: OriginFor, + #[pallet::compact] ref_index: ReferendumIndex, + vote: AccountVote>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::try_vote(&who, ref_index, vote) + } + + /// Delegate the voting power (with some given conviction) of the sending account. + /// + /// The balance delegated is locked for as long as it's delegated, and thereafter for the + /// time appropriate for the conviction's lock period. + /// + /// The dispatch origin of this call must be _Signed_, and the signing account must either: + /// - be delegating already; or + /// - have no voting activity (if there is, then it will need to be removed/consolidated + /// through `reap_vote` or `unvote`). + /// + /// - `to`: The account whose voting the `target` account's voting power will follow. + /// - `conviction`: The conviction that will be attached to the delegated votes. When the + /// account is undelegated, the funds will be locked for the corresponding period. + /// - `balance`: The amount of the account's balance to be used in delegating. This must not + /// be more than the account's current balance. + /// + /// Emits `Delegated`. + /// + /// Weight: `O(R)` where R is the number of referenda the voter delegating to has + /// voted on. Weight is charged as if maximum votes. + // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure + // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] + pub fn delegate( + origin: OriginFor, + to: T::AccountId, + conviction: Conviction, + balance: BalanceOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let votes = Self::try_delegate(who, to, conviction, balance)?; + + Ok(Some(T::WeightInfo::delegate(votes)).into()) + } + + /// Undelegate the voting power of the sending account. + /// + /// Tokens may be unlocked following once an amount of time consistent with the lock period + /// of the conviction with which the delegation was issued. + /// + /// The dispatch origin of this call must be _Signed_ and the signing account must be + /// currently delegating. + /// + /// Emits `Undelegated`. + /// + /// Weight: `O(R)` where R is the number of referenda the voter delegating to has + /// voted on. Weight is charged as if maximum votes. + // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure + // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] + pub fn undelegate(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let votes = Self::try_undelegate(who)?; + Ok(Some(T::WeightInfo::undelegate(votes)).into()) + } + + /// Unlock tokens that have an expired lock. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `target`: The account to remove the lock on. + /// + /// Weight: `O(R)` with R number of vote of target. + #[pallet::weight( + T::WeightInfo::unlock_set(T::MaxVotes::get()) + .max(T::WeightInfo::unlock_remove(T::MaxVotes::get())) + )] + pub fn unlock(origin: OriginFor, target: T::AccountId) -> DispatchResult { + ensure_signed(origin)?; + Self::update_lock(&target); + Ok(()) + } + + /// Remove a vote for a referendum. + /// + /// If: + /// - the referendum was cancelled, or + /// - the referendum is ongoing, or + /// - the referendum has ended such that + /// - the vote of the account was in opposition to the result; or + /// - there was no conviction to the account's vote; or + /// - the account made a split vote + /// ...then the vote is removed cleanly and a following call to `unlock` may result in more + /// funds being available. + /// + /// If, however, the referendum has ended and: + /// - it finished corresponding to the vote of the account, and + /// - the account made a standard vote with conviction, and + /// - the lock period of the conviction is not over + /// ...then the lock will be aggregated into the overall account's lock, which may involve + /// *overlocking* (where the two locks are combined into a single lock that is the maximum + /// of both the amount locked and the time is it locked for). + /// + /// The dispatch origin of this call must be _Signed_, and the signer must have a vote + /// registered for referendum `index`. + /// + /// - `index`: The index of referendum of the vote to be removed. + /// + /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// Weight is calculated for the maximum number of vote. + #[pallet::weight(T::WeightInfo::remove_vote(T::MaxVotes::get()))] + pub fn remove_vote(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::try_remove_vote(&who, index, UnvoteScope::Any) + } + + /// Remove a vote for a referendum. + /// + /// If the `target` is equal to the signer, then this function is exactly equivalent to + /// `remove_vote`. If not equal to the signer, then the vote must have expired, + /// either because the referendum was cancelled, because the voter lost the referendum or + /// because the conviction period is over. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `target`: The account of the vote to be removed; this account must have voted for + /// referendum `index`. + /// - `index`: The index of referendum of the vote to be removed. + /// + /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// Weight is calculated for the maximum number of vote. + #[pallet::weight(T::WeightInfo::remove_other_vote(T::MaxVotes::get()))] + pub fn remove_other_vote( + origin: OriginFor, + target: T::AccountId, + index: ReferendumIndex, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; + Self::try_remove_vote(&target, index, scope)?; + Ok(()) + } + +impl Pallet { + /// Actually enact a vote, if legit. + fn try_vote( + who: &T::AccountId, + ref_index: ReferendumIndex, + vote: AccountVote>, + ) -> DispatchResult { + let mut status = Self::referendum_status(ref_index)?; + ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); + VotingFor::::try_mutate(who, |voting| -> DispatchResult { + if let Voting::Direct { ref mut votes, delegations, .. } = voting { + match votes.binary_search_by_key(&ref_index, |i| i.0) { + Ok(i) => { + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; + if let Some(approve) = votes[i].1.as_standard() { + status.tally.reduce(approve, *delegations); + } + votes[i].1 = vote; + }, + Err(i) => { + ensure!( + votes.len() as u32 <= T::MaxVotes::get(), + Error::::MaxVotesReached + ); + votes.insert(i, (ref_index, vote)); + }, + } + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.add(vote).ok_or(ArithmeticError::Overflow)?; + if let Some(approve) = vote.as_standard() { + status.tally.increase(approve, *delegations); + } + Ok(()) + } else { + Err(Error::::AlreadyDelegating.into()) + } + })?; + // Extend the lock to `balance` (rather than setting it) since we don't know what other + // votes are in place. + T::Currency::extend_lock(ASSEMBLY_ID, who, vote.balance(), WithdrawReasons::TRANSFER); + ReferendumInfoFor::::insert(ref_index, ReferendumInfo::Ongoing(status)); + Ok(()) + } + + /// Remove the account's vote for the given referendum if possible. This is possible when: + /// - The referendum has not finished. + /// - The referendum has finished and the voter lost their direction. + /// - The referendum has finished and the voter's lock period is up. + /// + /// This will generally be combined with a call to `unlock`. + fn try_remove_vote( + who: &T::AccountId, + ref_index: ReferendumIndex, + scope: UnvoteScope, + ) -> DispatchResult { + let info = ReferendumInfoFor::::get(ref_index); + VotingFor::::try_mutate(who, |voting| -> DispatchResult { + if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting { + let i = votes + .binary_search_by_key(&ref_index, |i| i.0) + .map_err(|_| Error::::NotVoter)?; + match info { + Some(ReferendumInfo::Ongoing(mut status)) => { + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; + if let Some(approve) = votes[i].1.as_standard() { + status.tally.reduce(approve, *delegations); + } + ReferendumInfoFor::::insert(ref_index, ReferendumInfo::Ongoing(status)); + }, + Some(ReferendumInfo::Finished { end, approved }) => { + if let Some((lock_periods, balance)) = votes[i].1.locked_if(approved) { + let unlock_at = end + T::VoteLockingPeriod::get() * lock_periods.into(); + let now = frame_system::Pallet::::block_number(); + if now < unlock_at { + ensure!( + matches!(scope, UnvoteScope::Any), + Error::::NoPermission + ); + prior.accumulate(unlock_at, balance) + } + } + }, + None => {}, // Referendum was cancelled. + } + votes.remove(i); + } + Ok(()) + })?; + Ok(()) + } + + /// Return the number of votes for `who` + fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { + VotingFor::::mutate(who, |voting| match voting { + Voting::Delegating { delegations, .. } => { + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_add(amount); + 1 + }, + Voting::Direct { votes, delegations, .. } => { + *delegations = delegations.saturating_add(amount); + for &(ref_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + ReferendumInfoFor::::mutate(ref_index, |maybe_info| { + if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { + status.tally.increase(vote.aye, amount); + } + }); + } + } + votes.len() as u32 + }, + }) + } + + /// Return the number of votes for `who` + fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { + VotingFor::::mutate(who, |voting| match voting { + Voting::Delegating { delegations, .. } => { + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_sub(amount); + 1 + }, + Voting::Direct { votes, delegations, .. } => { + *delegations = delegations.saturating_sub(amount); + for &(ref_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + ReferendumInfoFor::::mutate(ref_index, |maybe_info| { + if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { + status.tally.reduce(vote.aye, amount); + } + }); + } + } + votes.len() as u32 + }, + }) + } + + /// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`. + /// + /// Return the upstream number of votes. + fn try_delegate( + who: T::AccountId, + target: T::AccountId, + conviction: Conviction, + balance: BalanceOf, + ) -> Result { + ensure!(who != target, Error::::Nonsense); + ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); + let votes = VotingFor::::try_mutate(&who, |voting| -> Result { + let mut old = Voting::Delegating { + balance, + target: target.clone(), + conviction, + delegations: Default::default(), + prior: Default::default(), + }; + sp_std::mem::swap(&mut old, voting); + match old { + Voting::Delegating { balance, target, conviction, delegations, prior, .. } => { + // remove any delegation votes to our current target. + Self::reduce_upstream_delegation(&target, conviction.votes(balance)); + voting.set_common(delegations, prior); + }, + Voting::Direct { votes, delegations, prior } => { + // here we just ensure that we're currently idling with no votes recorded. + ensure!(votes.is_empty(), Error::::VotesExist); + voting.set_common(delegations, prior); + }, + } + let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance)); + // Extend the lock to `balance` (rather than setting it) since we don't know what other + // votes are in place. + T::Currency::extend_lock(ASSEMBLY_ID, &who, balance, WithdrawReasons::TRANSFER); + Ok(votes) + })?; + Self::deposit_event(Event::::Delegated(who, target)); + Ok(votes) + } + + /// Attempt to end the current delegation. + /// + /// Return the number of votes of upstream. + fn try_undelegate(who: T::AccountId) -> Result { + let votes = VotingFor::::try_mutate(&who, |voting| -> Result { + let mut old = Voting::default(); + sp_std::mem::swap(&mut old, voting); + match old { + Voting::Delegating { balance, target, conviction, delegations, mut prior } => { + // remove any delegation votes to our current target. + let votes = + Self::reduce_upstream_delegation(&target, conviction.votes(balance)); + let now = frame_system::Pallet::::block_number(); + let lock_periods = conviction.lock_periods().into(); + prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); + voting.set_common(delegations, prior); + + Ok(votes) + }, + Voting::Direct { .. } => Err(Error::::NotDelegating.into()), + } + })?; + Self::deposit_event(Event::::Undelegated(who)); + Ok(votes) + } + + /// Rejig the lock on an account. It will never get more stringent (since that would indicate + /// a security hole) but may be reduced from what they are currently. + fn update_lock(who: &T::AccountId) { + let lock_needed = VotingFor::::mutate(who, |voting| { + voting.rejig(frame_system::Pallet::::block_number()); + voting.locked_balance() + }); + if lock_needed.is_zero() { + T::Currency::remove_lock(ASSEMBLY_ID, who); + } else { + T::Currency::set_lock(ASSEMBLY_ID, who, lock_needed, WithdrawReasons::TRANSFER); + } + } +} +*/ \ No newline at end of file diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs new file mode 100644 index 0000000000000..06c4ac666cfba --- /dev/null +++ b/frame/referenda/src/tests.rs @@ -0,0 +1,302 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The crate's tests. + +use super::*; +use crate as pallet_democracy; +use codec::Encode; +use frame_support::{ + assert_noop, assert_ok, ord_parameter_types, parameter_types, + traits::{Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers}, + weights::Weight, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use pallet_balances::{BalanceLock, Error as BalancesError}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + Perbill, +}; + +mod cancellation; +mod decoders; +mod delegation; +mod external_proposing; +mod fast_tracking; +mod lock_voting; +mod preimage; +mod public_proposals; +mod scheduling; +mod voting; + +const AYE: Vote = Vote { aye: true, conviction: Conviction::None }; +const NAY: Vote = Vote { aye: false, conviction: Conviction::None }; +const BIG_AYE: Vote = Vote { aye: true, conviction: Conviction::Locked1x }; +const BIG_NAY: Vote = Vote { aye: false, conviction: Conviction::Locked1x }; + +const MAX_PROPOSALS: u32 = 100; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, + Democracy: pallet_democracy::{Pallet, Call, Storage, Config, Event}, + } +); + +// Test that a fitlered call can be dispatched. +pub struct BaseFilter; +impl Contains 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; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; +} +impl pallet_scheduler::Config for Test { + type Event = Event; + type Origin = Origin; + type PalletsOrigin = OriginCaller; + type Call = Call; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureRoot; + type MaxScheduledPerBlock = (); + type WeightInfo = (); + type OriginPrivilegeCmp = EqualPrivilegeOnly; +} +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 const LaunchPeriod: u64 = 2; + pub const VotingPeriod: u64 = 2; + pub const FastTrackVotingPeriod: u64 = 2; + pub const MinimumDeposit: u64 = 1; + pub const EnactmentPeriod: u64 = 2; + pub const VoteLockingPeriod: u64 = 3; + pub const CooloffPeriod: u64 = 2; + pub const MaxVotes: u32 = 100; + pub const MaxProposals: u32 = MAX_PROPOSALS; + pub static PreimageByteDeposit: u64 = 0; + pub static InstantAllowed: bool = false; +} +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 for OneToFive { + fn sorted_members() -> Vec { + vec![1, 2, 3, 4, 5] + } + #[cfg(feature = "runtime-benchmarks")] + fn add(_m: &u64) {} +} + +impl Config for Test { + type Proposal = Call; + type Event = Event; + type Currency = pallet_balances::Pallet; + type EnactmentPeriod = EnactmentPeriod; + type LaunchPeriod = LaunchPeriod; + type VotingPeriod = VotingPeriod; + type VoteLockingPeriod = VoteLockingPeriod; + type FastTrackVotingPeriod = FastTrackVotingPeriod; + type MinimumDeposit = MinimumDeposit; + type ExternalOrigin = EnsureSignedBy; + type ExternalMajorityOrigin = EnsureSignedBy; + type ExternalDefaultOrigin = EnsureSignedBy; + type FastTrackOrigin = EnsureSignedBy; + type CancellationOrigin = EnsureSignedBy; + type BlacklistOrigin = EnsureRoot; + type CancelProposalOrigin = EnsureRoot; + type VetoOrigin = EnsureSignedBy; + type CooloffPeriod = CooloffPeriod; + type PreimageByteDeposit = PreimageByteDeposit; + type Slash = (); + type InstantOrigin = EnsureSignedBy; + type InstantAllowed = InstantAllowed; + type Scheduler = Scheduler; + type MaxVotes = MaxVotes; + type OperationalPreimageOrigin = EnsureSignedBy; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); + type MaxProposals = MaxProposals; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + } + .assimilate_storage(&mut t) + .unwrap(); + pallet_democracy::GenesisConfig::::default() + .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`. +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)); +} + +#[test] +fn params_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Democracy::referendum_count(), 0); + assert_eq!(Balances::free_balance(42), 0); + assert_eq!(Balances::total_issuance(), 210); + }); +} + +fn set_balance_proposal(value: u64) -> Vec { + Call::Balances(pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 }) + .encode() +} + +#[test] +fn set_balance_proposal_is_correctly_filtered_out() { + for i in 0..10 { + let call = Call::decode(&mut &set_balance_proposal(i)[..]).unwrap(); + assert!(!::BaseCallFilter::contains(&call)); + } +} + +fn set_balance_proposal_hash(value: u64) -> H256 { + BlakeTwo256::hash(&set_balance_proposal(value)[..]) +} + +fn set_balance_proposal_hash_and_note(value: u64) -> H256 { + let p = set_balance_proposal(value); + let h = BlakeTwo256::hash(&p[..]); + match Democracy::note_preimage(Origin::signed(6), p) { + Ok(_) => (), + Err(x) if x == Error::::DuplicatePreimage.into() => (), + Err(x) => panic!("{:?}", x), + } + h +} + +fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { + Democracy::propose(Origin::signed(who), set_balance_proposal_hash(value), delay) +} + +fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchResult { + Democracy::propose(Origin::signed(who), set_balance_proposal_hash_and_note(value), delay) +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); + Scheduler::on_initialize(System::block_number()); + Democracy::begin_block(System::block_number()); +} + +fn fast_forward_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +fn begin_referendum() -> ReferendumIndex { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + fast_forward_to(2); + 0 +} + +fn aye(who: u64) -> AccountVote { + AccountVote::Standard { vote: AYE, balance: Balances::free_balance(&who) } +} + +fn nay(who: u64) -> AccountVote { + AccountVote::Standard { vote: NAY, balance: Balances::free_balance(&who) } +} + +fn big_aye(who: u64) -> AccountVote { + AccountVote::Standard { vote: BIG_AYE, balance: Balances::free_balance(&who) } +} + +fn big_nay(who: u64) -> AccountVote { + AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } +} + +fn tally(r: ReferendumIndex) -> Tally { + Democracy::referendum_status(r).unwrap().tally +} diff --git a/frame/referenda/src/tests/cancellation.rs b/frame/referenda/src/tests/cancellation.rs new file mode 100644 index 0000000000000..83822bf51829f --- /dev/null +++ b/frame/referenda/src/tests/cancellation.rs @@ -0,0 +1,96 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for cancelation functionality. + +use super::*; + +#[test] +fn cancel_referendum_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::cancel_referendum(Origin::root(), r.into())); + assert_eq!(Democracy::lowest_unbaked(), 0); + + next_block(); + + next_block(); + + assert_eq!(Democracy::lowest_unbaked(), 1); + assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn cancel_queued_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + // start of 2 => next referendum scheduled. + fast_forward_to(2); + + assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); + + fast_forward_to(4); + + assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); + + assert_noop!(Democracy::cancel_queued(Origin::root(), 1), Error::::ProposalMissing); + assert_ok!(Democracy::cancel_queued(Origin::root(), 0)); + assert!(pallet_scheduler::Agenda::::get(6)[0].is_none()); + }); +} + +#[test] +fn emergency_cancel_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 2, + ); + assert!(Democracy::referendum_status(r).is_ok()); + + assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); + assert_ok!(Democracy::emergency_cancel(Origin::signed(4), r)); + assert!(Democracy::referendum_info(r).is_none()); + + // some time later... + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 2, + ); + assert!(Democracy::referendum_status(r).is_ok()); + assert_noop!( + Democracy::emergency_cancel(Origin::signed(4), r), + Error::::AlreadyCanceled, + ); + }); +} diff --git a/frame/referenda/src/tests/decoders.rs b/frame/referenda/src/tests/decoders.rs new file mode 100644 index 0000000000000..3c1729c4355c0 --- /dev/null +++ b/frame/referenda/src/tests/decoders.rs @@ -0,0 +1,85 @@ +// 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. + +//! The for various partial storage decoders + +use super::*; +use frame_support::storage::{migration, unhashed}; + +#[test] +fn test_decode_compact_u32_at() { + new_test_ext().execute_with(|| { + let v = codec::Compact(u64::MAX); + migration::put_storage_value(b"test", b"", &[], v); + assert_eq!(decode_compact_u32_at(b"test"), None); + + for v in vec![0, 10, u32::MAX] { + let compact_v = codec::Compact(v); + unhashed::put(b"test", &compact_v); + assert_eq!(decode_compact_u32_at(b"test"), Some(v)); + } + + unhashed::kill(b"test"); + assert_eq!(decode_compact_u32_at(b"test"), None); + }) +} + +#[test] +fn len_of_deposit_of() { + new_test_ext().execute_with(|| { + for l in vec![0, 1, 200, 1000] { + let value: (Vec, u64) = ((0..l).map(|_| Default::default()).collect(), 3u64); + DepositOf::::insert(2, value); + assert_eq!(Democracy::len_of_deposit_of(2), Some(l)); + } + + DepositOf::::remove(2); + assert_eq!(Democracy::len_of_deposit_of(2), None); + }) +} + +#[test] +fn pre_image() { + new_test_ext().execute_with(|| { + let key = Default::default(); + let missing = PreimageStatus::Missing(0); + Preimages::::insert(key, missing); + assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); + assert_eq!(Democracy::check_pre_image_is_missing(key), Ok(())); + + Preimages::::remove(key); + assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); + assert_noop!(Democracy::check_pre_image_is_missing(key), Error::::NotImminent); + + for l in vec![0, 10, 100, 1000u32] { + let available = PreimageStatus::Available { + data: (0..l).map(|i| i as u8).collect(), + provider: 0, + deposit: 0, + since: 0, + expiry: None, + }; + + Preimages::::insert(key, available); + assert_eq!(Democracy::pre_image_data_len(key), Ok(l)); + assert_noop!( + Democracy::check_pre_image_is_missing(key), + Error::::DuplicatePreimage + ); + } + }) +} diff --git a/frame/referenda/src/tests/delegation.rs b/frame/referenda/src/tests/delegation.rs new file mode 100644 index 0000000000000..d3afa1c13f90b --- /dev/null +++ b/frame/referenda/src/tests/delegation.rs @@ -0,0 +1,179 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for functionality concerning delegation. + +use super::*; + +#[test] +fn single_proposal_should_work_with_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + // Delegate first vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + + // Delegate a second vote. + assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); + assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 60 }); + + // Reduce first vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); + assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 50 }); + + // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. + assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); + assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 20 }); + + // Main voter cancels their vote + assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + + // First delegator delegates half funds with conviction; nothing changes yet. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked1x, 10)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + + // Main voter reinstates their vote + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 20 }); + }); +} + +#[test] +fn self_delegation_not_allowed() { + new_test_ext().execute_with(|| { + assert_noop!( + Democracy::delegate(Origin::signed(1), 1, Conviction::None, 10), + Error::::Nonsense, + ); + }); +} + +#[test] +fn cyclic_delegation_should_unwind() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + // Check behavior with cycle. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); + assert_ok!(Democracy::delegate(Origin::signed(1), 3, Conviction::None, 10)); + let r = 0; + assert_ok!(Democracy::undelegate(Origin::signed(3))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); + assert_ok!(Democracy::undelegate(Origin::signed(1))); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); + + // Delegated vote is counted. + assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 60 }); + }); +} + +#[test] +fn single_proposal_should_work_with_vote_and_delegation() { + // If transactor already voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, nay(2))); + assert_eq!(tally(r), Tally { ayes: 1, nays: 2, turnout: 30 }); + + // Delegate vote. + assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + // Delegated vote replaces the explicit vote. + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + }); +} + +#[test] +fn single_proposal_should_work_with_undelegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + // Delegate and undelegate vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::undelegate(Origin::signed(2))); + + fast_forward_to(2); + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + // Delegated vote is not counted. + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + }); +} + +#[test] +fn single_proposal_should_work_with_delegation_and_vote() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + let r = begin_referendum(); + // Delegate, undelegate and vote. + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + assert_ok!(Democracy::undelegate(Origin::signed(2))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + // Delegated vote is not counted. + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + }); +} + +#[test] +fn conviction_should_be_honored_in_delegation() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + let r = begin_referendum(); + // Delegate, undelegate and vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + // Delegated vote is huge. + assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 }); + }); +} + +#[test] +fn split_vote_delegation_should_be_ignored() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + let r = begin_referendum(); + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); + assert_ok!(Democracy::vote(Origin::signed(1), r, AccountVote::Split { aye: 10, nay: 0 })); + // Delegated vote is huge. + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + }); +} diff --git a/frame/referenda/src/tests/external_proposing.rs b/frame/referenda/src/tests/external_proposing.rs new file mode 100644 index 0000000000000..7442964584fa9 --- /dev/null +++ b/frame/referenda/src/tests/external_proposing.rs @@ -0,0 +1,303 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for functionality concerning the "external" origin. + +use super::*; + +#[test] +fn veto_external_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert!(>::exists()); + + let h = set_balance_proposal_hash_and_note(2); + assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone())); + // cancelled. + assert!(!>::exists()); + // fails - same proposal can't be resubmitted. + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), + Error::::ProposalBlacklisted + ); + + fast_forward_to(1); + // fails as we're still in cooloff period. + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), + Error::::ProposalBlacklisted + ); + + fast_forward_to(2); + // works; as we're out of the cooloff period. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert!(>::exists()); + + // 3 can't veto the same thing twice. + assert_noop!( + Democracy::veto_external(Origin::signed(3), h.clone()), + Error::::AlreadyVetoed + ); + + // 4 vetoes. + assert_ok!(Democracy::veto_external(Origin::signed(4), h.clone())); + // cancelled again. + assert!(!>::exists()); + + fast_forward_to(3); + // same proposal fails as we're still in cooloff + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), + Error::::ProposalBlacklisted + ); + // different proposal works fine. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); + }); +} + +#[test] +fn external_blacklisting_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + + let hash = set_balance_proposal_hash(2); + assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); + + fast_forward_to(2); + assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); + + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash_and_note(2),), + Error::::ProposalBlacklisted, + ); + }); +} + +#[test] +fn external_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_noop!( + Democracy::external_propose(Origin::signed(1), set_balance_proposal_hash(2),), + BadOrigin, + ); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(1),), + Error::::DuplicateProposal + ); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn external_majority_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_noop!( + Democracy::external_propose_majority(Origin::signed(1), set_balance_proposal_hash(2)), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SimpleMajority, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn external_default_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_noop!( + Democracy::external_propose_default(Origin::signed(3), set_balance_proposal_hash(2)), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_default( + Origin::signed(1), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SuperMajorityAgainst, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn external_and_public_interleaving_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(1), + )); + assert_ok!(propose_set_balance_and_note(6, 2, 2)); + + fast_forward_to(2); + + // both waiting: external goes first. + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash_and_note(1), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); + + fast_forward_to(4); + + // both waiting: public goes next. + assert_eq!( + Democracy::referendum_status(1), + Ok(ReferendumStatus { + end: 6, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // don't replenish public + + fast_forward_to(6); + + // it's external "turn" again, though since public is empty that doesn't really matter + assert_eq!( + Democracy::referendum_status(2), + Ok(ReferendumStatus { + end: 8, + proposal_hash: set_balance_proposal_hash_and_note(3), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(5), + )); + + fast_forward_to(8); + + // external goes again because there's no public waiting. + assert_eq!( + Democracy::referendum_status(3), + Ok(ReferendumStatus { + end: 10, + proposal_hash: set_balance_proposal_hash_and_note(5), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish both + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(7), + )); + assert_ok!(propose_set_balance_and_note(6, 4, 2)); + + fast_forward_to(10); + + // public goes now since external went last time. + assert_eq!( + Democracy::referendum_status(4), + Ok(ReferendumStatus { + end: 12, + proposal_hash: set_balance_proposal_hash_and_note(4), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish public again + assert_ok!(propose_set_balance_and_note(6, 6, 2)); + // cancel external + let h = set_balance_proposal_hash_and_note(7); + assert_ok!(Democracy::veto_external(Origin::signed(3), h)); + + fast_forward_to(12); + + // public goes again now since there's no external waiting. + assert_eq!( + Democracy::referendum_status(5), + Ok(ReferendumStatus { + end: 14, + proposal_hash: set_balance_proposal_hash_and_note(6), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} diff --git a/frame/referenda/src/tests/fast_tracking.rs b/frame/referenda/src/tests/fast_tracking.rs new file mode 100644 index 0000000000000..9b2f2760bde1c --- /dev/null +++ b/frame/referenda/src/tests/fast_tracking.rs @@ -0,0 +1,98 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for fast-tracking functionality. + +use super::*; + +#[test] +fn fast_track_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!( + Democracy::fast_track(Origin::signed(5), h, 3, 2), + Error::::ProposalMissing + ); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); + assert_ok!(Democracy::fast_track(Origin::signed(5), h, 2, 0)); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 2, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SimpleMajority, + delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn instant_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!( + Democracy::fast_track(Origin::signed(5), h, 3, 2), + Error::::ProposalMissing + ); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 1, 0), BadOrigin); + assert_noop!( + Democracy::fast_track(Origin::signed(6), h, 1, 0), + Error::::InstantNotAllowed + ); + INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true); + assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0)); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 1, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SimpleMajority, + delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn fast_track_referendum_fails_when_no_simple_majority() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!( + Democracy::fast_track(Origin::signed(5), h, 3, 2), + Error::::NotSimpleMajority + ); + }); +} diff --git a/frame/referenda/src/tests/lock_voting.rs b/frame/referenda/src/tests/lock_voting.rs new file mode 100644 index 0000000000000..8b80b39c14aab --- /dev/null +++ b/frame/referenda/src/tests/lock_voting.rs @@ -0,0 +1,377 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for functionality concerning locking and lock-voting. + +use super::*; +use std::convert::TryFrom; + +fn aye(x: u8, balance: u64) -> AccountVote { + AccountVote::Standard { + vote: Vote { aye: true, conviction: Conviction::try_from(x).unwrap() }, + balance, + } +} + +fn nay(x: u8, balance: u64) -> AccountVote { + AccountVote::Standard { + vote: Vote { aye: false, conviction: Conviction::try_from(x).unwrap() }, + balance, + } +} + +fn the_lock(amount: u64) -> BalanceLock { + BalanceLock { id: DEMOCRACY_ID, amount, reasons: pallet_balances::Reasons::Misc } +} + +#[test] +fn lock_voting_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); + assert_ok!(Democracy::vote(Origin::signed(4), r, aye(2, 40))); + assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); + assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); + + // All balances are currently locked. + for i in 1..=5 { + assert_eq!(Balances::locks(i), vec![the_lock(i * 10)]); + } + + fast_forward_to(2); + + // Referendum passed; 1 and 5 didn't get their way and can now reap and unlock. + assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); + assert_ok!(Democracy::unlock(Origin::signed(1), 1)); + // Anyone can reap and unlock anyone else's in this context. + assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 5, r)); + assert_ok!(Democracy::unlock(Origin::signed(2), 5)); + + // 2, 3, 4 got their way with the vote, so they cannot be reaped by others. + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 2, r), + Error::::NoPermission + ); + // However, they can be unvoted by the owner, though it will make no difference to the lock. + assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); + assert_ok!(Democracy::unlock(Origin::signed(2), 2)); + + assert_eq!(Balances::locks(1), vec![]); + assert_eq!(Balances::locks(2), vec![the_lock(20)]); + assert_eq!(Balances::locks(3), vec![the_lock(30)]); + assert_eq!(Balances::locks(4), vec![the_lock(40)]); + assert_eq!(Balances::locks(5), vec![]); + assert_eq!(Balances::free_balance(42), 2); + + fast_forward_to(7); + // No change yet... + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 4, r), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_eq!(Balances::locks(4), vec![the_lock(40)]); + fast_forward_to(8); + // 4 should now be able to reap and unlock + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 4, r)); + assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_eq!(Balances::locks(4), vec![]); + + fast_forward_to(13); + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 3, r), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_eq!(Balances::locks(3), vec![the_lock(30)]); + fast_forward_to(14); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 3, r)); + assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_eq!(Balances::locks(3), vec![]); + + // 2 doesn't need to reap_vote here because it was already done before. + fast_forward_to(25); + assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_eq!(Balances::locks(2), vec![the_lock(20)]); + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_eq!(Balances::locks(2), vec![]); + }); +} + +#[test] +fn no_locks_without_conviction_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(0, 10))); + + fast_forward_to(2); + + assert_eq!(Balances::free_balance(42), 2); + assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 1, r)); + assert_ok!(Democracy::unlock(Origin::signed(2), 1)); + assert_eq!(Balances::locks(1), vec![]); + }); +} + +#[test] +fn lock_voting_should_work_with_delegation() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); + assert_ok!(Democracy::delegate(Origin::signed(4), 2, Conviction::Locked2x, 40)); + assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); + + assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +fn setup_three_referenda() -> (u32, u32, u32) { + System::set_block_number(0); + let r1 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r1, aye(4, 10))); + + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r2, aye(3, 20))); + + let r3 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r3, aye(2, 50))); + + fast_forward_to(2); + + (r1, r2, r3) +} + +#[test] +fn prior_lockvotes_should_be_enforced() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until 2 + 8 * 3 = #26 + // r.1 locked 20 until 2 + 4 * 3 = #14 + // r.2 locked 50 until 2 + 2 * 3 = #8 + + fast_forward_to(7); + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 5, r.2), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(50)]); + fast_forward_to(8); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(13); + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 5, r.1), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(14); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(25); + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 5, r.0), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(26); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn single_consolidation_of_lockvotes_should_work_as_before() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until 2 + 8 * 3 = #26 + // r.1 locked 20 until 2 + 4 * 3 = #14 + // r.2 locked 50 until 2 + 2 * 3 = #8 + + fast_forward_to(7); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(50)]); + fast_forward_to(8); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + + fast_forward_to(13); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(14); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + + fast_forward_to(25); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn multi_consolidation_of_lockvotes_should_be_conservative() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until 2 + 8 * 3 = #26 + // r.1 locked 20 until 2 + 4 * 3 = #14 + // r.2 locked 50 until 2 + 2 * 3 = #8 + + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + + fast_forward_to(8); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 20); + + fast_forward_to(14); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn locks_should_persist_from_voting_to_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r, aye(4, 10))); + fast_forward_to(2); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); + // locked 10 until #26. + + assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked3x, 20)); + // locked 20. + assert!(Balances::locks(5)[0].amount == 20); + + assert_ok!(Democracy::undelegate(Origin::signed(5))); + // locked 20 until #14 + + fast_forward_to(13); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount == 20); + + fast_forward_to(14); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(25); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn locks_should_persist_from_delegation_to_voting() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked5x, 5)); + assert_ok!(Democracy::undelegate(Origin::signed(5))); + // locked 5 until 16 * 3 = #48 + + let r = setup_three_referenda(); + // r.0 locked 10 until 2 + 8 * 3 = #26 + // r.1 locked 20 until 2 + 4 * 3 = #14 + // r.2 locked 50 until 2 + 2 * 3 = #8 + + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + + fast_forward_to(8); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 20); + + fast_forward_to(14); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 5); + + fast_forward_to(48); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} diff --git a/frame/referenda/src/tests/preimage.rs b/frame/referenda/src/tests/preimage.rs new file mode 100644 index 0000000000000..6d478fcaa68c7 --- /dev/null +++ b/frame/referenda/src/tests/preimage.rs @@ -0,0 +1,219 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The preimage tests. + +use super::*; + +#[test] +fn missing_preimage_should_fail() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn preimage_deposit_should_be_required_and_returned() { + new_test_ext_execute_with_cond(|operational| { + // fee of 100 is too much. + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); + assert_noop!( + if operational { + Democracy::note_preimage_operational(Origin::signed(6), vec![0; 500]) + } else { + Democracy::note_preimage(Origin::signed(6), vec![0; 500]) + }, + BalancesError::::InsufficientBalance, + ); + // fee of 1 is reasonable. + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + next_block(); + + assert_eq!(Balances::reserved_balance(6), 0); + assert_eq!(Balances::free_balance(6), 60); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn preimage_deposit_should_be_reapable_earlier_by_owner() { + new_test_ext_execute_with_cond(|operational| { + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(if operational { + Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) + } else { + Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) + }); + + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2), u32::MAX), + Error::::TooEarly + ); + next_block(); + assert_ok!(Democracy::reap_preimage( + Origin::signed(6), + set_balance_proposal_hash(2), + u32::MAX + )); + + assert_eq!(Balances::free_balance(6), 60); + assert_eq!(Balances::reserved_balance(6), 0); + }); +} + +#[test] +fn preimage_deposit_should_be_reapable() { + new_test_ext_execute_with_cond(|operational| { + assert_noop!( + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), + Error::::PreimageMissing + ); + + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(if operational { + Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) + } else { + Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) + }); + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + next_block(); + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), + Error::::TooEarly + ); + + next_block(); + assert_ok!(Democracy::reap_preimage( + Origin::signed(5), + set_balance_proposal_hash(2), + u32::MAX + )); + assert_eq!(Balances::reserved_balance(6), 0); + assert_eq!(Balances::free_balance(6), 48); + assert_eq!(Balances::free_balance(5), 62); + }); +} + +#[test] +fn noting_imminent_preimage_for_free_should_work() { + new_test_ext_execute_with_cond(|operational| { + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 1, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + assert_noop!( + if operational { + Democracy::note_imminent_preimage_operational( + Origin::signed(6), + set_balance_proposal(2), + ) + } else { + Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)) + }, + Error::::NotImminent + ); + + next_block(); + + // Now we're in the dispatch queue it's all good. + assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); + + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn reaping_imminent_preimage_should_fail() { + new_test_ext().execute_with(|| { + let h = set_balance_proposal_hash_and_note(2); + let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + next_block(); + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(6), h, u32::MAX), + Error::::Imminent + ); + }); +} + +#[test] +fn note_imminent_preimage_can_only_be_successful_once() { + new_test_ext().execute_with(|| { + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 1, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + next_block(); + + // First time works + assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); + + // Second time fails + assert_noop!( + Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)), + Error::::DuplicatePreimage + ); + + // Fails from any user + assert_noop!( + Democracy::note_imminent_preimage(Origin::signed(5), set_balance_proposal(2)), + Error::::DuplicatePreimage + ); + }); +} diff --git a/frame/referenda/src/tests/public_proposals.rs b/frame/referenda/src/tests/public_proposals.rs new file mode 100644 index 0000000000000..34713c3e15725 --- /dev/null +++ b/frame/referenda/src/tests/public_proposals.rs @@ -0,0 +1,149 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for the public proposal queue. + +use super::*; + +#[test] +fn backing_for_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_ok!(propose_set_balance_and_note(1, 3, 3)); + assert_eq!(Democracy::backing_for(0), Some(2)); + assert_eq!(Democracy::backing_for(1), Some(4)); + assert_eq!(Democracy::backing_for(2), Some(3)); + }); +} + +#[test] +fn deposit_for_proposals_should_be_taken() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(1, 2, 5)); + assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_eq!(Balances::free_balance(1), 5); + assert_eq!(Balances::free_balance(2), 15); + assert_eq!(Balances::free_balance(5), 35); + }); +} + +#[test] +fn deposit_for_proposals_should_be_returned() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(1, 2, 5)); + assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + fast_forward_to(3); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 20); + assert_eq!(Balances::free_balance(5), 50); + }); +} + +#[test] +fn proposal_with_deposit_below_minimum_should_not_work() { + new_test_ext().execute_with(|| { + assert_noop!(propose_set_balance(1, 2, 0), Error::::ValueLow); + }); +} + +#[test] +fn poor_proposer_should_not_work() { + new_test_ext().execute_with(|| { + assert_noop!(propose_set_balance(1, 2, 11), BalancesError::::InsufficientBalance); + }); +} + +#[test] +fn poor_seconder_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(2, 2, 11)); + assert_noop!( + Democracy::second(Origin::signed(1), 0, u32::MAX), + BalancesError::::InsufficientBalance + ); + }); +} + +#[test] +fn invalid_seconds_upper_bound_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(1, 2, 5)); + assert_noop!(Democracy::second(Origin::signed(2), 0, 0), Error::::WrongUpperBound); + }); +} + +#[test] +fn cancel_proposal_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_noop!(Democracy::cancel_proposal(Origin::signed(1), 0), BadOrigin); + assert_ok!(Democracy::cancel_proposal(Origin::root(), 0)); + assert_eq!(Democracy::backing_for(0), None); + assert_eq!(Democracy::backing_for(1), Some(4)); + }); +} + +#[test] +fn blacklisting_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let hash = set_balance_proposal_hash(2); + + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + + assert_noop!(Democracy::blacklist(Origin::signed(1), hash.clone(), None), BadOrigin); + assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); + + assert_eq!(Democracy::backing_for(0), None); + assert_eq!(Democracy::backing_for(1), Some(4)); + + assert_noop!(propose_set_balance_and_note(1, 2, 2), Error::::ProposalBlacklisted); + + fast_forward_to(2); + + let hash = set_balance_proposal_hash(4); + assert_ok!(Democracy::referendum_status(0)); + assert_ok!(Democracy::blacklist(Origin::root(), hash, Some(0))); + assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); + }); +} + +#[test] +fn runners_up_should_come_after() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_ok!(propose_set_balance_and_note(1, 3, 3)); + fast_forward_to(2); + assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); + fast_forward_to(4); + assert_ok!(Democracy::vote(Origin::signed(1), 1, aye(1))); + fast_forward_to(6); + assert_ok!(Democracy::vote(Origin::signed(1), 2, aye(1))); + }); +} diff --git a/frame/referenda/src/tests/scheduling.rs b/frame/referenda/src/tests/scheduling.rs new file mode 100644 index 0000000000000..5c857a632b97b --- /dev/null +++ b/frame/referenda/src/tests/scheduling.rs @@ -0,0 +1,156 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for functionality concerning normal starting, ending and enacting of referenda. + +use super::*; + +#[test] +fn simple_passing_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + assert_eq!(Democracy::lowest_unbaked(), 0); + next_block(); + next_block(); + assert_eq!(Democracy::lowest_unbaked(), 1); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn simple_failing_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); + assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 10 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn ooo_inject_referendums_should_work() { + new_test_ext().execute_with(|| { + let r1 = Democracy::inject_referendum( + 3, + set_balance_proposal_hash_and_note(3), + VoteThreshold::SuperMajorityApprove, + 0, + ); + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + + assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); + assert_eq!(tally(r2), Tally { ayes: 1, nays: 0, turnout: 10 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 2); + + assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); + assert_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 10 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 3); + }); +} + +#[test] +fn delayed_enactment_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 1, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); + assert_ok!(Democracy::vote(Origin::signed(4), r, aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, aye(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, aye(6))); + + assert_eq!(tally(r), Tally { ayes: 21, nays: 0, turnout: 210 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 0); + + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn lowest_unbaked_should_be_sensible() { + new_test_ext().execute_with(|| { + let r1 = Democracy::inject_referendum( + 3, + set_balance_proposal_hash_and_note(1), + VoteThreshold::SuperMajorityApprove, + 0, + ); + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + let r3 = Democracy::inject_referendum( + 10, + set_balance_proposal_hash_and_note(3), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); + assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); + // r3 is canceled + assert_ok!(Democracy::cancel_referendum(Origin::root(), r3.into())); + assert_eq!(Democracy::lowest_unbaked(), 0); + + next_block(); + + // r2 is approved + assert_eq!(Balances::free_balance(42), 2); + assert_eq!(Democracy::lowest_unbaked(), 0); + + next_block(); + + // r1 is approved + assert_eq!(Balances::free_balance(42), 1); + assert_eq!(Democracy::lowest_unbaked(), 3); + assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); + }); +} diff --git a/frame/referenda/src/tests/voting.rs b/frame/referenda/src/tests/voting.rs new file mode 100644 index 0000000000000..e035c2d46c1b6 --- /dev/null +++ b/frame/referenda/src/tests/voting.rs @@ -0,0 +1,169 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for normal voting functionality. + +use super::*; + +#[test] +fn overvoting_should_fail() { + new_test_ext().execute_with(|| { + let r = begin_referendum(); + assert_noop!( + Democracy::vote(Origin::signed(1), r, aye(2)), + Error::::InsufficientFunds + ); + }); +} + +#[test] +fn split_voting_should_work() { + new_test_ext().execute_with(|| { + let r = begin_referendum(); + let v = AccountVote::Split { aye: 40, nay: 20 }; + assert_noop!(Democracy::vote(Origin::signed(5), r, v), Error::::InsufficientFunds); + let v = AccountVote::Split { aye: 30, nay: 20 }; + assert_ok!(Democracy::vote(Origin::signed(5), r, v)); + + assert_eq!(tally(r), Tally { ayes: 3, nays: 2, turnout: 50 }); + }); +} + +#[test] +fn split_vote_cancellation_should_work() { + new_test_ext().execute_with(|| { + let r = begin_referendum(); + let v = AccountVote::Split { aye: 30, nay: 20 }; + assert_ok!(Democracy::vote(Origin::signed(5), r, v)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn single_proposal_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + let r = 0; + assert!(Democracy::referendum_info(r).is_none()); + + // start of 2 => next referendum scheduled. + fast_forward_to(2); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + assert_eq!(Democracy::referendum_count(), 1); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 1, nays: 0, turnout: 10 }, + }) + ); + + fast_forward_to(3); + + // referendum still running + assert_ok!(Democracy::referendum_status(0)); + + // referendum runs during 2 and 3, ends @ start of 4. + fast_forward_to(4); + + assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); + assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); + + // referendum passes and wait another two blocks for enactment. + fast_forward_to(6); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn controversial_voting_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + + assert_ok!(Democracy::vote(Origin::signed(1), r, big_aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, big_nay(2))); + assert_ok!(Democracy::vote(Origin::signed(3), r, big_nay(3))); + assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + + assert_eq!(tally(r), Tally { ayes: 110, nays: 100, turnout: 210 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn controversial_low_turnout_voting_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + + assert_eq!(tally(r), Tally { ayes: 60, nays: 50, turnout: 110 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn passing_low_turnout_voting_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_eq!(Balances::total_issuance(), 210); + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + assert_eq!(tally(r), Tally { ayes: 100, nays: 50, turnout: 150 }); + + next_block(); + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs new file mode 100644 index 0000000000000..9d7423dd57bb9 --- /dev/null +++ b/frame/referenda/src/types.rs @@ -0,0 +1,348 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Miscellaneous additional datatypes. + +use super::*; +use crate::{AccountVote, Conviction, Vote}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Saturating, Zero}, + RuntimeDebug, +}; + +/// Info regarding an ongoing referendum. +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Tally { + /// The number of aye votes, expressed in terms of post-conviction lock-vote. + pub ayes: Balance, + /// The number of nay votes, expressed in terms of post-conviction lock-vote. + pub nays: Balance, + /// The amount of funds currently expressing its opinion. Pre-conviction. + pub turnout: Balance, +} + +/// Amount of votes and capital placed in delegation for an account. +#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Delegations { + /// The number of votes (this is post-conviction). + pub votes: Balance, + /// The amount of raw capital, used for the turnout. + pub capital: Balance, +} + +impl Saturating for Delegations { + 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) } + } +} + +impl Tally where + Balance: From + Zero + Copy + CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + Bounded + + Saturating, +{ + /// Create a new tally. + pub fn new(vote: Vote, balance: Balance) -> 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, + } + } + + /// Add an account's vote into the tally. + pub fn add(&mut self, vote: AccountVote) -> 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) -> 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) -> Option<()> { + 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), + } + Some(()) + } + + /// Decrement some amount of votes. + pub fn reduce(&mut self, approve: bool, delegations: Delegations) -> Option<()> { + 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), + } + Some(()) + } + + pub fn turnout(&self, total: Balance) -> Perbill { + Perbill::from_rational(self.turnout, total) + } + + pub fn approval(&self) -> Perbill { + Perbill::from_rational(self.ayes, self.ayes.saturating_add(self.nays)) + } + + pub fn is_passing( + &self, + t: Moment, + period: Moment, + total: Balance, + turnout_needed: Curve, + approval_needed: Curve, + ) -> bool { + let x = Perbill::from_rational(t.min(period), period); + turnout_needed.passing(x, self.turnout) && approval_needed.passing(x, self.approval::()) + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct DecidingStatus { + /// When this referendum will end. If confirming, then the + /// end will actually be delayed until the end of the confirmation period. + pub(crate) ending: BlockNumber, + /// How long we will be deciding on this referendum for. + pub(crate) period: 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, +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Deposit { + pub(crate) who: AccountId, + pub(crate) amount: Balance, +} + +#[derive(Clone, Encode, TypeInfo)] +pub struct TrackInfo { + /// Name of this track. + pub(crate) 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(crate) max_deciding: u32, + /// Amount that must be placed on deposit before a decision can be made. + pub(crate) decision_deposit: Balance, + /// Amount of time this must be submitted for before a decision can be made. + pub(crate) prepare_period: Moment, + /// Amount of time that a decision may take to be approved prior to cancellation. + pub(crate) decision_period: Moment, + /// Amount of time that the approval criteria must hold before it can be approved. + pub(crate) confirm_period: Moment, + /// Minimum amount of time that an approved proposal must be in the dispatch queue. + pub(crate) 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(crate) min_approvals: Curve, + /// Minimum turnout as percentage of overall population that is needed for + /// approval as a function of time into decision period. + pub(crate) min_turnout: Curve, +} + +pub trait TracksInfo { + type Id: Copy + Eq + Codec; + type Origin; + fn tracks() -> &'static [(Self::Id, TrackInfo)]; + fn track_for(id: &Self::Origin) -> Result; +} + +/// Indication of either a specific moment or a delay from a implicitly defined moment. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum AtOrAfter { + /// 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 AtOrAfter { + 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)] +pub struct ReferendumStatus { + /// 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, + /// 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, + /// The deposit reserved for this referendum to be decided. + pub(crate) decision_deposit: Option>, + /// The status of a decision being made. If `None`, it has not entered the deciding period. + pub(crate) deciding: Option>, + /// The current tally of votes in this referendum. + pub(crate) tally: Tally, + /// The number of aye votes we are in the track queue for, if any. `None` if we're not + /// yet in the deciding queue or are already deciding. If a vote results in fewer ayes + /// in the `tally` than this, then the voter is required to pay to reorder the track queue. + /// Automatic advancement is scheduled when ayes_in_queue is Some value greater than the + /// ayes in `tally`. + pub(crate) ayes_in_queue: Option, +} + +impl + ReferendumStatus +{ + pub fn begin_deciding(&mut self, now: Moment, decision_period: Moment) { + self.deciding = Some(DecidingStatus { + ending: now.saturating_add(decision_period), + period: decision_period, + confirming: None, + }); + } +} + +/// Info regarding a referendum, present or past. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum ReferendumInfo { + /// Referendum has been submitted and is being voted on. + Ongoing(ReferendumStatus), + /// Referendum finished at `end` with approval. Submission deposit is held. + Confirmed(ReferendumStatus), + /// Referendum finished at `end` with approval. Submission deposit is held. + Approved(Deposit, Option>), + /// Referendum finished at `end` with rejection. Submission deposit is held. + Rejected(Deposit, Option>), + /// Referendum finished at `end` and was never decided. Submission deposit is held. + TimedOut(Deposit, Option>), +} + +impl + ReferendumInfo +{ + pub fn take_decision_deposit(&mut self) -> Option> { + use ReferendumInfo::*; + match self { + Approved(_, d) | Rejected(_, d) | TimedOut(_, d) => d.take(), + Confirmed(status) => status.decision_deposit.take(), + // Cannot refund deposit if Ongoing as this breaks assumptions. + _ => None, + } + } +} + +/// 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, +} + + +#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +pub enum Curve { + /// Linear curve starting at `(0, begin)`, ending at `(period, begin - delta)`. + LinearDecreasing { begin: Perbill, delta: Perbill }, +} + +impl Curve { + fn threshold(&self, x: Perbill) -> Perbill { + match self { + Self::LinearDecreasing { begin, delta } => begin - delta * x, + } + } + pub fn delay(&self, y: Perbill) { + match self { + Self::LinearDecreasing { begin, delta } => { + (begin - y.min(begin)).min(delta) / delta + }, + } + } + pub fn passing(&self, x: Perbill, y: Perbill) -> bool { + y >= self.threshold(x) + } +} diff --git a/frame/referenda/src/vote.rs b/frame/referenda/src/vote.rs new file mode 100644 index 0000000000000..03ca020ca0949 --- /dev/null +++ b/frame/referenda/src/vote.rs @@ -0,0 +1,202 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The vote datatype. + +use crate::{Conviction, Delegations, ReferendumIndex}; +use codec::{Decode, Encode, EncodeLike, Input, Output}; +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)] +pub struct Vote { + pub aye: bool, + pub conviction: Conviction, +} + +impl Encode for Vote { + fn encode_to(&self, output: &mut T) { + output.push_byte(u8::from(self.conviction) | if self.aye { 0b1000_0000 } else { 0 }); + } +} + +impl EncodeLike for Vote {} + +impl Decode for Vote { + fn decode(input: &mut I) -> Result { + let b = input.read_byte()?; + Ok(Vote { + aye: (b & 0b1000_0000) == 0b1000_0000, + conviction: Conviction::try_from(b & 0b0111_1111) + .map_err(|_| codec::Error::from("Invalid conviction"))?, + }) + } +} + +impl TypeInfo for Vote { + type Identity = Self; + + fn type_info() -> scale_info::Type { + scale_info::Type::builder() + .path(scale_info::Path::new("Vote", module_path!())) + .composite( + scale_info::build::Fields::unnamed() + .field(|f| f.ty::().docs(&["Raw vote byte, encodes aye + conviction"])), + ) + } +} + +/// A vote for a referendum of a particular account. +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub enum AccountVote { + /// A standard vote, one-way (approve or reject) with a given amount of conviction. + Standard { vote: Vote, balance: Balance }, + /// A split vote with balances given for both ways, and with no conviction, useful for + /// parachains when voting. + Split { aye: Balance, nay: Balance }, +} + +impl AccountVote { + /// 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, 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 { + 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, +)] +pub struct PriorLock(BlockNumber, Balance); + +impl PriorLock { + /// Accumulates an additional lock. + pub fn accumulate(&mut self, until: BlockNumber, amount: Balance) { + self.0 = self.0.max(until); + self.1 = self.1.max(amount); + } + + pub fn locked(&self) -> Balance { + self.1 + } + + pub fn rejig(&mut self, now: BlockNumber) { + if now >= self.0 { + self.0 = Zero::zero(); + self.1 = Zero::zero(); + } + } +} + +/// An indicator for what an account is doing; it can either be delegating or voting. +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub enum Voting { + /// The account is voting directly. `delegations` is the total amount of post-conviction voting + /// weight that it controls from those that have delegated to it. + Direct { + /// The current votes of the account. + votes: Vec<(ReferendumIndex, AccountVote)>, + /// The total amount of delegations that this account has received. + delegations: Delegations, + /// Any pre-existing locks from past voting/delegating activity. + prior: PriorLock, + }, + /// The account is delegating `balance` of its balance to a `target` account with `conviction`. + Delegating { + balance: Balance, + target: AccountId, + conviction: Conviction, + /// The total amount of delegations that this account has received. + delegations: Delegations, + /// Any pre-existing locks from past voting/delegating activity. + prior: PriorLock, + }, +} + +impl Default + for Voting +{ + fn default() -> Self { + Voting::Direct { + votes: Vec::new(), + delegations: Default::default(), + prior: PriorLock(Zero::zero(), Default::default()), + } + } +} + +impl + Voting +{ + pub fn rejig(&mut self, now: BlockNumber) { + match self { + Voting::Direct { prior, .. } => prior, + Voting::Delegating { prior, .. } => prior, + } + .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::Direct { votes, prior, .. } => + votes.iter().map(|i| i.1.balance()).fold(prior.locked(), |a, i| a.max(i)), + Voting::Delegating { balance, .. } => *balance, + } + } + + pub fn set_common( + &mut self, + delegations: Delegations, + prior: PriorLock, + ) { + let (d, p) = match self { + Voting::Direct { ref mut delegations, ref mut prior, .. } => (delegations, prior), + Voting::Delegating { ref mut delegations, ref mut prior, .. } => (delegations, prior), + }; + *d = delegations; + *p = prior; + } +} diff --git a/frame/referenda/src/weights.rs b/frame/referenda/src/weights.rs new file mode 100644 index 0000000000000..638852d3c7e19 --- /dev/null +++ b/frame/referenda/src/weights.rs @@ -0,0 +1,545 @@ +// 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_democracy +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-09-30, 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_democracy +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/democracy/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_democracy. +pub trait WeightInfo { + fn propose() -> Weight; + fn second(s: u32, ) -> Weight; + fn vote_new(r: u32, ) -> Weight; + fn vote_existing(r: u32, ) -> Weight; + fn emergency_cancel() -> Weight; + fn blacklist(p: u32, ) -> Weight; + fn external_propose(v: u32, ) -> Weight; + fn external_propose_majority() -> Weight; + fn external_propose_default() -> Weight; + fn fast_track() -> Weight; + fn veto_external(v: u32, ) -> Weight; + fn cancel_proposal(p: u32, ) -> Weight; + fn cancel_referendum() -> Weight; + fn cancel_queued(r: u32, ) -> Weight; + fn on_initialize_base(r: u32, ) -> Weight; + fn on_initialize_base_with_launch_period(r: u32, ) -> Weight; + fn delegate(r: u32, ) -> Weight; + fn undelegate(r: u32, ) -> Weight; + fn clear_public_proposals() -> Weight; + fn note_preimage(b: u32, ) -> Weight; + fn note_imminent_preimage(b: u32, ) -> Weight; + fn reap_preimage(b: u32, ) -> Weight; + fn unlock_remove(r: u32, ) -> Weight; + fn unlock_set(r: u32, ) -> Weight; + fn remove_vote(r: u32, ) -> Weight; + fn remove_other_vote(r: u32, ) -> Weight; +} + +/// Weights for pallet_democracy using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Democracy PublicPropCount (r:1 w:1) + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:0) + // Storage: Democracy DepositOf (r:0 w:1) + fn propose() -> Weight { + (67_388_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy DepositOf (r:1 w:1) + fn second(s: u32, ) -> Weight { + (41_157_000 as Weight) + // Standard Error: 0 + .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn vote_new(r: u32, ) -> Weight { + (46_406_000 as Weight) + // Standard Error: 1_000 + .saturating_add((170_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn vote_existing(r: u32, ) -> Weight { + (46_071_000 as Weight) + // Standard Error: 1_000 + .saturating_add((166_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy Cancellations (r:1 w:1) + fn emergency_cancel() -> Weight { + (27_699_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy Blacklist (r:0 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn blacklist(p: u32, ) -> Weight { + (82_703_000 as Weight) + // Standard Error: 4_000 + .saturating_add((500_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:0) + fn external_propose(v: u32, ) -> Weight { + (13_747_000 as Weight) + // Standard Error: 0 + .saturating_add((76_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:0 w:1) + fn external_propose_majority() -> Weight { + (3_070_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:0 w:1) + fn external_propose_default() -> Weight { + (3_080_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy ReferendumCount (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:0 w:1) + fn fast_track() -> Weight { + (29_129_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:1) + fn veto_external(v: u32, ) -> Weight { + (30_105_000 as Weight) + // Standard Error: 0 + .saturating_add((104_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn cancel_proposal(p: u32, ) -> Weight { + (55_228_000 as Weight) + // Standard Error: 1_000 + .saturating_add((457_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:0 w:1) + fn cancel_referendum() -> Weight { + (17_319_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn cancel_queued(r: u32, ) -> Weight { + (29_738_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_153_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy LowestUnbaked (r:1 w:0) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:1 w:0) + fn on_initialize_base(r: u32, ) -> Weight { + (2_165_000 as Weight) + // Standard Error: 3_000 + .saturating_add((5_577_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy LowestUnbaked (r:1 w:0) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy LastTabledWasExternal (r:1 w:0) + // Storage: Democracy NextExternal (r:1 w:0) + // Storage: Democracy PublicProps (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:1 w:0) + fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { + (9_396_000 as Weight) + // Standard Error: 4_000 + .saturating_add((5_604_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy VotingOf (r:3 w:3) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn delegate(r: u32, ) -> Weight { + (57_783_000 as Weight) + // Standard Error: 4_000 + .saturating_add((7_623_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy VotingOf (r:2 w:2) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + fn undelegate(r: u32, ) -> Weight { + (26_027_000 as Weight) + // Standard Error: 4_000 + .saturating_add((7_593_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy PublicProps (r:0 w:1) + fn clear_public_proposals() -> Weight { + (2_780_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + fn note_preimage(b: u32, ) -> Weight { + (46_416_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + fn note_imminent_preimage(b: u32, ) -> Weight { + (29_735_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + // Storage: System Account (r:1 w:0) + fn reap_preimage(b: u32, ) -> Weight { + (41_276_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn unlock_remove(r: u32, ) -> Weight { + (40_348_000 as Weight) + // Standard Error: 1_000 + .saturating_add((60_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn unlock_set(r: u32, ) -> Weight { + (37_475_000 as Weight) + // Standard Error: 1_000 + .saturating_add((151_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + fn remove_vote(r: u32, ) -> Weight { + (19_970_000 as Weight) + // Standard Error: 1_000 + .saturating_add((153_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + fn remove_other_vote(r: u32, ) -> Weight { + (20_094_000 as Weight) + // Standard Error: 1_000 + .saturating_add((157_000 as Weight).saturating_mul(r 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: Democracy PublicPropCount (r:1 w:1) + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:0) + // Storage: Democracy DepositOf (r:0 w:1) + fn propose() -> Weight { + (67_388_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy DepositOf (r:1 w:1) + fn second(s: u32, ) -> Weight { + (41_157_000 as Weight) + // Standard Error: 0 + .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn vote_new(r: u32, ) -> Weight { + (46_406_000 as Weight) + // Standard Error: 1_000 + .saturating_add((170_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn vote_existing(r: u32, ) -> Weight { + (46_071_000 as Weight) + // Standard Error: 1_000 + .saturating_add((166_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy Cancellations (r:1 w:1) + fn emergency_cancel() -> Weight { + (27_699_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy Blacklist (r:0 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn blacklist(p: u32, ) -> Weight { + (82_703_000 as Weight) + // Standard Error: 4_000 + .saturating_add((500_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:0) + fn external_propose(v: u32, ) -> Weight { + (13_747_000 as Weight) + // Standard Error: 0 + .saturating_add((76_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:0 w:1) + fn external_propose_majority() -> Weight { + (3_070_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:0 w:1) + fn external_propose_default() -> Weight { + (3_080_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy ReferendumCount (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:0 w:1) + fn fast_track() -> Weight { + (29_129_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:1) + fn veto_external(v: u32, ) -> Weight { + (30_105_000 as Weight) + // Standard Error: 0 + .saturating_add((104_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn cancel_proposal(p: u32, ) -> Weight { + (55_228_000 as Weight) + // Standard Error: 1_000 + .saturating_add((457_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:0 w:1) + fn cancel_referendum() -> Weight { + (17_319_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn cancel_queued(r: u32, ) -> Weight { + (29_738_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_153_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy LowestUnbaked (r:1 w:0) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:1 w:0) + fn on_initialize_base(r: u32, ) -> Weight { + (2_165_000 as Weight) + // Standard Error: 3_000 + .saturating_add((5_577_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy LowestUnbaked (r:1 w:0) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy LastTabledWasExternal (r:1 w:0) + // Storage: Democracy NextExternal (r:1 w:0) + // Storage: Democracy PublicProps (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:1 w:0) + fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { + (9_396_000 as Weight) + // Standard Error: 4_000 + .saturating_add((5_604_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy VotingOf (r:3 w:3) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn delegate(r: u32, ) -> Weight { + (57_783_000 as Weight) + // Standard Error: 4_000 + .saturating_add((7_623_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy VotingOf (r:2 w:2) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + fn undelegate(r: u32, ) -> Weight { + (26_027_000 as Weight) + // Standard Error: 4_000 + .saturating_add((7_593_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy PublicProps (r:0 w:1) + fn clear_public_proposals() -> Weight { + (2_780_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + fn note_preimage(b: u32, ) -> Weight { + (46_416_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + fn note_imminent_preimage(b: u32, ) -> Weight { + (29_735_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + // Storage: System Account (r:1 w:0) + fn reap_preimage(b: u32, ) -> Weight { + (41_276_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn unlock_remove(r: u32, ) -> Weight { + (40_348_000 as Weight) + // Standard Error: 1_000 + .saturating_add((60_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn unlock_set(r: u32, ) -> Weight { + (37_475_000 as Weight) + // Standard Error: 1_000 + .saturating_add((151_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + fn remove_vote(r: u32, ) -> Weight { + (19_970_000 as Weight) + // Standard Error: 1_000 + .saturating_add((153_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + fn remove_other_vote(r: u32, ) -> Weight { + (20_094_000 as Weight) + // Standard Error: 1_000 + .saturating_add((157_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } +} diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 2e7f26eef16f4..b0f385914ac1a 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -1026,6 +1026,21 @@ where } } +/// Ensure that the origin `o` represents a signed extrinsic (i.e. transaction). +/// Returns `Ok` with the account that signed the extrinsic or an `Err` otherwise. +pub fn ensure_signed_or_root(o: OuterOrigin) + -> Result, BadOrigin> +where + OuterOrigin: Into, OuterOrigin>>, +{ + match o.into() { + Ok(RawOrigin::Signed(t)) => Ok(Some(t)), + Ok(RawOrigin::Root) => Ok(None), + _ => Err(BadOrigin), + } +} + + /// A type of block initialization to perform. pub enum InitKind { /// Leave inspectable storage entries in state. @@ -1749,7 +1764,7 @@ impl Lookup for ChainContext { /// 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 = ::Origin; From e18562edc1eaa584b20dbe3870f57d751349cb2e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 5 Nov 2021 13:03:18 +0100 Subject: [PATCH 02/66] Docs --- frame/referenda/README.md | 2 +- frame/referenda/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/referenda/README.md b/frame/referenda/README.md index f07602c4a7e99..85031a0113033 100644 --- a/frame/referenda/README.md +++ b/frame/referenda/README.md @@ -1,4 +1,4 @@ -# Assembly Pallet +# 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) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index e638cf7d3be45..53045d2735e25 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -13,14 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Assembly Pallet +//! # Referenda Pallet //! //! - [`Config`] //! - [`Call`] //! //! ## Overview //! -//! The Assembly pallet handles the administration of general stakeholder voting. +//! The Referenda pallet handles the administration of general stakeholder voting. #![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] From a79ceb19a64b4652fca5a55265abdb335b982baa Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 5 Nov 2021 19:43:43 +0100 Subject: [PATCH 03/66] Fixes --- frame/referenda/src/lib.rs | 434 ++++++++++++----------- frame/referenda/src/types.rs | 39 +- frame/support/src/storage/bounded_vec.rs | 15 + 3 files changed, 266 insertions(+), 222 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 53045d2735e25..83110169343b5 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -25,20 +25,19 @@ #![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode, Input, Codec}; +use codec::{Encode, Codec}; use frame_support::{ - ensure, BoundedVec, weights::Weight, traits::{ + ensure, BoundedVec, traits::{ schedule::{DispatchTime, Named as ScheduleNamed}, - BalanceStatus, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, - ReservableCurrency, WithdrawReasons, + Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, }, }; use scale_info::TypeInfo; use sp_runtime::{ - traits::{Bounded, Dispatchable, Hash, Saturating, Zero, AtLeast32BitUnsigned, One}, - ArithmeticError, DispatchError, DispatchResult, RuntimeDebug, PerThing, Perbill, + traits::{Dispatchable, Saturating, One, AtLeast32BitUnsigned}, DispatchError, DispatchResult, + Perbill, }; -use sp_std::prelude::*; +use sp_std::{prelude::*, fmt::Debug}; mod conviction; mod types; @@ -69,9 +68,11 @@ type BalanceOf = type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; +type CallOf = ::Call; +type OriginOf = ::Origin; type ReferendumInfoOf = ReferendumInfo< TrackIdOf, - ::Origin, + OriginOf, ::BlockNumber, ::Hash, BalanceOf, @@ -80,42 +81,49 @@ type ReferendumInfoOf = ReferendumInfo< >; type ReferendumStatusOf = ReferendumStatus< TrackIdOf, - ::Origin, + OriginOf, ::BlockNumber, ::Hash, BalanceOf, BalanceOf, ::AccountId, >; -type VotingOf = Voting< - BalanceOf, - ::AccountId, +type DecidingStatusOf = DecidingStatus< ::BlockNumber, >; -type TracksInfoOf = TracksInfo< +type VotingOf = Voting< BalanceOf, + ::AccountId, ::BlockNumber, >; +type TracksInfoOf = ::Tracks; type TrackInfoOf = TrackInfo< BalanceOf, ::BlockNumber, >; -type TrackIdOf = TracksInfoOf::Id; +type TrackIdOf = < + ::Tracks as TracksInfo< + BalanceOf, + ::BlockNumber, + > +>::Id; pub trait InsertSorted { - fn insert_sorted_by_key(&mut self, t: T) -> bool; + fn insert_sorted_by_key< + F: FnMut(&T) -> K, + K: PartialOrd + Ord, + >(&mut self, t: T, f: F,) -> Result<(), ()>; } -impl> InsertSorted for BoundedVec { +impl> InsertSorted for BoundedVec { fn insert_sorted_by_key< F: FnMut(&T) -> K, - K: PartialOrd, - >(&mut self, t: T, f: F,) -> Result<(), ()> { - let index = self.binary_search_by_key(&t, f).unwrap_or_else(|x| x); + K: PartialOrd + Ord, + >(&mut self, t: T, mut f: F,) -> Result<(), ()> { + let index = self.binary_search_by_key::(&f(&t), f).unwrap_or_else(|x| x); if index >= S::get() as usize { return Err(()) } - self.truncate(S::get() as usize - 1); - self.insert(index, t); + self.force_insert(index, t); Ok(()) } } @@ -124,10 +132,9 @@ impl> InsertSorted for BoundedVec { pub mod pallet { use super::*; use frame_support::{ - dispatch::DispatchResultWithPostInfo, pallet_prelude::*, traits::EnsureOrigin, - weights::{DispatchClass, Pays}, + weights::Pays, Parameter, }; use frame_system::pallet_prelude::*; @@ -140,21 +147,22 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config + Sized { // System level stuff. - type Proposal: Parameter + Dispatchable + From>; + type Call: Parameter + Dispatchable> + From>; + type Origin: From> + Codec + Clone + Eq + TypeInfo + Debug; type Event: From> + IsType<::Event>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// The Scheduler. - type Scheduler: ScheduleNamed; + type Scheduler: ScheduleNamed, OriginOf>; /// Currency type for this pallet. type Currency: ReservableCurrency + LockableCurrency; // Origins and unbalances. /// Origin from which any vote may be cancelled. - type CancelOrigin: EnsureOrigin; + type CancelOrigin: EnsureOrigin>; /// Origin from which any vote may be killed. - type KillOrigin: EnsureOrigin; + type KillOrigin: EnsureOrigin>; /// Handler for the unbalanced reduction when slashing a preimage deposit. type Slash: OnUnbalanced>; @@ -194,7 +202,7 @@ pub mod pallet { // The other stuff. /// Information concerning the different referendum tracks. - type Tracks: TracksInfo, Self::BlockNumber, Origin = Self::Origin>; + type Tracks: TracksInfo, Self::BlockNumber, Origin = OriginOf>; } /// The next free referendum index, aka the number of referenda started so far. @@ -389,6 +397,8 @@ pub mod pallet { BadReferendum, /// There was nothing to do in the advancement. NothingToDo, + /// No track exists for the proposal origin. + NoTrack, } // TODO: bans @@ -405,17 +415,17 @@ pub mod pallet { /// - `enactment_moment`: The moment that the proposal should be enacted. /// /// Emits `Submitted`. - #[pallet::weight(T::WeightInfo::propose())] + #[pallet::weight(0)] pub fn submit( origin: OriginFor, - proposal_origin: T::Origin, + proposal_origin: OriginOf, proposal_hash: T::Hash, enactment_moment: AtOrAfter, ) -> DispatchResult { let who = ensure_signed(origin)?; let index = ReferendumCount::::mutate(|x| { let r = *x; *x += 1; r }); - let track = T::Tracks::track_for(&proposal_origin); + let track = T::Tracks::track_for(&proposal_origin).map_err(|_| Error::::NoTrack)?; let status = ReferendumStatus { track, origin: proposal_origin, @@ -442,11 +452,11 @@ pub mod pallet { let who = ensure_signed(origin)?; let status = Self::ensure_ongoing(index)?; ensure!(status.decision_deposit.is_none(), Error::::HaveDeposit); - let track = Self::track(status.track); - status.decision_deposit = Self::take_deposit(who, track.decision_deposit)?; - ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + let track = Self::track(status.track).ok_or(Error::::NoTrack)?; + status.decision_deposit = Some(Self::take_deposit(who, track.decision_deposit)?); let now = frame_system::Pallet::::block_number(); - Self::service_referendum(now, index, status); + let info = Self::service_referendum(now, index, status).0; + ReferendumInfoFor::::insert(index, info); Ok(()) } @@ -462,59 +472,25 @@ pub mod pallet { Ok(()) } - /// - #[pallet::weight(0)] - pub fn consider_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { - ensure_signed_or_root(origin)?; - Self::do_consider_referendum(index)?; - Ok(Pays::No.into()) - } + // TODO: cancel_referendum + // TODO: kill_referendum + /// Advance a referendum onto its next logical state. This will happen eventually anyway, + /// but you can nudge it #[pallet::weight(0)] - pub fn advance_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { + pub fn nudge_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { ensure_signed_or_root(origin)?; - Self::do_advance_referendum(index)?; - Ok(Pays::No.into()) + Self::advance_referendum(index)?; + Ok(()) } #[pallet::weight(0)] - pub fn advance_track(origin: OriginFor, track: TrackIdOf) -> DispatchResult { + pub fn nudge_track( + origin: OriginFor, + track: TrackIdOf, + ) -> DispatchResultWithPostInfo { ensure_signed_or_root(origin)?; - Self::do_advance_track(track)?; - Ok(Pays::No.into()) - } - - #[pallet::weight(0)] - pub fn pass_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { - ensure_signed(origin)?; - let info = ReferendumInfoFor::::get(index); - let mut status = match info { - ReferendumInfo::Confirmed(status) => status, - _ => return Err(()), - }; - Self::refund_deposit(status.decision_deposit.take()); - - // Enqueue proposal - let now = frame_system::Pallet::::block_number(); - let track = Self::track(status.track)?; - let earliest_allowed = now.saturating_add(track.min_enactment_period); - let desired = status.enactment.evaluate(now); - let enactment = desired.max(earliest_allowed); - let ok = T::Scheduler::schedule_named( - (ASSEMBLY_ID, index).encode(), - DispatchTime::At(enactment), - None, - 63, - status.origin, - Call::stub { call_hash: status.proposal_hash }.into(), - ).is_ok(); - debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed"); - - Self::deposit_event(Event::::Approved { index }); - - let info = ReferendumInfo::Approved(status.submission_deposit); - ReferendumStatusOf::::insert(index, info); - + Self::advance_track(track)?; Ok(Pays::No.into()) } @@ -525,41 +501,82 @@ pub mod pallet { } impl Pallet { - pub fn ensure_ongoing(index: ReferendumIndex) -> Result, DispatchError> { + pub fn ensure_ongoing(index: ReferendumIndex) + -> Result, DispatchError> + { match ReferendumInfoFor::::get(index) { - ReferendumInfo::Ongoing(status) => Ok(status), + Some(ReferendumInfo::Ongoing(status)) => Ok(status), _ => Err(Error::::NotOngoing.into()), } } + // Enqueue a proposal from a referendum which has presumably passed. + fn schedule_enactment( + index: ReferendumIndex, + track: &TrackInfoOf, + desired: AtOrAfter, + origin: OriginOf, + call_hash: T::Hash, + ) { + let now = frame_system::Pallet::::block_number(); + let earliest_allowed = now.saturating_add(track.min_enactment_period); + let desired = desired.evaluate(now); + let ok = T::Scheduler::schedule_named( + (ASSEMBLY_ID, "enactment", index).encode(), + DispatchTime::At(desired.max(earliest_allowed)), + None, + 63, + origin, + Call::stub { call_hash }.into(), + ).is_ok(); + debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed"); + } + + /// Sets a referendum's alarm call. Returns `true` if there was not already an alarm call in + /// place for the same time. fn set_referendum_alarm(index: ReferendumIndex, when: T::BlockNumber) -> bool { let scheduler_id = (ASSEMBLY_ID, "referendum", index).encode(); - Self::set_alarm(scheduler_id, Call::advance_referendum { index }, when) + // NOTE: This only works because we know that that this ID will only ever be used for this + // call. + Self::set_alarm(scheduler_id, Call::nudge_referendum { index }, when) + } + + /// Cancels a referendum's alarm call. Returns `true` if there was an alarm scheduled. + fn cancel_referendum_alarm(index: ReferendumIndex) -> bool { + let id = (ASSEMBLY_ID, "referendum", index).encode(); + T::Scheduler::cancel_named(id).is_ok() } + /// Sets a track's alarm call. Returns `true` if there was not already an alarm call in place + /// for the same time. fn set_track_alarm(track: TrackIdOf, when: T::BlockNumber) -> bool { let scheduler_id = (ASSEMBLY_ID, "track", track).encode(); - Self::set_alarm(scheduler_id, Call::advance_track { track }, when) + // NOTE: This only works because we know that that this ID will only ever be used for this + // call. + Self::set_alarm(scheduler_id, Call::nudge_track { track }, when) } + /// Set or reset an alarm for a given ID. This assumes that an ID always uses the same + /// call. If it doesn't then this won't work! + /// /// Returns `false` if there is no change. - fn set_alarm(id: Vec, call: impl Into, when: T::BlockNumber) -> bool { + fn set_alarm(id: Vec, call: impl Into>, when: T::BlockNumber) -> bool { let alarm_interval = T::AlarmInterval::get(); - let when = (when / alarm_interval + 1) * alarm_interval; + let when = (when / alarm_interval + One::one()) * alarm_interval; if let Ok(t) = T::Scheduler::next_dispatch_time(id.clone()) { if t == when { return false } - let ok = T::Scheduler::reschedule_named(id, when).is_ok(); + let ok = T::Scheduler::reschedule_named(id, DispatchTime::At(when)).is_ok(); debug_assert!(ok, "Unable to reschedule an extant referendum?!"); } else { let _ = T::Scheduler::cancel_named(id.clone()); let ok = T::Scheduler::schedule_named( id, - when, + DispatchTime::At(when), None, 128u8, - frame_system::Origin::Root.into(), + OriginOf::::from(frame_system::Origin::::Root), call.into(), ).is_ok(); debug_assert!(ok, "Unable to schedule a new referendum?!"); @@ -581,18 +598,21 @@ impl Pallet { } else { // Add to queue. let item = (index, status.tally.ayes); - TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)) + status.ayes_in_queue = Some(status.tally.ayes); + TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)); } } /// Grab the index and status for the referendum which is the highest priority of those for the /// given track which are ready for being decided. - fn next_for_deciding(track_queue: BoundedVec<(u32, BalanceOf), T::MaxQueued>) -> Option<(ReferendumIndex, ReferendumStatusOf)> { + fn next_for_deciding( + track_queue: &mut BoundedVec<(u32, BalanceOf), T::MaxQueued>, + ) -> Option<(ReferendumIndex, ReferendumStatusOf)> { loop { let (index, _) = track_queue.pop()?; match Self::ensure_ongoing(index) { Ok(s) => return Some((index, s)), - Err() => debug_assert!(false, "Queued referendum not ongoing?!"), + Err(_) => debug_assert!(false, "Queued referendum not ongoing?!"), } } } @@ -600,10 +620,12 @@ impl Pallet { /// Advance a track - this dequeues one or more referenda from the from the `TrackQueue` of /// referenda which are ready to be decided until the `DecidingCount` is equal to the track's /// `max_deciding`. - fn do_advance_track(track: TrackIdOf) -> Result { + /// + /// This should never be needed, since referenda should automatically begin when others end. + fn advance_track(track: TrackIdOf) -> Result { let track_info = Self::track(track).ok_or(Error::::BadTrack)?; let deciding_count = DecidingCount::::get(track); - let track_queue = TrackQueue::::get(track); + let mut track_queue = TrackQueue::::get(track); let count = 0; while deciding_count < track_info.max_deciding { if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { @@ -623,55 +645,30 @@ impl Pallet { Ok(count) } - // TODO: if a vote results in `ayes_in_queue.map_or(false, |v| tally.ayes > v)` then schedule - // the referendum alarm to update `TrackQueue` - could either update entry's position or move - // index in. - - /// Applies only to referendums in the `TrackQueue`. - /// - /// Checks a referendum's aye votes and if above the lowest in its `TrackQueue` or if - /// `TrackQueue` is below capacity then inserts it into its `TrackQueue`. - fn do_consider_referendum(index: ReferendumIndex) -> DispatchResult { - let now = frame_system::Pallet::::block_number(); - let status = Self::ensure_ongoing(index)?; - if let Some(info) = Self::service_referendum(now, status) { - if let ReferendumInfo::Ongoing(&status) = info { - let when = Self::next_referendum_advance(&status).max(now + One::one()); - Self::set_referendum_alarm(index, when); - } - ReferendumInfoFor::::insert(index, info); - } - } - - // TODO: If a vote results in `ayes_in_queue.map_or(false, |v| tally.ayes < v)` then it must - // include a reorder of the queue. - - /// Applies only to referendums in the `TrackQueue`. - /// - /// Updates the referendum's aye votes into `ayes_in_queue` and into `TrackQueue` and then - /// sort the queue. - fn do_reconsider_referendum(index: ReferendumIndex) -> DispatchResult { + /// Attempts to advance the referendum. Returns `Ok` if something useful happened. + fn advance_referendum(index: ReferendumIndex) -> DispatchResult { let now = frame_system::Pallet::::block_number(); let status = Self::ensure_ongoing(index)?; - if let Some(info) = Self::service_referendum(now, status) { - if let ReferendumInfo::Ongoing(&status) = info { - let when = Self::next_referendum_advance(&status).max(now + One::one()); - Self::set_referendum_alarm(index, when); - } + let (info, dirty) = Self::service_referendum(now, index, status); + if dirty { ReferendumInfoFor::::insert(index, info); } + Ok(()) } - /// Attempts to advance the referendum. Returns `Ok` if something useful happened. - fn do_advance_referendum(index: ReferendumIndex) -> DispatchResult { - let now = frame_system::Pallet::::block_number(); - let status = Self::ensure_ongoing(index)?; - if let Some(info) = Self::service_referendum(now, status) { - if let ReferendumInfo::Ongoing(&status) = info { - let when = Self::next_referendum_advance(&status).max(now + One::one()); - Self::set_referendum_alarm(index, when); - } - ReferendumInfoFor::::insert(index, info); + /// Action item for when there is now one fewer referendum in the deciding phase and the + /// `DecidingCount` is not yet updated. This means that we should either: + /// - begin deciding another referendum (and leave `DecidingCount` alone); or + /// - decrement `DecidingCount`. + fn note_one_fewer_deciding(track: TrackIdOf, track_info: &TrackInfoOf) { + let mut track_queue = TrackQueue::::get(track); + if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { + let now = frame_system::Pallet::::block_number(); + status.begin_deciding(now, track_info.decision_period); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + TrackQueue::::insert(track, track_queue); + } else { + DecidingCount::::mutate(track, |x| x.saturating_dec()); } } @@ -681,18 +678,69 @@ impl Pallet { /// - If it's ongoing and passing, ensure confirming; if at end of confirmation period, pass. /// - If it's ongoing and not passing, stop confirning; if it has reached end time, fail. /// - /// Weight will be a bit different depending on what it does, but if designed so as not to - /// differ dramatically. In particular there are no balance operations. + /// Weight will be a bit different depending on what it does, but it's designed so as not to + /// differ dramatically, especially if `MaxQueue` is kept small. In particular _there are no + /// balance operations in here_. + /// + /// In terms of storage, every call to it is expected to access: + /// - The scheduler, either to insert, remove or alter an entry; + /// - `TrackQueue`, which should be a `BoundedVec` with a low limit (8-16). + /// - `DecidingCount`. + /// + /// Both of the two storage items will only have as many items as there are different tracks, + /// perhaps around 10 and should be whitelisted. + /// + /// The heaviest branch is likely to be when a proposal is placed into, or moved within, the + /// `TrackQueue`. Basically this happens when a referendum is in the deciding queue and receives + /// a vote, or when it moves into the deciding queue. fn service_referendum( now: T::BlockNumber, index: ReferendumIndex, mut status: ReferendumStatusOf, - ) -> Option> { + ) -> (ReferendumInfoOf, bool) { + let mut dirty = false; // Should it begin being decided? - let track = Self::track(status.track)?; - if status.deciding.is_none() && status.decision_deposit.is_some() { - if now.saturating_sub(status.submitted) >= track.prepare_period { - Self::ready_for_deciding(now, &track, &mut status); + let track = match Self::track(status.track) { + Some(x) => x, + None => return (ReferendumInfo::Ongoing(status), false), + }; + let mut alarm; + let timeout = status.submitted + T::UndecidingTimeout::get(); + if status.deciding.is_none() { + // Default the alarm to the submission timeout. + alarm = timeout; + // Are we already queued for deciding? + if let Some(_) = status.ayes_in_queue.as_ref() { + // Does our position in the queue need updating? + let ayes = status.tally.ayes; + let mut queue = TrackQueue::::get(status.track); + let maybe_old_pos = queue.iter().position(|(x, _)| *x == index); + let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x); + if maybe_old_pos.is_none() && new_pos < queue.len() { + // Just insert. + queue.truncate(queue.len() - 1); + queue.insert(new_pos, (index, ayes)); + } else if let Some(old_pos) = maybe_old_pos { + // We were in the queue - just update and sort. + queue[old_pos].1 = ayes; + if new_pos < old_pos { + queue[new_pos..=old_pos].rotate_right(1); + } else if old_pos < new_pos { + queue[old_pos..=new_pos].rotate_left(1); + } + } + TrackQueue::::insert(status.track, queue); + } else { + // Are we ready for deciding? + if status.decision_deposit.is_some() { + let prepare_end = status.submitted.saturating_add(track.prepare_period); + if now >= prepare_end { + Self::ready_for_deciding(now, &track, index, &mut status); + dirty = true; + } else { + alarm = alarm.min(prepare_end); + } + } } } if let Some(deciding) = &mut status.deciding { @@ -703,34 +751,56 @@ impl Pallet { track.min_turnout, track.min_approvals, ); - if let Some(confirming) = deciding.confirming { - if is_passing && confirming >= now { + if is_passing { + if deciding.confirming.map_or(false, |c| now >= c) { // Passed! - DecidingCount::::mutate(status.track, |x| x.saturating_dec()); + Self::cancel_referendum_alarm(index); + Self::note_one_fewer_deciding(status.track, track); + let (desired, call_hash) = (status.enactment, status.proposal_hash); + Self::schedule_enactment(index, track, desired, status.origin, call_hash); Self::deposit_event(Event::::Confirmed { index, tally: status.tally }); - return Some(ReferendumInfo::Confirmed(status)) - } else if !is_passing { - // Move back out of confirming - deciding.confirming = None; + return (ReferendumInfo::Approved(status.submission_deposit, status.decision_deposit), true) } - } - if deciding.confirming.is_none() { - if is_passing { - // Begin confirming - deciding.confirming = Some(now.saturating_add(track.confirm_period)); - } else if now >= deciding.ending { + dirty = deciding.confirming.is_none(); + let confirmation = deciding.confirming + .unwrap_or_else(|| now.saturating_add(track.confirm_period)); + deciding.confirming = Some(confirmation); + alarm = confirmation; + } else { + if now >= deciding.ending { // Failed! - DecidingCount::::mutate(status.track, |x| x.saturating_dec()); + Self::cancel_referendum_alarm(index); + Self::note_one_fewer_deciding(status.track, track); Self::deposit_event(Event::::Rejected { index, tally: status.tally }); - return Some(ReferendumInfo::Rejected(status.submission_deposit, status.decision_deposit)) + return (ReferendumInfo::Rejected(status.submission_deposit, status.decision_deposit), true) } + // Cannot be confirming + dirty = deciding.confirming.is_some(); + deciding.confirming = None; + alarm = Self::decision_time(&deciding, &status, track); } - } else if status.submitted.saturating_add(T::UndecidingTimeout::get()) >= now { - // Too long without being decided. + } else if now >= timeout { + // Too long without being decided - end it. + Self::cancel_referendum_alarm(index); Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); - return Some(ReferendumInfo::TimedOut(status.submission_deposit, status.decision_deposit)) + return (ReferendumInfo::TimedOut(status.submission_deposit, status.decision_deposit), true) } - Some(ReferendumInfo::Ongoing(status)) + + Self::set_referendum_alarm(index, alarm); + (ReferendumInfo::Ongoing(status), dirty) + } + + fn decision_time( + deciding: &DecidingStatusOf, + status: &ReferendumStatusOf, + track: &TrackInfoOf, + ) -> T::BlockNumber { + // Set alarm to the point where the current voting would make it pass. + let approvals = Perbill::from_rational(status.tally.ayes, status.tally.ayes + status.tally.nays); + let turnout = Perbill::from_rational(status.tally.turnout, T::Currency::total_issuance()); + let until_approvals = track.min_approvals.delay(approvals) * deciding.period; + let until_turnout = track.min_turnout.delay(turnout) * deciding.period; + deciding.ending.min(until_turnout.max(until_approvals)) } /// Reserve a deposit and return the `Deposit` instance. @@ -748,45 +818,11 @@ impl Pallet { } } - /// Returns the earliest block that advancing this referendum should do something useful. - pub fn next_referendum_advance(status: &ReferendumStatusOf) -> T::BlockNumber { - let now = frame_system::Pallet::::block_number(); - let track = match Self::track(status.track) { - Some(t) => t, - None => return now, - }; - if let Some(deciding) = status.deciding { - if let Some(confirming) = deciding.confirming { - confirming - } else { - let mut t = deciding.ending; - let approvals = Perbill::from_rational(status.tally.ayes, status.tally.ayes + status.tally.nays); - let turnout = Perbill::from_rational(status.tally.turnout, T::Currency::total_issuance()); - let until_approvals = track.min_approvals.delay(approvals) * deciding.period; - let until_turnout = track.min_turnout.delay(turnout) * deciding.period; - t.min(until_turnout.max(until_approvals)) - } - } else { - if status.decision_deposit.is_some() { - let prepare_end = status.submitted.saturating_add(track.prepare_period); - if now < prepare_end { - prepare_end - } else { - // Not yet bumped or too many being decided. - // Track should already be scheduled for a bump. - status.submitted + T::UndecidingTimeout::get() - } - } else { - status.submitted + T::UndecidingTimeout::get() - } - } - } - fn track(id: TrackIdOf) -> Option<&'static TrackInfoOf> { let tracks = T::Tracks::tracks(); let index = tracks.binary_search_by_key(&id, |x| x.0) .unwrap_or_else(|x| x); - tracks[index].1 + Some(&tracks[index].1) } } diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 9d7423dd57bb9..3636861cd1b2a 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -20,11 +20,9 @@ use super::*; use crate::{AccountVote, Conviction, Vote}; use codec::{Decode, Encode}; +use frame_support::Parameter; use scale_info::TypeInfo; -use sp_runtime::{ - traits::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Saturating, Zero}, - RuntimeDebug, -}; +use sp_runtime::{traits::{Saturating, Zero}, RuntimeDebug}; /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] @@ -73,10 +71,7 @@ impl Saturating for Delegations { } } -impl Tally where - Balance: From + Zero + Copy + CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + Bounded - + Saturating, -{ +impl Tally { /// Create a new tally. pub fn new(vote: Vote, balance: Balance) -> Self { let Delegations { votes, capital } = vote.conviction.votes(balance); @@ -159,7 +154,7 @@ impl Tally where Perbill::from_rational(self.ayes, self.ayes.saturating_add(self.nays)) } - pub fn is_passing( + pub fn is_passing( &self, t: Moment, period: Moment, @@ -168,7 +163,7 @@ impl Tally where approval_needed: Curve, ) -> bool { let x = Perbill::from_rational(t.min(period), period); - turnout_needed.passing(x, self.turnout) && approval_needed.passing(x, self.approval::()) + turnout_needed.passing(x, self.turnout(total)) && approval_needed.passing(x, self.approval()) } } @@ -216,7 +211,7 @@ pub struct TrackInfo { } pub trait TracksInfo { - type Id: Copy + Eq + Codec; + type Id: Copy + Eq + Codec + TypeInfo + Parameter + Ord + PartialOrd; type Origin; fn tracks() -> &'static [(Self::Id, TrackInfo)]; fn track_for(id: &Self::Origin) -> Result; @@ -272,10 +267,11 @@ pub struct ReferendumStatus, } -impl - ReferendumStatus +impl + ReferendumStatus { pub fn begin_deciding(&mut self, now: Moment, decision_period: Moment) { + self.ayes_in_queue = None; self.deciding = Some(DecidingStatus { ending: now.saturating_add(decision_period), period: decision_period, @@ -286,11 +282,9 @@ impl /// Info regarding a referendum, present or past. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub enum ReferendumInfo { +pub enum ReferendumInfo { /// Referendum has been submitted and is being voted on. - Ongoing(ReferendumStatus), - /// Referendum finished at `end` with approval. Submission deposit is held. - Confirmed(ReferendumStatus), + Ongoing(ReferendumStatus), /// Referendum finished at `end` with approval. Submission deposit is held. Approved(Deposit, Option>), /// Referendum finished at `end` with rejection. Submission deposit is held. @@ -299,14 +293,13 @@ pub enum ReferendumInfo, Option>), } -impl - ReferendumInfo +impl + ReferendumInfo { pub fn take_decision_deposit(&mut self) -> Option> { use ReferendumInfo::*; match self { Approved(_, d) | Rejected(_, d) | TimedOut(_, d) => d.take(), - Confirmed(status) => status.decision_deposit.take(), // Cannot refund deposit if Ongoing as this breaks assumptions. _ => None, } @@ -332,13 +325,13 @@ pub enum Curve { impl Curve { fn threshold(&self, x: Perbill) -> Perbill { match self { - Self::LinearDecreasing { begin, delta } => begin - delta * x, + Self::LinearDecreasing { begin, delta } => *begin - *delta * x, } } - pub fn delay(&self, y: Perbill) { + pub fn delay(&self, y: Perbill) -> Perbill { match self { Self::LinearDecreasing { begin, delta } => { - (begin - y.min(begin)).min(delta) / delta + (*begin - y.min(*begin)).min(*delta) / *delta }, } } diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index e51c6cd734113..94efbf5b52c40 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -151,6 +151,21 @@ impl> BoundedVec { S::get() as usize } + /// Exactly as `Vec::truncate`. + pub fn truncate(&mut self, s: usize) { + self.0.truncate(s); + } + + /// Forces the insertion of `s` into `self`, truncating `self` first, if necessary. + /// + /// Infallible, but if the limit is zero, then it's a no-op. + pub fn force_insert(&mut self, index: usize, element: T) { + if S::get() > 0 { + self.0.truncate(S::get() as usize - 1); + self.0.insert(index, element); + } + } + /// Consumes self and mutates self via the given `mutate` function. /// /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is From a13df25bbeca383adea5bb0bfbd656475e1cf09a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 6 Nov 2021 12:34:01 +0100 Subject: [PATCH 04/66] Fixes --- frame/referenda/src/lib.rs | 40 +++++++++++++----------- frame/referenda/src/types.rs | 4 +-- frame/support/src/storage/bounded_vec.rs | 32 ++++++++++++++----- 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 83110169343b5..011fd2cb74bba 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -96,7 +96,6 @@ type VotingOf = Voting< ::AccountId, ::BlockNumber, >; -type TracksInfoOf = ::Tracks; type TrackInfoOf = TrackInfo< BalanceOf, ::BlockNumber, @@ -109,22 +108,26 @@ type TrackIdOf = < >::Id; pub trait InsertSorted { + /// 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 + Ord, - >(&mut self, t: T, f: F,) -> Result<(), ()>; + >(&mut self, t: T, f: F,) -> bool; } impl> InsertSorted for BoundedVec { fn insert_sorted_by_key< F: FnMut(&T) -> K, K: PartialOrd + Ord, - >(&mut self, t: T, mut f: F,) -> Result<(), ()> { + >(&mut self, t: T, mut f: F,) -> bool { let index = self.binary_search_by_key::(&f(&t), f).unwrap_or_else(|x| x); if index >= S::get() as usize { - return Err(()) + return false } self.force_insert(index, t); - Ok(()) + true } } @@ -450,7 +453,7 @@ pub mod pallet { index: ReferendumIndex, ) -> DispatchResult { let who = ensure_signed(origin)?; - let status = Self::ensure_ongoing(index)?; + let mut status = Self::ensure_ongoing(index)?; ensure!(status.decision_deposit.is_none(), Error::::HaveDeposit); let track = Self::track(status.track).ok_or(Error::::NoTrack)?; status.decision_deposit = Some(Self::take_deposit(who, track.decision_deposit)?); @@ -549,6 +552,7 @@ impl Pallet { /// Sets a track's alarm call. Returns `true` if there was not already an alarm call in place /// for the same time. + #[allow(dead_code)] fn set_track_alarm(track: TrackIdOf, when: T::BlockNumber) -> bool { let scheduler_id = (ASSEMBLY_ID, "track", track).encode(); // NOTE: This only works because we know that that this ID will only ever be used for this @@ -624,9 +628,9 @@ impl Pallet { /// This should never be needed, since referenda should automatically begin when others end. fn advance_track(track: TrackIdOf) -> Result { let track_info = Self::track(track).ok_or(Error::::BadTrack)?; - let deciding_count = DecidingCount::::get(track); + let mut deciding_count = DecidingCount::::get(track); let mut track_queue = TrackQueue::::get(track); - let count = 0; + let mut count = 0; while deciding_count < track_info.max_deciding { if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { let now = frame_system::Pallet::::block_number(); @@ -704,11 +708,10 @@ impl Pallet { Some(x) => x, None => return (ReferendumInfo::Ongoing(status), false), }; - let mut alarm; let timeout = status.submitted + T::UndecidingTimeout::get(); + // Default the alarm to the submission timeout. + let mut alarm = timeout; if status.deciding.is_none() { - // Default the alarm to the submission timeout. - alarm = timeout; // Are we already queued for deciding? if let Some(_) = status.ayes_in_queue.as_ref() { // Does our position in the queue need updating? @@ -718,8 +721,7 @@ impl Pallet { let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x); if maybe_old_pos.is_none() && new_pos < queue.len() { // Just insert. - queue.truncate(queue.len() - 1); - queue.insert(new_pos, (index, ayes)); + queue.force_insert(new_pos, (index, ayes)); } else if let Some(old_pos) = maybe_old_pos { // We were in the queue - just update and sort. queue[old_pos].1 = ayes; @@ -748,8 +750,8 @@ impl Pallet { now, deciding.period, T::Currency::total_issuance(), - track.min_turnout, - track.min_approvals, + &track.min_turnout, + &track.min_approvals, ); if is_passing { if deciding.confirming.map_or(false, |c| now >= c) { @@ -777,7 +779,7 @@ impl Pallet { // Cannot be confirming dirty = deciding.confirming.is_some(); deciding.confirming = None; - alarm = Self::decision_time(&deciding, &status, track); + alarm = Self::decision_time(&deciding, &status.tally, track); } } else if now >= timeout { // Too long without being decided - end it. @@ -792,12 +794,12 @@ impl Pallet { fn decision_time( deciding: &DecidingStatusOf, - status: &ReferendumStatusOf, + tally: &Tally>, track: &TrackInfoOf, ) -> T::BlockNumber { // Set alarm to the point where the current voting would make it pass. - let approvals = Perbill::from_rational(status.tally.ayes, status.tally.ayes + status.tally.nays); - let turnout = Perbill::from_rational(status.tally.turnout, T::Currency::total_issuance()); + let approvals = Perbill::from_rational(tally.ayes, tally.ayes + tally.nays); + let turnout = Perbill::from_rational(tally.turnout, T::Currency::total_issuance()); let until_approvals = track.min_approvals.delay(approvals) * deciding.period; let until_turnout = track.min_turnout.delay(turnout) * deciding.period; deciding.ending.min(until_turnout.max(until_approvals)) diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 3636861cd1b2a..9a6d479abfa1e 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -159,8 +159,8 @@ impl Tally { t: Moment, period: Moment, total: Balance, - turnout_needed: Curve, - approval_needed: Curve, + turnout_needed: &Curve, + approval_needed: &Curve, ) -> bool { let x = Perbill::from_rational(t.min(period), period); turnout_needed.passing(x, self.turnout(total)) && approval_needed.passing(x, self.approval()) diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 94efbf5b52c40..c06bc827426eb 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -137,6 +137,17 @@ impl BoundedVec { ) -> Option<&mut >::Output> { self.0.get_mut(index) } + + /// Exactly the same semantics as [`Vec::truncate`]. + pub fn truncate(&mut self, s: usize) { + self.0.truncate(s); + } + + /// Exactly the same semantics as [`Vec::pop`]. + pub fn pop(&mut self) -> Option { + self.0.pop() + } + } impl> From> for Vec { @@ -151,21 +162,26 @@ impl> BoundedVec { S::get() as usize } - /// Exactly as `Vec::truncate`. - pub fn truncate(&mut self, s: usize) { - self.0.truncate(s); - } - - /// Forces the insertion of `s` into `self`, truncating `self` first, if necessary. + /// Forces the insertion of `s` into `self` truncating first if necessary. /// /// Infallible, but if the limit is zero, then it's a no-op. pub fn force_insert(&mut self, index: usize, element: T) { - if S::get() > 0 { - self.0.truncate(S::get() as usize - 1); + if Self::bound() > 0 { + self.0.truncate(Self::bound() as usize - 1); self.0.insert(index, element); } } + /// Forces the insertion of `s` into `self` truncating first if necessary. + /// + /// Infallible, but if the limit is zero, then it's a no-op. + pub fn force_push(&mut self, element: T) { + if Self::bound() > 0 { + self.0.truncate(Self::bound() as usize - 1); + self.0.push(element); + } + } + /// Consumes self and mutates self via the given `mutate` function. /// /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is From 4413936db31e06847055eeb27ac034fdb07ad216 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 8 Nov 2021 12:24:04 +0100 Subject: [PATCH 05/66] Add conviction-voting pallet --- Cargo.lock | 18 + Cargo.toml | 7 +- frame/conviction-voting/Cargo.toml | 52 ++ frame/conviction-voting/README.md | 8 + frame/conviction-voting/src/benchmarking.rs | 811 ++++++++++++++++++ .../src/conviction.rs | 0 frame/conviction-voting/src/lib.rs | 558 ++++++++++++ frame/conviction-voting/src/tests.rs | 302 +++++++ .../src/tests/cancellation.rs | 96 +++ frame/conviction-voting/src/tests/decoders.rs | 85 ++ .../conviction-voting/src/tests/delegation.rs | 179 ++++ .../src/tests/external_proposing.rs | 303 +++++++ .../src/tests/fast_tracking.rs | 98 +++ .../src/tests/lock_voting.rs | 377 ++++++++ frame/conviction-voting/src/tests/preimage.rs | 219 +++++ .../src/tests/public_proposals.rs | 149 ++++ .../conviction-voting/src/tests/scheduling.rs | 156 ++++ frame/conviction-voting/src/tests/voting.rs | 169 ++++ frame/conviction-voting/src/types.rs | 176 ++++ .../src/vote.rs | 0 frame/conviction-voting/src/weights.rs | 545 ++++++++++++ frame/referenda/src/lib.rs | 569 ++---------- frame/referenda/src/types.rs | 236 ++--- frame/support/src/traits.rs | 4 +- frame/support/src/traits/voting.rs | 18 +- 25 files changed, 4449 insertions(+), 686 deletions(-) create mode 100644 frame/conviction-voting/Cargo.toml create mode 100644 frame/conviction-voting/README.md create mode 100644 frame/conviction-voting/src/benchmarking.rs rename frame/{referenda => conviction-voting}/src/conviction.rs (100%) create mode 100644 frame/conviction-voting/src/lib.rs create mode 100644 frame/conviction-voting/src/tests.rs create mode 100644 frame/conviction-voting/src/tests/cancellation.rs create mode 100644 frame/conviction-voting/src/tests/decoders.rs create mode 100644 frame/conviction-voting/src/tests/delegation.rs create mode 100644 frame/conviction-voting/src/tests/external_proposing.rs create mode 100644 frame/conviction-voting/src/tests/fast_tracking.rs create mode 100644 frame/conviction-voting/src/tests/lock_voting.rs create mode 100644 frame/conviction-voting/src/tests/preimage.rs create mode 100644 frame/conviction-voting/src/tests/public_proposals.rs create mode 100644 frame/conviction-voting/src/tests/scheduling.rs create mode 100644 frame/conviction-voting/src/tests/voting.rs create mode 100644 frame/conviction-voting/src/types.rs rename frame/{referenda => conviction-voting}/src/vote.rs (100%) create mode 100644 frame/conviction-voting/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 6ac784717f0f1..6552d6811ae3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5404,6 +5404,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-conviction-voting" +version = "4.0.0-dev" +dependencies = [ + "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" diff --git a/Cargo.toml b/Cargo.toml index c609f9df545d6..607045e1c89c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,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", @@ -80,6 +83,7 @@ members = [ "frame/contracts", "frame/contracts/rpc", "frame/contracts/rpc/runtime-api", + "frame/conviction-voting", "frame/democracy", "frame/try-runtime", "frame/elections", @@ -135,9 +139,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", diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml new file mode 100644 index 0000000000000..3d55069203aff --- /dev/null +++ b/frame/conviction-voting/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "pallet-conviction-voting" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +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 = "4.0.0-dev", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "4.0.0-dev", 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" } + +[dev-dependencies] +sp-core = { version = "4.0.0-dev", 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"] diff --git a/frame/conviction-voting/README.md b/frame/conviction-voting/README.md new file mode 100644 index 0000000000000..5dc5d526d5c23 --- /dev/null +++ b/frame/conviction-voting/README.md @@ -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. diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs new file mode 100644 index 0000000000000..34bcb0da301e6 --- /dev/null +++ b/frame/conviction-voting/src/benchmarking.rs @@ -0,0 +1,811 @@ +// 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 frame_benchmarking::{account, benchmarks, whitelist_account}; +use frame_support::{ + assert_noop, assert_ok, + codec::Decode, + traits::{ + schedule::DispatchTime, Currency, EnsureOrigin, Get, OnInitialize, UnfilteredDispatchable, + }, +}; +use frame_system::{Pallet as System, RawOrigin}; +use sp_runtime::traits::{BadOrigin, Bounded, One}; + +use crate::Pallet as Democracy; + +const SEED: u32 = 0; +const MAX_REFERENDUMS: u32 = 99; +const MAX_SECONDERS: u32 = 100; +const MAX_BYTES: u32 = 16_384; + +fn assert_last_event(generic_event: ::Event) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn funded_account(name: &'static str, index: u32) -> T::AccountId { + let caller: T::AccountId = account(name, index, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + caller +} + +fn add_proposal(n: u32) -> Result { + let other = funded_account::("proposer", n); + let value = T::MinimumDeposit::get(); + let proposal_hash: T::Hash = T::Hashing::hash_of(&n); + + Democracy::::propose(RawOrigin::Signed(other).into(), proposal_hash, value.into())?; + + Ok(proposal_hash) +} + +fn add_referendum(n: u32) -> Result { + let proposal_hash: T::Hash = T::Hashing::hash_of(&n); + let vote_threshold = VoteThreshold::SimpleMajority; + + Democracy::::inject_referendum( + T::LaunchPeriod::get(), + proposal_hash, + vote_threshold, + 0u32.into(), + ); + let referendum_index: ReferendumIndex = ReferendumCount::::get() - 1; + T::Scheduler::schedule_named( + (DEMOCRACY_ID, referendum_index).encode(), + DispatchTime::At(2u32.into()), + None, + 63, + frame_system::RawOrigin::Root.into(), + Call::enact_proposal { proposal_hash, index: referendum_index }.into(), + ) + .map_err(|_| "failed to schedule named")?; + Ok(referendum_index) +} + +fn account_vote(b: BalanceOf) -> AccountVote> { + let v = Vote { aye: true, conviction: Conviction::Locked1x }; + + AccountVote::Standard { vote: v, balance: b } +} + +benchmarks! { + propose { + let p = T::MaxProposals::get(); + + for i in 0 .. (p - 1) { + add_proposal::(i)?; + } + + let caller = funded_account::("caller", 0); + let proposal_hash: T::Hash = T::Hashing::hash_of(&0); + let value = T::MinimumDeposit::get(); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), proposal_hash, value.into()) + verify { + assert_eq!(Democracy::::public_props().len(), p as usize, "Proposals not created."); + } + + second { + let s in 0 .. MAX_SECONDERS; + + let caller = funded_account::("caller", 0); + let proposal_hash = add_proposal::(s)?; + + // Create s existing "seconds" + for i in 0 .. s { + let seconder = funded_account::("seconder", i); + Democracy::::second(RawOrigin::Signed(seconder).into(), 0, u32::MAX)?; + } + + let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; + assert_eq!(deposits.0.len(), (s + 1) as usize, "Seconds not recorded"); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), 0, u32::MAX) + verify { + let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; + assert_eq!(deposits.0.len(), (s + 2) as usize, "`second` benchmark did not work"); + } + + vote_new { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", 0); + let account_vote = account_vote::(100u32.into()); + + // We need to create existing direct votes + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes were not recorded."); + + let referendum_index = add_referendum::(r)?; + whitelist_account!(caller); + }: vote(RawOrigin::Signed(caller.clone()), referendum_index, account_vote) + verify { + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r + 1) as usize, "Vote was not recorded."); + } + + vote_existing { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", 0); + let account_vote = account_vote::(100u32.into()); + + // We need to create existing direct votes + for i in 0 ..=r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded."); + + // Change vote from aye to nay + let nay = Vote { aye: false, conviction: Conviction::Locked1x }; + let new_vote = AccountVote::Standard { vote: nay, balance: 1000u32.into() }; + let referendum_index = Democracy::::referendum_count() - 1; + + // This tests when a user changes a vote + whitelist_account!(caller); + }: vote(RawOrigin::Signed(caller.clone()), referendum_index, new_vote) + verify { + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r + 1) as usize, "Vote was incorrectly added"); + let referendum_info = Democracy::::referendum_info(referendum_index) + .ok_or("referendum doesn't exist")?; + let tally = match referendum_info { + ReferendumInfo::Ongoing(r) => r.tally, + _ => return Err("referendum not ongoing".into()), + }; + assert_eq!(tally.nays, 1000u32.into(), "changed vote was not recorded"); + } + + emergency_cancel { + let origin = T::CancellationOrigin::successful_origin(); + let referendum_index = add_referendum::(0)?; + assert_ok!(Democracy::::referendum_status(referendum_index)); + }: _(origin, referendum_index) + verify { + // Referendum has been canceled + assert_noop!( + Democracy::::referendum_status(referendum_index), + Error::::ReferendumInvalid, + ); + } + + blacklist { + let p in 1 .. T::MaxProposals::get(); + + // Place our proposal at the end to make sure it's worst case. + for i in 0 .. p - 1 { + add_proposal::(i)?; + } + // We should really add a lot of seconds here, but we're not doing it elsewhere. + + // Place our proposal in the external queue, too. + let hash = T::Hashing::hash_of(&0); + assert_ok!( + Democracy::::external_propose(T::ExternalOrigin::successful_origin(), hash.clone()) + ); + let origin = T::BlacklistOrigin::successful_origin(); + // Add a referendum of our proposal. + let referendum_index = add_referendum::(0)?; + assert_ok!(Democracy::::referendum_status(referendum_index)); + }: _(origin, hash, Some(referendum_index)) + verify { + // Referendum has been canceled + assert_noop!( + Democracy::::referendum_status(referendum_index), + Error::::ReferendumInvalid + ); + } + + // Worst case scenario, we external propose a previously blacklisted proposal + external_propose { + let v in 1 .. MAX_VETOERS as u32; + + let origin = T::ExternalOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&0); + // Add proposal to blacklist with block number 0 + Blacklist::::insert( + proposal_hash, + (T::BlockNumber::zero(), vec![T::AccountId::default(); v as usize]) + ); + }: _(origin, proposal_hash) + verify { + // External proposal created + ensure!(>::exists(), "External proposal didn't work"); + } + + external_propose_majority { + let origin = T::ExternalMajorityOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&0); + }: _(origin, proposal_hash) + verify { + // External proposal created + ensure!(>::exists(), "External proposal didn't work"); + } + + external_propose_default { + let origin = T::ExternalDefaultOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&0); + }: _(origin, proposal_hash) + verify { + // External proposal created + ensure!(>::exists(), "External proposal didn't work"); + } + + fast_track { + let origin_propose = T::ExternalDefaultOrigin::successful_origin(); + let proposal_hash: T::Hash = T::Hashing::hash_of(&0); + Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; + + // NOTE: Instant origin may invoke a little bit more logic, but may not always succeed. + let origin_fast_track = T::FastTrackOrigin::successful_origin(); + let voting_period = T::FastTrackVotingPeriod::get(); + let delay = 0u32; + }: _(origin_fast_track, proposal_hash, voting_period.into(), delay.into()) + verify { + assert_eq!(Democracy::::referendum_count(), 1, "referendum not created") + } + + veto_external { + // Existing veto-ers + let v in 0 .. MAX_VETOERS as u32; + + let proposal_hash: T::Hash = T::Hashing::hash_of(&v); + + let origin_propose = T::ExternalDefaultOrigin::successful_origin(); + Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; + + let mut vetoers: Vec = Vec::new(); + for i in 0 .. v { + vetoers.push(account("vetoer", i, SEED)); + } + vetoers.sort(); + Blacklist::::insert(proposal_hash, (T::BlockNumber::zero(), vetoers)); + + let origin = T::VetoOrigin::successful_origin(); + ensure!(NextExternal::::get().is_some(), "no external proposal"); + }: _(origin, proposal_hash) + verify { + assert!(NextExternal::::get().is_none()); + let (_, new_vetoers) = >::get(&proposal_hash).ok_or("no blacklist")?; + assert_eq!(new_vetoers.len(), (v + 1) as usize, "vetoers not added"); + } + + cancel_proposal { + let p in 1 .. T::MaxProposals::get(); + + // Place our proposal at the end to make sure it's worst case. + for i in 0 .. p { + add_proposal::(i)?; + } + }: _(RawOrigin::Root, 0) + + cancel_referendum { + let referendum_index = add_referendum::(0)?; + }: _(RawOrigin::Root, referendum_index) + + cancel_queued { + let r in 1 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; // This add one element in the scheduler + } + + let referendum_index = add_referendum::(r)?; + }: _(RawOrigin::Root, referendum_index) + + // This measures the path of `launch_next` external. Not currently used as we simply + // assume the weight is `MaxBlockWeight` when executing. + #[extra] + on_initialize_external { + let r in 0 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; + } + + assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); + + // Launch external + LastTabledWasExternal::::put(false); + + let origin = T::ExternalMajorityOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&r); + let call = Call::::external_propose_majority { proposal_hash }; + call.dispatch_bypass_filter(origin)?; + // External proposal created + ensure!(>::exists(), "External proposal didn't work"); + + let block_number = T::LaunchPeriod::get(); + + }: { Democracy::::on_initialize(block_number) } + verify { + // One extra because of next external + assert_eq!(Democracy::::referendum_count(), r + 1, "referenda not created"); + ensure!(!>::exists(), "External wasn't taken"); + + // All but the new next external should be finished + for i in 0 .. r { + if let Some(value) = ReferendumInfoOf::::get(i) { + match value { + ReferendumInfo::Finished { .. } => (), + ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()), + } + } + } + } + + // This measures the path of `launch_next` public. Not currently used as we simply + // assume the weight is `MaxBlockWeight` when executing. + #[extra] + on_initialize_public { + let r in 1 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; + } + + assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); + + // Launch public + assert!(add_proposal::(r).is_ok(), "proposal not created"); + LastTabledWasExternal::::put(true); + + let block_number = T::LaunchPeriod::get(); + + }: { Democracy::::on_initialize(block_number) } + verify { + // One extra because of next public + assert_eq!(Democracy::::referendum_count(), r + 1, "proposal not accepted"); + + // All should be finished + for i in 0 .. r { + if let Some(value) = ReferendumInfoOf::::get(i) { + match value { + ReferendumInfo::Finished { .. } => (), + ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()), + } + } + } + } + + // No launch no maturing referenda. + on_initialize_base { + let r in 1 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; + } + + for (key, mut info) in ReferendumInfoOf::::iter() { + if let ReferendumInfo::Ongoing(ref mut status) = info { + status.end += 100u32.into(); + } + ReferendumInfoOf::::insert(key, info); + } + + assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); + assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); + + }: { Democracy::::on_initialize(1u32.into()) } + verify { + // All should be on going + for i in 0 .. r { + if let Some(value) = ReferendumInfoOf::::get(i) { + match value { + ReferendumInfo::Finished { .. } => return Err("Referendum has been finished".into()), + ReferendumInfo::Ongoing(_) => (), + } + } + } + } + + on_initialize_base_with_launch_period { + let r in 1 .. MAX_REFERENDUMS; + + for i in 0..r { + add_referendum::(i)?; + } + + for (key, mut info) in ReferendumInfoOf::::iter() { + if let ReferendumInfo::Ongoing(ref mut status) = info { + status.end += 100u32.into(); + } + ReferendumInfoOf::::insert(key, info); + } + + assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); + assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); + + let block_number = T::LaunchPeriod::get(); + + }: { Democracy::::on_initialize(block_number) } + verify { + // All should be on going + for i in 0 .. r { + if let Some(value) = ReferendumInfoOf::::get(i) { + match value { + ReferendumInfo::Finished { .. } => return Err("Referendum has been finished".into()), + ReferendumInfo::Ongoing(_) => (), + } + } + } + } + + delegate { + let r in 1 .. MAX_REFERENDUMS; + + let initial_balance: BalanceOf = 100u32.into(); + let delegated_balance: BalanceOf = 1000u32.into(); + + let caller = funded_account::("caller", 0); + // Caller will initially delegate to `old_delegate` + let old_delegate: T::AccountId = funded_account::("old_delegate", r); + Democracy::::delegate( + RawOrigin::Signed(caller.clone()).into(), + old_delegate.clone(), + Conviction::Locked1x, + delegated_balance, + )?; + let (target, balance) = match VotingOf::::get(&caller) { + Voting::Delegating { target, balance, .. } => (target, balance), + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(target, old_delegate, "delegation target didn't work"); + assert_eq!(balance, delegated_balance, "delegation balance didn't work"); + // Caller will now switch to `new_delegate` + let new_delegate: T::AccountId = funded_account::("new_delegate", r); + let account_vote = account_vote::(initial_balance); + // We need to create existing direct votes for the `new_delegate` + for i in 0..r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote.clone())?; + } + let votes = match VotingOf::::get(&new_delegate) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes were not recorded."); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller.clone()), new_delegate.clone(), Conviction::Locked1x, delegated_balance) + verify { + let (target, balance) = match VotingOf::::get(&caller) { + Voting::Delegating { target, balance, .. } => (target, balance), + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(target, new_delegate, "delegation target didn't work"); + assert_eq!(balance, delegated_balance, "delegation balance didn't work"); + let delegations = match VotingOf::::get(&new_delegate) { + Voting::Direct { delegations, .. } => delegations, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(delegations.capital, delegated_balance, "delegation was not recorded."); + } + + undelegate { + let r in 1 .. MAX_REFERENDUMS; + + let initial_balance: BalanceOf = 100u32.into(); + let delegated_balance: BalanceOf = 1000u32.into(); + + let caller = funded_account::("caller", 0); + // Caller will delegate + let the_delegate: T::AccountId = funded_account::("delegate", r); + Democracy::::delegate( + RawOrigin::Signed(caller.clone()).into(), + the_delegate.clone(), + Conviction::Locked1x, + delegated_balance, + )?; + let (target, balance) = match VotingOf::::get(&caller) { + Voting::Delegating { target, balance, .. } => (target, balance), + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(target, the_delegate, "delegation target didn't work"); + assert_eq!(balance, delegated_balance, "delegation balance didn't work"); + // We need to create votes direct votes for the `delegate` + let account_vote = account_vote::(initial_balance); + for i in 0..r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote( + RawOrigin::Signed(the_delegate.clone()).into(), + ref_idx, + account_vote.clone() + )?; + } + let votes = match VotingOf::::get(&the_delegate) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes were not recorded."); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller.clone())) + verify { + // Voting should now be direct + match VotingOf::::get(&caller) { + Voting::Direct { .. } => (), + _ => return Err("undelegation failed".into()), + } + } + + clear_public_proposals { + add_proposal::(0)?; + + }: _(RawOrigin::Root) + + note_preimage { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + let caller = funded_account::("caller", 0); + let encoded_proposal = vec![1; b as usize]; + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) + verify { + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + match Preimages::::get(proposal_hash) { + Some(PreimageStatus::Available { .. }) => (), + _ => return Err("preimage not available".into()) + } + } + + note_imminent_preimage { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + // d + 1 to include the one we are testing + let encoded_proposal = vec![1; b as usize]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + let block_number = T::BlockNumber::one(); + Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number)); + + let caller = funded_account::("caller", 0); + let encoded_proposal = vec![1; b as usize]; + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) + verify { + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + match Preimages::::get(proposal_hash) { + Some(PreimageStatus::Available { .. }) => (), + _ => return Err("preimage not available".into()) + } + } + + reap_preimage { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + let encoded_proposal = vec![1; b as usize]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + + let submitter = funded_account::("submitter", b); + Democracy::::note_preimage(RawOrigin::Signed(submitter.clone()).into(), encoded_proposal.clone())?; + + // We need to set this otherwise we get `Early` error. + let block_number = T::VotingPeriod::get() + T::EnactmentPeriod::get() + T::BlockNumber::one(); + System::::set_block_number(block_number.into()); + + assert!(Preimages::::contains_key(proposal_hash)); + + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), proposal_hash.clone(), u32::MAX) + verify { + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + assert!(!Preimages::::contains_key(proposal_hash)); + } + + // Test when unlock will remove locks + unlock_remove { + let r in 1 .. MAX_REFERENDUMS; + + let locker = funded_account::("locker", 0); + // Populate votes so things are locked + let base_balance: BalanceOf = 100u32.into(); + let small_vote = account_vote::(base_balance); + // Vote and immediately unvote + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; + Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_idx)?; + } + + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + }: unlock(RawOrigin::Signed(caller), locker.clone()) + verify { + // Note that we may want to add a `get_lock` api to actually verify + let voting = VotingOf::::get(&locker); + assert_eq!(voting.locked_balance(), BalanceOf::::zero()); + } + + // Test when unlock will set a new value + unlock_set { + let r in 1 .. MAX_REFERENDUMS; + + let locker = funded_account::("locker", 0); + // Populate votes so things are locked + let base_balance: BalanceOf = 100u32.into(); + let small_vote = account_vote::(base_balance); + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; + } + + // Create a big vote so lock increases + let big_vote = account_vote::(base_balance * 10u32.into()); + let referendum_index = add_referendum::(r)?; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), referendum_index, big_vote)?; + + let votes = match VotingOf::::get(&locker) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded."); + + let voting = VotingOf::::get(&locker); + assert_eq!(voting.locked_balance(), base_balance * 10u32.into()); + + Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), referendum_index)?; + + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + }: unlock(RawOrigin::Signed(caller), locker.clone()) + verify { + let votes = match VotingOf::::get(&locker) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Vote was not removed"); + + let voting = VotingOf::::get(&locker); + // Note that we may want to add a `get_lock` api to actually verify + assert_eq!(voting.locked_balance(), base_balance); + } + + remove_vote { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", 0); + let account_vote = account_vote::(100u32.into()); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes not created"); + + let referendum_index = r - 1; + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller.clone()), referendum_index) + verify { + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); + } + + // Worst case is when target == caller and referendum is ongoing + remove_other_vote { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", r); + let account_vote = account_vote::(100u32.into()); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes not created"); + + let referendum_index = r - 1; + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller.clone()), caller.clone(), referendum_index) + verify { + let votes = match VotingOf::::get(&caller) { + Voting::Direct { votes, .. } => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); + } + + #[extra] + enact_proposal_execute { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + let proposer = funded_account::("proposer", 0); + let raw_call = Call::note_preimage { encoded_proposal: vec![1; b as usize] }; + let generic_call: T::Proposal = raw_call.into(); + let encoded_proposal = generic_call.encode(); + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; + + match Preimages::::get(proposal_hash) { + Some(PreimageStatus::Available { .. }) => (), + _ => return Err("preimage not available".into()) + } + }: enact_proposal(RawOrigin::Root, proposal_hash, 0) + verify { + // Fails due to mismatched origin + assert_last_event::(Event::::Executed(0, Err(BadOrigin.into())).into()); + } + + #[extra] + enact_proposal_slash { + // Num of bytes in encoded proposal + let b in 0 .. MAX_BYTES; + + let proposer = funded_account::("proposer", 0); + // Random invalid bytes + let encoded_proposal = vec![200; b as usize]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; + + match Preimages::::get(proposal_hash) { + Some(PreimageStatus::Available { .. }) => (), + _ => return Err("preimage not available".into()) + } + let origin = RawOrigin::Root.into(); + let call = Call::::enact_proposal { proposal_hash, index: 0 }.encode(); + }: { + assert_eq!( + as Decode>::decode(&mut &*call) + .expect("call is encoded above, encoding must be correct") + .dispatch_bypass_filter(origin), + Err(Error::::PreimageInvalid.into()) + ); + } + + impl_benchmark_test_suite!( + Democracy, + crate::tests::new_test_ext(), + crate::tests::Test + ); +} diff --git a/frame/referenda/src/conviction.rs b/frame/conviction-voting/src/conviction.rs similarity index 100% rename from frame/referenda/src/conviction.rs rename to frame/conviction-voting/src/conviction.rs diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs new file mode 100644 index 0000000000000..b417e3168e548 --- /dev/null +++ b/frame/conviction-voting/src/lib.rs @@ -0,0 +1,558 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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.0sp_runtime::{DispatchResult, traits::One}asp_runtime::{DispatchResult, traits::AtLeast32BitUnsigned} 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 referenda. + +#![recursion_limit = "256"] +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Encode, Codec}; +use frame_support::{BoundedVec, ensure, traits::{Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, Referenda, ReservableCurrency, WithdrawReasons, schedule::{DispatchTime, Named as ScheduleNamed}}}; +use scale_info::TypeInfo; +use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, Perbill, traits::{Dispatchable, Saturating, One, AtLeast32BitUnsigned}}; +use sp_std::{prelude::*, fmt::Debug}; + +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, 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 = + <::Currency as Currency<::AccountId>>::Balance; +type NegativeImbalanceOf = <::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; +type VotingOf = Voting< + BalanceOf, + ::AccountId, + ::BlockNumber, +>; +type ReferendumIndexOf = <::Referenda as Referenda>::Index; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::EnsureOrigin, + weights::Pays, + Parameter, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::DispatchResult; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + Sized { + // System level stuff. + type Event: From> + IsType<::Event>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + /// Currency type with which voting happens. + type Currency: ReservableCurrency + + LockableCurrency; + + /// The implementation of the logic which conducts referenda. + type Referenda: Referenda>; + + /// 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; + + /// 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; + } + + /// All votes for a particular voter. We store the balance for the number of votes that we + /// have recorded. The second item is the total amount of delegations, that will be added. + /// + /// TWOX-NOTE: SAFE as `AccountId`s are crypto hashes anyway. + #[pallet::storage] + pub type VotingFor = StorageMap< + _, + Twox64Concat, + T::AccountId, + VotingOf, + 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 = StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// 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 { + /// Value too low + ValueLow, + /// Referendum is not ongoing. + NotOngoing, + /// Referendum's decision deposit is already paid. + HaveDeposit, + /// Proposal does not exist + ProposalMissing, + /// Cannot cancel the same proposal twice + AlreadyCanceled, + /// Proposal already made + DuplicateProposal, + /// Vote given for invalid referendum + ReferendumInvalid, + /// The given account did not vote on the referendum. + NotVoter, + /// The actor has no permission to conduct the action. + NoPermission, + /// The account is already delegating. + AlreadyDelegating, + /// Too high a balance was provided that the account cannot afford. + InsufficientFunds, + /// The account is not currently delegating. + NotDelegating, + /// The account currently has votes attached to it and the operation cannot succeed until + /// these are removed, either through `unvote` or `reap_vote`. + VotesExist, + /// Delegation to oneself makes no sense. + Nonsense, + /// Invalid upper bound. + WrongUpperBound, + /// Maximum number of votes reached. + MaxVotesReached, + } + + #[pallet::call] + impl Pallet { + /// Vote in a referendum. If `vote.is_aye()`, the vote is to enact the proposal; + /// otherwise it is a vote to keep the status quo. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `ref_index`: The index of the referendum to vote for. + /// - `vote`: The vote configuration. + /// + /// Weight: `O(R)` where R is the number of referenda the voter has voted on. + #[pallet::weight( + T::WeightInfo::vote_new(T::MaxVotes::get()) + .max(T::WeightInfo::vote_existing(T::MaxVotes::get())) + )] + pub fn vote( + origin: OriginFor, + #[pallet::compact] ref_index: ReferendumIndexOf, + vote: AccountVote>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::try_vote(&who, ref_index, vote) + } + + /// Delegate the voting power (with some given conviction) of the sending account. + /// + /// The balance delegated is locked for as long as it's delegated, and thereafter for the + /// time appropriate for the conviction's lock period. + /// + /// The dispatch origin of this call must be _Signed_, and the signing account must either: + /// - be delegating already; or + /// - have no voting activity (if there is, then it will need to be removed/consolidated + /// through `reap_vote` or `unvote`). + /// + /// - `to`: The account whose voting the `target` account's voting power will follow. + /// - `conviction`: The conviction that will be attached to the delegated votes. When the + /// account is undelegated, the funds will be locked for the corresponding period. + /// - `balance`: The amount of the account's balance to be used in delegating. This must not + /// be more than the account's current balance. + /// + /// Emits `Delegated`. + /// + /// Weight: `O(R)` where R is the number of referenda the voter delegating to has + /// voted on. Weight is charged as if maximum votes. + // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure + // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] + pub fn delegate( + origin: OriginFor, + to: T::AccountId, + conviction: Conviction, + balance: BalanceOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let votes = Self::try_delegate(who, to, conviction, balance)?; + + Ok(Some(T::WeightInfo::delegate(votes)).into()) + } + + /// Undelegate the voting power of the sending account. + /// + /// Tokens may be unlocked following once an amount of time consistent with the lock period + /// of the conviction with which the delegation was issued. + /// + /// The dispatch origin of this call must be _Signed_ and the signing account must be + /// currently delegating. + /// + /// Emits `Undelegated`. + /// + /// Weight: `O(R)` where R is the number of referenda the voter delegating to has + /// voted on. Weight is charged as if maximum votes. + // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure + // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] + pub fn undelegate(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let votes = Self::try_undelegate(who)?; + Ok(Some(T::WeightInfo::undelegate(votes)).into()) + } + + /// Unlock tokens that have an expired lock. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `target`: The account to remove the lock on. + /// + /// Weight: `O(R)` with R number of vote of target. + #[pallet::weight( + T::WeightInfo::unlock_set(T::MaxVotes::get()) + .max(T::WeightInfo::unlock_remove(T::MaxVotes::get())) + )] + pub fn unlock(origin: OriginFor, target: T::AccountId) -> DispatchResult { + ensure_signed(origin)?; + Self::update_lock(&target); + Ok(()) + } + + /// Remove a vote for a referendum. + /// + /// If: + /// - the referendum was cancelled, or + /// - the referendum is ongoing, or + /// - the referendum has ended such that + /// - the vote of the account was in opposition to the result; or + /// - there was no conviction to the account's vote; or + /// - the account made a split vote + /// ...then the vote is removed cleanly and a following call to `unlock` may result in more + /// funds being available. + /// + /// If, however, the referendum has ended and: + /// - it finished corresponding to the vote of the account, and + /// - the account made a standard vote with conviction, and + /// - the lock period of the conviction is not over + /// ...then the lock will be aggregated into the overall account's lock, which may involve + /// *overlocking* (where the two locks are combined into a single lock that is the maximum + /// of both the amount locked and the time is it locked for). + /// + /// The dispatch origin of this call must be _Signed_, and the signer must have a vote + /// registered for referendum `index`. + /// + /// - `index`: The index of referendum of the vote to be removed. + /// + /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// Weight is calculated for the maximum number of vote. + #[pallet::weight(T::WeightInfo::remove_vote(T::MaxVotes::get()))] + pub fn remove_vote(origin: OriginFor, index: ReferendumIndexOf) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::try_remove_vote(&who, index, UnvoteScope::Any) + } + + /// Remove a vote for a referendum. + /// + /// If the `target` is equal to the signer, then this function is exactly equivalent to + /// `remove_vote`. If not equal to the signer, then the vote must have expired, + /// either because the referendum was cancelled, because the voter lost the referendum or + /// because the conviction period is over. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `target`: The account of the vote to be removed; this account must have voted for + /// referendum `index`. + /// - `index`: The index of referendum of the vote to be removed. + /// + /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// Weight is calculated for the maximum number of vote. + #[pallet::weight(T::WeightInfo::remove_other_vote(T::MaxVotes::get()))] + pub fn remove_other_vote( + origin: OriginFor, + target: T::AccountId, + index: ReferendumIndexOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; + Self::try_remove_vote(&target, index, scope)?; + Ok(()) + } + } +} + +impl Pallet { + /// Actually enact a vote, if legit. + fn try_vote( + who: &T::AccountId, + ref_index: ReferendumIndexOf, + vote: AccountVote>, + ) -> DispatchResult { + let mut status = Self::referendum_status(ref_index)?; + ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); + VotingFor::::try_mutate(who, |voting| -> DispatchResult { + if let Voting::Direct { ref mut votes, delegations, .. } = voting { + match votes.binary_search_by_key(&ref_index, |i| i.0) { + Ok(i) => { + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; + if let Some(approve) = votes[i].1.as_standard() { + status.tally.reduce(approve, *delegations); + } + votes[i].1 = vote; + }, + Err(i) => { + ensure!( + votes.len() as u32 <= T::MaxVotes::get(), + Error::::MaxVotesReached + ); + votes.insert(i, (ref_index, vote)); + }, + } + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.add(vote).ok_or(ArithmeticError::Overflow)?; + if let Some(approve) = vote.as_standard() { + status.tally.increase(approve, *delegations); + } + Ok(()) + } else { + Err(Error::::AlreadyDelegating.into()) + } + })?; + // Extend the lock to `balance` (rather than setting it) since we don't know what other + // votes are in place. + T::Currency::extend_lock(CONVICTION_VOTING_ID, who, vote.balance(), WithdrawReasons::TRANSFER); + ReferendumInfoFor::::insert(ref_index, ReferendumInfo::Ongoing(status)); + Ok(()) + } + /* + + /// Remove the account's vote for the given referendum if possible. This is possible when: + /// - The referendum has not finished. + /// - The referendum has finished and the voter lost their direction. + /// - The referendum has finished and the voter's lock period is up. + /// + /// This will generally be combined with a call to `unlock`. + fn try_remove_vote( + who: &T::AccountId, + ref_index: ReferendumIndexOf, + scope: UnvoteScope, + ) -> DispatchResult { + let info = ReferendumInfoFor::::get(ref_index); + VotingFor::::try_mutate(who, |voting| -> DispatchResult { + if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting { + let i = votes + .binary_search_by_key(&ref_index, |i| i.0) + .map_err(|_| Error::::NotVoter)?; + match info { + Some(ReferendumInfo::Ongoing(mut status)) => { + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; + if let Some(approve) = votes[i].1.as_standard() { + status.tally.reduce(approve, *delegations); + } + ReferendumInfoFor::::insert(ref_index, ReferendumInfo::Ongoing(status)); + }, + Some(ReferendumInfo::Finished { end, approved }) => { + if let Some((lock_periods, balance)) = votes[i].1.locked_if(approved) { + let unlock_at = end + T::VoteLockingPeriod::get() * lock_periods.into(); + let now = frame_system::Pallet::::block_number(); + if now < unlock_at { + ensure!( + matches!(scope, UnvoteScope::Any), + Error::::NoPermission + ); + prior.accumulate(unlock_at, balance) + } + } + }, + None => {}, // Referendum was cancelled. + } + votes.remove(i); + } + Ok(()) + })?; + Ok(()) + } + + /// Return the number of votes for `who` + fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { + VotingFor::::mutate(who, |voting| match voting { + Voting::Delegating { delegations, .. } => { + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_add(amount); + 1 + }, + Voting::Direct { votes, delegations, .. } => { + *delegations = delegations.saturating_add(amount); + for &(ref_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + ReferendumInfoFor::::mutate(ref_index, |maybe_info| { + if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { + status.tally.increase(vote.aye, amount); + } + }); + } + } + votes.len() as u32 + }, + }) + } + + /// Return the number of votes for `who` + fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { + VotingFor::::mutate(who, |voting| match voting { + Voting::Delegating { delegations, .. } => { + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_sub(amount); + 1 + }, + Voting::Direct { votes, delegations, .. } => { + *delegations = delegations.saturating_sub(amount); + for &(ref_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + ReferendumInfoFor::::mutate(ref_index, |maybe_info| { + if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { + status.tally.reduce(vote.aye, amount); + } + }); + } + } + votes.len() as u32 + }, + }) + } +*/ + /// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`. + /// + /// Return the upstream number of votes. + fn try_delegate( + who: T::AccountId, + target: T::AccountId, + conviction: Conviction, + balance: BalanceOf, + ) -> Result { + ensure!(who != target, Error::::Nonsense); + ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); + let votes = VotingFor::::try_mutate(&who, |voting| -> Result { + let mut old = Voting::Delegating { + balance, + target: target.clone(), + conviction, + delegations: Default::default(), + prior: Default::default(), + }; + sp_std::mem::swap(&mut old, voting); + match old { + Voting::Delegating { balance, target, conviction, delegations, prior, .. } => { + // remove any delegation votes to our current target. + Self::reduce_upstream_delegation(&target, conviction.votes(balance)); + voting.set_common(delegations, prior); + }, + Voting::Direct { votes, delegations, prior } => { + // here we just ensure that we're currently idling with no votes recorded. + ensure!(votes.is_empty(), Error::::VotesExist); + voting.set_common(delegations, prior); + }, + } + let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance)); + // Extend the lock to `balance` (rather than setting it) since we don't know what other + // votes are in place. + T::Currency::extend_lock(CONVICTION_VOTING_ID, &who, balance, WithdrawReasons::TRANSFER); + Ok(votes) + })?; + Self::deposit_event(Event::::Delegated(who, target)); + Ok(votes) + } + + /// Attempt to end the current delegation. + /// + /// Return the number of votes of upstream. + fn try_undelegate(who: T::AccountId) -> Result { + let votes = VotingFor::::try_mutate(&who, |voting| -> Result { + let mut old = Voting::default(); + sp_std::mem::swap(&mut old, voting); + match old { + Voting::Delegating { balance, target, conviction, delegations, mut prior } => { + // remove any delegation votes to our current target. + let votes = + Self::reduce_upstream_delegation(&target, conviction.votes(balance)); + let now = frame_system::Pallet::::block_number(); + let lock_periods = conviction.lock_periods().into(); + prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); + voting.set_common(delegations, prior); + + Ok(votes) + }, + Voting::Direct { .. } => Err(Error::::NotDelegating.into()), + } + })?; + Self::deposit_event(Event::::Undelegated(who)); + Ok(votes) + } + + /// Rejig the lock on an account. It will never get more stringent (since that would indicate + /// a security hole) but may be reduced from what they are currently. + fn update_lock(who: &T::AccountId) { + let lock_needed = VotingFor::::mutate(who, |voting| { + voting.rejig(frame_system::Pallet::::block_number()); + voting.locked_balance() + }); + 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); + } + } +} diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs new file mode 100644 index 0000000000000..06c4ac666cfba --- /dev/null +++ b/frame/conviction-voting/src/tests.rs @@ -0,0 +1,302 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The crate's tests. + +use super::*; +use crate as pallet_democracy; +use codec::Encode; +use frame_support::{ + assert_noop, assert_ok, ord_parameter_types, parameter_types, + traits::{Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers}, + weights::Weight, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use pallet_balances::{BalanceLock, Error as BalancesError}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + Perbill, +}; + +mod cancellation; +mod decoders; +mod delegation; +mod external_proposing; +mod fast_tracking; +mod lock_voting; +mod preimage; +mod public_proposals; +mod scheduling; +mod voting; + +const AYE: Vote = Vote { aye: true, conviction: Conviction::None }; +const NAY: Vote = Vote { aye: false, conviction: Conviction::None }; +const BIG_AYE: Vote = Vote { aye: true, conviction: Conviction::Locked1x }; +const BIG_NAY: Vote = Vote { aye: false, conviction: Conviction::Locked1x }; + +const MAX_PROPOSALS: u32 = 100; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, + Democracy: pallet_democracy::{Pallet, Call, Storage, Config, Event}, + } +); + +// Test that a fitlered call can be dispatched. +pub struct BaseFilter; +impl Contains 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; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; +} +impl pallet_scheduler::Config for Test { + type Event = Event; + type Origin = Origin; + type PalletsOrigin = OriginCaller; + type Call = Call; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureRoot; + type MaxScheduledPerBlock = (); + type WeightInfo = (); + type OriginPrivilegeCmp = EqualPrivilegeOnly; +} +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 const LaunchPeriod: u64 = 2; + pub const VotingPeriod: u64 = 2; + pub const FastTrackVotingPeriod: u64 = 2; + pub const MinimumDeposit: u64 = 1; + pub const EnactmentPeriod: u64 = 2; + pub const VoteLockingPeriod: u64 = 3; + pub const CooloffPeriod: u64 = 2; + pub const MaxVotes: u32 = 100; + pub const MaxProposals: u32 = MAX_PROPOSALS; + pub static PreimageByteDeposit: u64 = 0; + pub static InstantAllowed: bool = false; +} +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 for OneToFive { + fn sorted_members() -> Vec { + vec![1, 2, 3, 4, 5] + } + #[cfg(feature = "runtime-benchmarks")] + fn add(_m: &u64) {} +} + +impl Config for Test { + type Proposal = Call; + type Event = Event; + type Currency = pallet_balances::Pallet; + type EnactmentPeriod = EnactmentPeriod; + type LaunchPeriod = LaunchPeriod; + type VotingPeriod = VotingPeriod; + type VoteLockingPeriod = VoteLockingPeriod; + type FastTrackVotingPeriod = FastTrackVotingPeriod; + type MinimumDeposit = MinimumDeposit; + type ExternalOrigin = EnsureSignedBy; + type ExternalMajorityOrigin = EnsureSignedBy; + type ExternalDefaultOrigin = EnsureSignedBy; + type FastTrackOrigin = EnsureSignedBy; + type CancellationOrigin = EnsureSignedBy; + type BlacklistOrigin = EnsureRoot; + type CancelProposalOrigin = EnsureRoot; + type VetoOrigin = EnsureSignedBy; + type CooloffPeriod = CooloffPeriod; + type PreimageByteDeposit = PreimageByteDeposit; + type Slash = (); + type InstantOrigin = EnsureSignedBy; + type InstantAllowed = InstantAllowed; + type Scheduler = Scheduler; + type MaxVotes = MaxVotes; + type OperationalPreimageOrigin = EnsureSignedBy; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); + type MaxProposals = MaxProposals; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + } + .assimilate_storage(&mut t) + .unwrap(); + pallet_democracy::GenesisConfig::::default() + .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`. +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)); +} + +#[test] +fn params_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Democracy::referendum_count(), 0); + assert_eq!(Balances::free_balance(42), 0); + assert_eq!(Balances::total_issuance(), 210); + }); +} + +fn set_balance_proposal(value: u64) -> Vec { + Call::Balances(pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 }) + .encode() +} + +#[test] +fn set_balance_proposal_is_correctly_filtered_out() { + for i in 0..10 { + let call = Call::decode(&mut &set_balance_proposal(i)[..]).unwrap(); + assert!(!::BaseCallFilter::contains(&call)); + } +} + +fn set_balance_proposal_hash(value: u64) -> H256 { + BlakeTwo256::hash(&set_balance_proposal(value)[..]) +} + +fn set_balance_proposal_hash_and_note(value: u64) -> H256 { + let p = set_balance_proposal(value); + let h = BlakeTwo256::hash(&p[..]); + match Democracy::note_preimage(Origin::signed(6), p) { + Ok(_) => (), + Err(x) if x == Error::::DuplicatePreimage.into() => (), + Err(x) => panic!("{:?}", x), + } + h +} + +fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { + Democracy::propose(Origin::signed(who), set_balance_proposal_hash(value), delay) +} + +fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchResult { + Democracy::propose(Origin::signed(who), set_balance_proposal_hash_and_note(value), delay) +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); + Scheduler::on_initialize(System::block_number()); + Democracy::begin_block(System::block_number()); +} + +fn fast_forward_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +fn begin_referendum() -> ReferendumIndex { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + fast_forward_to(2); + 0 +} + +fn aye(who: u64) -> AccountVote { + AccountVote::Standard { vote: AYE, balance: Balances::free_balance(&who) } +} + +fn nay(who: u64) -> AccountVote { + AccountVote::Standard { vote: NAY, balance: Balances::free_balance(&who) } +} + +fn big_aye(who: u64) -> AccountVote { + AccountVote::Standard { vote: BIG_AYE, balance: Balances::free_balance(&who) } +} + +fn big_nay(who: u64) -> AccountVote { + AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } +} + +fn tally(r: ReferendumIndex) -> Tally { + Democracy::referendum_status(r).unwrap().tally +} diff --git a/frame/conviction-voting/src/tests/cancellation.rs b/frame/conviction-voting/src/tests/cancellation.rs new file mode 100644 index 0000000000000..83822bf51829f --- /dev/null +++ b/frame/conviction-voting/src/tests/cancellation.rs @@ -0,0 +1,96 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for cancelation functionality. + +use super::*; + +#[test] +fn cancel_referendum_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::cancel_referendum(Origin::root(), r.into())); + assert_eq!(Democracy::lowest_unbaked(), 0); + + next_block(); + + next_block(); + + assert_eq!(Democracy::lowest_unbaked(), 1); + assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn cancel_queued_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + // start of 2 => next referendum scheduled. + fast_forward_to(2); + + assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); + + fast_forward_to(4); + + assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); + + assert_noop!(Democracy::cancel_queued(Origin::root(), 1), Error::::ProposalMissing); + assert_ok!(Democracy::cancel_queued(Origin::root(), 0)); + assert!(pallet_scheduler::Agenda::::get(6)[0].is_none()); + }); +} + +#[test] +fn emergency_cancel_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 2, + ); + assert!(Democracy::referendum_status(r).is_ok()); + + assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); + assert_ok!(Democracy::emergency_cancel(Origin::signed(4), r)); + assert!(Democracy::referendum_info(r).is_none()); + + // some time later... + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 2, + ); + assert!(Democracy::referendum_status(r).is_ok()); + assert_noop!( + Democracy::emergency_cancel(Origin::signed(4), r), + Error::::AlreadyCanceled, + ); + }); +} diff --git a/frame/conviction-voting/src/tests/decoders.rs b/frame/conviction-voting/src/tests/decoders.rs new file mode 100644 index 0000000000000..3c1729c4355c0 --- /dev/null +++ b/frame/conviction-voting/src/tests/decoders.rs @@ -0,0 +1,85 @@ +// 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. + +//! The for various partial storage decoders + +use super::*; +use frame_support::storage::{migration, unhashed}; + +#[test] +fn test_decode_compact_u32_at() { + new_test_ext().execute_with(|| { + let v = codec::Compact(u64::MAX); + migration::put_storage_value(b"test", b"", &[], v); + assert_eq!(decode_compact_u32_at(b"test"), None); + + for v in vec![0, 10, u32::MAX] { + let compact_v = codec::Compact(v); + unhashed::put(b"test", &compact_v); + assert_eq!(decode_compact_u32_at(b"test"), Some(v)); + } + + unhashed::kill(b"test"); + assert_eq!(decode_compact_u32_at(b"test"), None); + }) +} + +#[test] +fn len_of_deposit_of() { + new_test_ext().execute_with(|| { + for l in vec![0, 1, 200, 1000] { + let value: (Vec, u64) = ((0..l).map(|_| Default::default()).collect(), 3u64); + DepositOf::::insert(2, value); + assert_eq!(Democracy::len_of_deposit_of(2), Some(l)); + } + + DepositOf::::remove(2); + assert_eq!(Democracy::len_of_deposit_of(2), None); + }) +} + +#[test] +fn pre_image() { + new_test_ext().execute_with(|| { + let key = Default::default(); + let missing = PreimageStatus::Missing(0); + Preimages::::insert(key, missing); + assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); + assert_eq!(Democracy::check_pre_image_is_missing(key), Ok(())); + + Preimages::::remove(key); + assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); + assert_noop!(Democracy::check_pre_image_is_missing(key), Error::::NotImminent); + + for l in vec![0, 10, 100, 1000u32] { + let available = PreimageStatus::Available { + data: (0..l).map(|i| i as u8).collect(), + provider: 0, + deposit: 0, + since: 0, + expiry: None, + }; + + Preimages::::insert(key, available); + assert_eq!(Democracy::pre_image_data_len(key), Ok(l)); + assert_noop!( + Democracy::check_pre_image_is_missing(key), + Error::::DuplicatePreimage + ); + } + }) +} diff --git a/frame/conviction-voting/src/tests/delegation.rs b/frame/conviction-voting/src/tests/delegation.rs new file mode 100644 index 0000000000000..d3afa1c13f90b --- /dev/null +++ b/frame/conviction-voting/src/tests/delegation.rs @@ -0,0 +1,179 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for functionality concerning delegation. + +use super::*; + +#[test] +fn single_proposal_should_work_with_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + // Delegate first vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + + // Delegate a second vote. + assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); + assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 60 }); + + // Reduce first vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); + assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 50 }); + + // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. + assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); + assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 20 }); + + // Main voter cancels their vote + assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + + // First delegator delegates half funds with conviction; nothing changes yet. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked1x, 10)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + + // Main voter reinstates their vote + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 20 }); + }); +} + +#[test] +fn self_delegation_not_allowed() { + new_test_ext().execute_with(|| { + assert_noop!( + Democracy::delegate(Origin::signed(1), 1, Conviction::None, 10), + Error::::Nonsense, + ); + }); +} + +#[test] +fn cyclic_delegation_should_unwind() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + // Check behavior with cycle. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); + assert_ok!(Democracy::delegate(Origin::signed(1), 3, Conviction::None, 10)); + let r = 0; + assert_ok!(Democracy::undelegate(Origin::signed(3))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); + assert_ok!(Democracy::undelegate(Origin::signed(1))); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); + + // Delegated vote is counted. + assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 60 }); + }); +} + +#[test] +fn single_proposal_should_work_with_vote_and_delegation() { + // If transactor already voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, nay(2))); + assert_eq!(tally(r), Tally { ayes: 1, nays: 2, turnout: 30 }); + + // Delegate vote. + assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + // Delegated vote replaces the explicit vote. + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + }); +} + +#[test] +fn single_proposal_should_work_with_undelegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + // Delegate and undelegate vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::undelegate(Origin::signed(2))); + + fast_forward_to(2); + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + // Delegated vote is not counted. + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + }); +} + +#[test] +fn single_proposal_should_work_with_delegation_and_vote() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + let r = begin_referendum(); + // Delegate, undelegate and vote. + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + assert_ok!(Democracy::undelegate(Origin::signed(2))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + // Delegated vote is not counted. + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + }); +} + +#[test] +fn conviction_should_be_honored_in_delegation() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + let r = begin_referendum(); + // Delegate, undelegate and vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + // Delegated vote is huge. + assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 }); + }); +} + +#[test] +fn split_vote_delegation_should_be_ignored() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + let r = begin_referendum(); + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); + assert_ok!(Democracy::vote(Origin::signed(1), r, AccountVote::Split { aye: 10, nay: 0 })); + // Delegated vote is huge. + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + }); +} diff --git a/frame/conviction-voting/src/tests/external_proposing.rs b/frame/conviction-voting/src/tests/external_proposing.rs new file mode 100644 index 0000000000000..7442964584fa9 --- /dev/null +++ b/frame/conviction-voting/src/tests/external_proposing.rs @@ -0,0 +1,303 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for functionality concerning the "external" origin. + +use super::*; + +#[test] +fn veto_external_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert!(>::exists()); + + let h = set_balance_proposal_hash_and_note(2); + assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone())); + // cancelled. + assert!(!>::exists()); + // fails - same proposal can't be resubmitted. + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), + Error::::ProposalBlacklisted + ); + + fast_forward_to(1); + // fails as we're still in cooloff period. + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), + Error::::ProposalBlacklisted + ); + + fast_forward_to(2); + // works; as we're out of the cooloff period. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert!(>::exists()); + + // 3 can't veto the same thing twice. + assert_noop!( + Democracy::veto_external(Origin::signed(3), h.clone()), + Error::::AlreadyVetoed + ); + + // 4 vetoes. + assert_ok!(Democracy::veto_external(Origin::signed(4), h.clone())); + // cancelled again. + assert!(!>::exists()); + + fast_forward_to(3); + // same proposal fails as we're still in cooloff + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), + Error::::ProposalBlacklisted + ); + // different proposal works fine. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); + }); +} + +#[test] +fn external_blacklisting_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + + let hash = set_balance_proposal_hash(2); + assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); + + fast_forward_to(2); + assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); + + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash_and_note(2),), + Error::::ProposalBlacklisted, + ); + }); +} + +#[test] +fn external_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_noop!( + Democracy::external_propose(Origin::signed(1), set_balance_proposal_hash(2),), + BadOrigin, + ); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert_noop!( + Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(1),), + Error::::DuplicateProposal + ); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn external_majority_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_noop!( + Democracy::external_propose_majority(Origin::signed(1), set_balance_proposal_hash(2)), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SimpleMajority, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn external_default_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_noop!( + Democracy::external_propose_default(Origin::signed(3), set_balance_proposal_hash(2)), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_default( + Origin::signed(1), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SuperMajorityAgainst, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn external_and_public_interleaving_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(1), + )); + assert_ok!(propose_set_balance_and_note(6, 2, 2)); + + fast_forward_to(2); + + // both waiting: external goes first. + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash_and_note(1), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); + + fast_forward_to(4); + + // both waiting: public goes next. + assert_eq!( + Democracy::referendum_status(1), + Ok(ReferendumStatus { + end: 6, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // don't replenish public + + fast_forward_to(6); + + // it's external "turn" again, though since public is empty that doesn't really matter + assert_eq!( + Democracy::referendum_status(2), + Ok(ReferendumStatus { + end: 8, + proposal_hash: set_balance_proposal_hash_and_note(3), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(5), + )); + + fast_forward_to(8); + + // external goes again because there's no public waiting. + assert_eq!( + Democracy::referendum_status(3), + Ok(ReferendumStatus { + end: 10, + proposal_hash: set_balance_proposal_hash_and_note(5), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish both + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(7), + )); + assert_ok!(propose_set_balance_and_note(6, 4, 2)); + + fast_forward_to(10); + + // public goes now since external went last time. + assert_eq!( + Democracy::referendum_status(4), + Ok(ReferendumStatus { + end: 12, + proposal_hash: set_balance_proposal_hash_and_note(4), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish public again + assert_ok!(propose_set_balance_and_note(6, 6, 2)); + // cancel external + let h = set_balance_proposal_hash_and_note(7); + assert_ok!(Democracy::veto_external(Origin::signed(3), h)); + + fast_forward_to(12); + + // public goes again now since there's no external waiting. + assert_eq!( + Democracy::referendum_status(5), + Ok(ReferendumStatus { + end: 14, + proposal_hash: set_balance_proposal_hash_and_note(6), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} diff --git a/frame/conviction-voting/src/tests/fast_tracking.rs b/frame/conviction-voting/src/tests/fast_tracking.rs new file mode 100644 index 0000000000000..9b2f2760bde1c --- /dev/null +++ b/frame/conviction-voting/src/tests/fast_tracking.rs @@ -0,0 +1,98 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for fast-tracking functionality. + +use super::*; + +#[test] +fn fast_track_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!( + Democracy::fast_track(Origin::signed(5), h, 3, 2), + Error::::ProposalMissing + ); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); + assert_ok!(Democracy::fast_track(Origin::signed(5), h, 2, 0)); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 2, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SimpleMajority, + delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn instant_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!( + Democracy::fast_track(Origin::signed(5), h, 3, 2), + Error::::ProposalMissing + ); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 1, 0), BadOrigin); + assert_noop!( + Democracy::fast_track(Origin::signed(6), h, 1, 0), + Error::::InstantNotAllowed + ); + INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true); + assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0)); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 1, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SimpleMajority, + delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn fast_track_referendum_fails_when_no_simple_majority() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!( + Democracy::fast_track(Origin::signed(5), h, 3, 2), + Error::::NotSimpleMajority + ); + }); +} diff --git a/frame/conviction-voting/src/tests/lock_voting.rs b/frame/conviction-voting/src/tests/lock_voting.rs new file mode 100644 index 0000000000000..8b80b39c14aab --- /dev/null +++ b/frame/conviction-voting/src/tests/lock_voting.rs @@ -0,0 +1,377 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for functionality concerning locking and lock-voting. + +use super::*; +use std::convert::TryFrom; + +fn aye(x: u8, balance: u64) -> AccountVote { + AccountVote::Standard { + vote: Vote { aye: true, conviction: Conviction::try_from(x).unwrap() }, + balance, + } +} + +fn nay(x: u8, balance: u64) -> AccountVote { + AccountVote::Standard { + vote: Vote { aye: false, conviction: Conviction::try_from(x).unwrap() }, + balance, + } +} + +fn the_lock(amount: u64) -> BalanceLock { + BalanceLock { id: DEMOCRACY_ID, amount, reasons: pallet_balances::Reasons::Misc } +} + +#[test] +fn lock_voting_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); + assert_ok!(Democracy::vote(Origin::signed(4), r, aye(2, 40))); + assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); + assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); + + // All balances are currently locked. + for i in 1..=5 { + assert_eq!(Balances::locks(i), vec![the_lock(i * 10)]); + } + + fast_forward_to(2); + + // Referendum passed; 1 and 5 didn't get their way and can now reap and unlock. + assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); + assert_ok!(Democracy::unlock(Origin::signed(1), 1)); + // Anyone can reap and unlock anyone else's in this context. + assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 5, r)); + assert_ok!(Democracy::unlock(Origin::signed(2), 5)); + + // 2, 3, 4 got their way with the vote, so they cannot be reaped by others. + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 2, r), + Error::::NoPermission + ); + // However, they can be unvoted by the owner, though it will make no difference to the lock. + assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); + assert_ok!(Democracy::unlock(Origin::signed(2), 2)); + + assert_eq!(Balances::locks(1), vec![]); + assert_eq!(Balances::locks(2), vec![the_lock(20)]); + assert_eq!(Balances::locks(3), vec![the_lock(30)]); + assert_eq!(Balances::locks(4), vec![the_lock(40)]); + assert_eq!(Balances::locks(5), vec![]); + assert_eq!(Balances::free_balance(42), 2); + + fast_forward_to(7); + // No change yet... + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 4, r), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_eq!(Balances::locks(4), vec![the_lock(40)]); + fast_forward_to(8); + // 4 should now be able to reap and unlock + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 4, r)); + assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_eq!(Balances::locks(4), vec![]); + + fast_forward_to(13); + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 3, r), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_eq!(Balances::locks(3), vec![the_lock(30)]); + fast_forward_to(14); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 3, r)); + assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_eq!(Balances::locks(3), vec![]); + + // 2 doesn't need to reap_vote here because it was already done before. + fast_forward_to(25); + assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_eq!(Balances::locks(2), vec![the_lock(20)]); + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_eq!(Balances::locks(2), vec![]); + }); +} + +#[test] +fn no_locks_without_conviction_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(0, 10))); + + fast_forward_to(2); + + assert_eq!(Balances::free_balance(42), 2); + assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 1, r)); + assert_ok!(Democracy::unlock(Origin::signed(2), 1)); + assert_eq!(Balances::locks(1), vec![]); + }); +} + +#[test] +fn lock_voting_should_work_with_delegation() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); + assert_ok!(Democracy::delegate(Origin::signed(4), 2, Conviction::Locked2x, 40)); + assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); + + assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +fn setup_three_referenda() -> (u32, u32, u32) { + System::set_block_number(0); + let r1 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r1, aye(4, 10))); + + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r2, aye(3, 20))); + + let r3 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r3, aye(2, 50))); + + fast_forward_to(2); + + (r1, r2, r3) +} + +#[test] +fn prior_lockvotes_should_be_enforced() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until 2 + 8 * 3 = #26 + // r.1 locked 20 until 2 + 4 * 3 = #14 + // r.2 locked 50 until 2 + 2 * 3 = #8 + + fast_forward_to(7); + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 5, r.2), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(50)]); + fast_forward_to(8); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(13); + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 5, r.1), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(14); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(25); + assert_noop!( + Democracy::remove_other_vote(Origin::signed(1), 5, r.0), + Error::::NoPermission + ); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(26); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn single_consolidation_of_lockvotes_should_work_as_before() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until 2 + 8 * 3 = #26 + // r.1 locked 20 until 2 + 4 * 3 = #14 + // r.2 locked 50 until 2 + 2 * 3 = #8 + + fast_forward_to(7); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(50)]); + fast_forward_to(8); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + + fast_forward_to(13); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(14); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + + fast_forward_to(25); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn multi_consolidation_of_lockvotes_should_be_conservative() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until 2 + 8 * 3 = #26 + // r.1 locked 20 until 2 + 4 * 3 = #14 + // r.2 locked 50 until 2 + 2 * 3 = #8 + + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + + fast_forward_to(8); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 20); + + fast_forward_to(14); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn locks_should_persist_from_voting_to_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r, aye(4, 10))); + fast_forward_to(2); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); + // locked 10 until #26. + + assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked3x, 20)); + // locked 20. + assert!(Balances::locks(5)[0].amount == 20); + + assert_ok!(Democracy::undelegate(Origin::signed(5))); + // locked 20 until #14 + + fast_forward_to(13); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount == 20); + + fast_forward_to(14); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(25); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn locks_should_persist_from_delegation_to_voting() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked5x, 5)); + assert_ok!(Democracy::undelegate(Origin::signed(5))); + // locked 5 until 16 * 3 = #48 + + let r = setup_three_referenda(); + // r.0 locked 10 until 2 + 8 * 3 = #26 + // r.1 locked 20 until 2 + 4 * 3 = #14 + // r.2 locked 50 until 2 + 2 * 3 = #8 + + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + + fast_forward_to(8); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 20); + + fast_forward_to(14); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(26); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 5); + + fast_forward_to(48); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} diff --git a/frame/conviction-voting/src/tests/preimage.rs b/frame/conviction-voting/src/tests/preimage.rs new file mode 100644 index 0000000000000..6d478fcaa68c7 --- /dev/null +++ b/frame/conviction-voting/src/tests/preimage.rs @@ -0,0 +1,219 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The preimage tests. + +use super::*; + +#[test] +fn missing_preimage_should_fail() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn preimage_deposit_should_be_required_and_returned() { + new_test_ext_execute_with_cond(|operational| { + // fee of 100 is too much. + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); + assert_noop!( + if operational { + Democracy::note_preimage_operational(Origin::signed(6), vec![0; 500]) + } else { + Democracy::note_preimage(Origin::signed(6), vec![0; 500]) + }, + BalancesError::::InsufficientBalance, + ); + // fee of 1 is reasonable. + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + next_block(); + + assert_eq!(Balances::reserved_balance(6), 0); + assert_eq!(Balances::free_balance(6), 60); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn preimage_deposit_should_be_reapable_earlier_by_owner() { + new_test_ext_execute_with_cond(|operational| { + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(if operational { + Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) + } else { + Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) + }); + + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2), u32::MAX), + Error::::TooEarly + ); + next_block(); + assert_ok!(Democracy::reap_preimage( + Origin::signed(6), + set_balance_proposal_hash(2), + u32::MAX + )); + + assert_eq!(Balances::free_balance(6), 60); + assert_eq!(Balances::reserved_balance(6), 0); + }); +} + +#[test] +fn preimage_deposit_should_be_reapable() { + new_test_ext_execute_with_cond(|operational| { + assert_noop!( + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), + Error::::PreimageMissing + ); + + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(if operational { + Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) + } else { + Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) + }); + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + next_block(); + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), + Error::::TooEarly + ); + + next_block(); + assert_ok!(Democracy::reap_preimage( + Origin::signed(5), + set_balance_proposal_hash(2), + u32::MAX + )); + assert_eq!(Balances::reserved_balance(6), 0); + assert_eq!(Balances::free_balance(6), 48); + assert_eq!(Balances::free_balance(5), 62); + }); +} + +#[test] +fn noting_imminent_preimage_for_free_should_work() { + new_test_ext_execute_with_cond(|operational| { + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 1, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + assert_noop!( + if operational { + Democracy::note_imminent_preimage_operational( + Origin::signed(6), + set_balance_proposal(2), + ) + } else { + Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)) + }, + Error::::NotImminent + ); + + next_block(); + + // Now we're in the dispatch queue it's all good. + assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); + + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn reaping_imminent_preimage_should_fail() { + new_test_ext().execute_with(|| { + let h = set_balance_proposal_hash_and_note(2); + let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + next_block(); + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(6), h, u32::MAX), + Error::::Imminent + ); + }); +} + +#[test] +fn note_imminent_preimage_can_only_be_successful_once() { + new_test_ext().execute_with(|| { + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 1, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + next_block(); + + // First time works + assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); + + // Second time fails + assert_noop!( + Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)), + Error::::DuplicatePreimage + ); + + // Fails from any user + assert_noop!( + Democracy::note_imminent_preimage(Origin::signed(5), set_balance_proposal(2)), + Error::::DuplicatePreimage + ); + }); +} diff --git a/frame/conviction-voting/src/tests/public_proposals.rs b/frame/conviction-voting/src/tests/public_proposals.rs new file mode 100644 index 0000000000000..34713c3e15725 --- /dev/null +++ b/frame/conviction-voting/src/tests/public_proposals.rs @@ -0,0 +1,149 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for the public proposal queue. + +use super::*; + +#[test] +fn backing_for_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_ok!(propose_set_balance_and_note(1, 3, 3)); + assert_eq!(Democracy::backing_for(0), Some(2)); + assert_eq!(Democracy::backing_for(1), Some(4)); + assert_eq!(Democracy::backing_for(2), Some(3)); + }); +} + +#[test] +fn deposit_for_proposals_should_be_taken() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(1, 2, 5)); + assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_eq!(Balances::free_balance(1), 5); + assert_eq!(Balances::free_balance(2), 15); + assert_eq!(Balances::free_balance(5), 35); + }); +} + +#[test] +fn deposit_for_proposals_should_be_returned() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(1, 2, 5)); + assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + fast_forward_to(3); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 20); + assert_eq!(Balances::free_balance(5), 50); + }); +} + +#[test] +fn proposal_with_deposit_below_minimum_should_not_work() { + new_test_ext().execute_with(|| { + assert_noop!(propose_set_balance(1, 2, 0), Error::::ValueLow); + }); +} + +#[test] +fn poor_proposer_should_not_work() { + new_test_ext().execute_with(|| { + assert_noop!(propose_set_balance(1, 2, 11), BalancesError::::InsufficientBalance); + }); +} + +#[test] +fn poor_seconder_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(2, 2, 11)); + assert_noop!( + Democracy::second(Origin::signed(1), 0, u32::MAX), + BalancesError::::InsufficientBalance + ); + }); +} + +#[test] +fn invalid_seconds_upper_bound_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(1, 2, 5)); + assert_noop!(Democracy::second(Origin::signed(2), 0, 0), Error::::WrongUpperBound); + }); +} + +#[test] +fn cancel_proposal_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_noop!(Democracy::cancel_proposal(Origin::signed(1), 0), BadOrigin); + assert_ok!(Democracy::cancel_proposal(Origin::root(), 0)); + assert_eq!(Democracy::backing_for(0), None); + assert_eq!(Democracy::backing_for(1), Some(4)); + }); +} + +#[test] +fn blacklisting_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let hash = set_balance_proposal_hash(2); + + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + + assert_noop!(Democracy::blacklist(Origin::signed(1), hash.clone(), None), BadOrigin); + assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); + + assert_eq!(Democracy::backing_for(0), None); + assert_eq!(Democracy::backing_for(1), Some(4)); + + assert_noop!(propose_set_balance_and_note(1, 2, 2), Error::::ProposalBlacklisted); + + fast_forward_to(2); + + let hash = set_balance_proposal_hash(4); + assert_ok!(Democracy::referendum_status(0)); + assert_ok!(Democracy::blacklist(Origin::root(), hash, Some(0))); + assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); + }); +} + +#[test] +fn runners_up_should_come_after() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_ok!(propose_set_balance_and_note(1, 3, 3)); + fast_forward_to(2); + assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); + fast_forward_to(4); + assert_ok!(Democracy::vote(Origin::signed(1), 1, aye(1))); + fast_forward_to(6); + assert_ok!(Democracy::vote(Origin::signed(1), 2, aye(1))); + }); +} diff --git a/frame/conviction-voting/src/tests/scheduling.rs b/frame/conviction-voting/src/tests/scheduling.rs new file mode 100644 index 0000000000000..5c857a632b97b --- /dev/null +++ b/frame/conviction-voting/src/tests/scheduling.rs @@ -0,0 +1,156 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for functionality concerning normal starting, ending and enacting of referenda. + +use super::*; + +#[test] +fn simple_passing_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + assert_eq!(Democracy::lowest_unbaked(), 0); + next_block(); + next_block(); + assert_eq!(Democracy::lowest_unbaked(), 1); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn simple_failing_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); + assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 10 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn ooo_inject_referendums_should_work() { + new_test_ext().execute_with(|| { + let r1 = Democracy::inject_referendum( + 3, + set_balance_proposal_hash_and_note(3), + VoteThreshold::SuperMajorityApprove, + 0, + ); + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + + assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); + assert_eq!(tally(r2), Tally { ayes: 1, nays: 0, turnout: 10 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 2); + + assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); + assert_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 10 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 3); + }); +} + +#[test] +fn delayed_enactment_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 1, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); + assert_ok!(Democracy::vote(Origin::signed(4), r, aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, aye(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, aye(6))); + + assert_eq!(tally(r), Tally { ayes: 21, nays: 0, turnout: 210 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 0); + + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn lowest_unbaked_should_be_sensible() { + new_test_ext().execute_with(|| { + let r1 = Democracy::inject_referendum( + 3, + set_balance_proposal_hash_and_note(1), + VoteThreshold::SuperMajorityApprove, + 0, + ); + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + let r3 = Democracy::inject_referendum( + 10, + set_balance_proposal_hash_and_note(3), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); + assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); + // r3 is canceled + assert_ok!(Democracy::cancel_referendum(Origin::root(), r3.into())); + assert_eq!(Democracy::lowest_unbaked(), 0); + + next_block(); + + // r2 is approved + assert_eq!(Balances::free_balance(42), 2); + assert_eq!(Democracy::lowest_unbaked(), 0); + + next_block(); + + // r1 is approved + assert_eq!(Balances::free_balance(42), 1); + assert_eq!(Democracy::lowest_unbaked(), 3); + assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); + }); +} diff --git a/frame/conviction-voting/src/tests/voting.rs b/frame/conviction-voting/src/tests/voting.rs new file mode 100644 index 0000000000000..e035c2d46c1b6 --- /dev/null +++ b/frame/conviction-voting/src/tests/voting.rs @@ -0,0 +1,169 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The tests for normal voting functionality. + +use super::*; + +#[test] +fn overvoting_should_fail() { + new_test_ext().execute_with(|| { + let r = begin_referendum(); + assert_noop!( + Democracy::vote(Origin::signed(1), r, aye(2)), + Error::::InsufficientFunds + ); + }); +} + +#[test] +fn split_voting_should_work() { + new_test_ext().execute_with(|| { + let r = begin_referendum(); + let v = AccountVote::Split { aye: 40, nay: 20 }; + assert_noop!(Democracy::vote(Origin::signed(5), r, v), Error::::InsufficientFunds); + let v = AccountVote::Split { aye: 30, nay: 20 }; + assert_ok!(Democracy::vote(Origin::signed(5), r, v)); + + assert_eq!(tally(r), Tally { ayes: 3, nays: 2, turnout: 50 }); + }); +} + +#[test] +fn split_vote_cancellation_should_work() { + new_test_ext().execute_with(|| { + let r = begin_referendum(); + let v = AccountVote::Split { aye: 30, nay: 20 }; + assert_ok!(Democracy::vote(Origin::signed(5), r, v)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn single_proposal_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + let r = 0; + assert!(Democracy::referendum_info(r).is_none()); + + // start of 2 => next referendum scheduled. + fast_forward_to(2); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + assert_eq!(Democracy::referendum_count(), 1); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 1, nays: 0, turnout: 10 }, + }) + ); + + fast_forward_to(3); + + // referendum still running + assert_ok!(Democracy::referendum_status(0)); + + // referendum runs during 2 and 3, ends @ start of 4. + fast_forward_to(4); + + assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); + assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); + + // referendum passes and wait another two blocks for enactment. + fast_forward_to(6); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn controversial_voting_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + + assert_ok!(Democracy::vote(Origin::signed(1), r, big_aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, big_nay(2))); + assert_ok!(Democracy::vote(Origin::signed(3), r, big_nay(3))); + assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + + assert_eq!(tally(r), Tally { ayes: 110, nays: 100, turnout: 210 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn controversial_low_turnout_voting_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + + assert_eq!(tally(r), Tally { ayes: 60, nays: 50, turnout: 110 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn passing_low_turnout_voting_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_eq!(Balances::total_issuance(), 210); + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + assert_eq!(tally(r), Tally { ayes: 100, nays: 50, turnout: 150 }); + + next_block(); + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs new file mode 100644 index 0000000000000..19ee8baf609e6 --- /dev/null +++ b/frame/conviction-voting/src/types.rs @@ -0,0 +1,176 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Miscellaneous additional datatypes. + +use std::marker::PhantomData; + +use super::*; +use crate::{AccountVote, Conviction, Vote}; +use codec::{Decode, Encode}; +use frame_support::traits::VoteTally; +use scale_info::TypeInfo; +use sp_runtime::{traits::{Saturating, Zero}, RuntimeDebug}; + +/// Info regarding an ongoing referendum. +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Tally { + /// 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, +} + +impl> VoteTally + for Tally +{ + 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)) + } +} + +impl> Tally { + /// 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, + } + } + + /// Add an account's vote into the tally. + pub fn add(&mut self, vote: AccountVote) -> 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) -> 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) -> Option<()> { + 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), + } + Some(()) + } + + /// Decrement some amount of votes. + pub fn reduce(&mut self, approve: bool, delegations: Delegations) -> Option<()> { + 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), + } + Some(()) + } +} + +/// Amount of votes and capital placed in delegation for an account. +#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Delegations { + /// The number of votes (this is post-conviction). + pub votes: Balance, + /// The amount of raw capital, used for the turnout. + pub capital: Balance, +} + +impl Saturating for Delegations { + 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, +} diff --git a/frame/referenda/src/vote.rs b/frame/conviction-voting/src/vote.rs similarity index 100% rename from frame/referenda/src/vote.rs rename to frame/conviction-voting/src/vote.rs diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs new file mode 100644 index 0000000000000..638852d3c7e19 --- /dev/null +++ b/frame/conviction-voting/src/weights.rs @@ -0,0 +1,545 @@ +// 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_democracy +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-09-30, 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_democracy +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/democracy/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_democracy. +pub trait WeightInfo { + fn propose() -> Weight; + fn second(s: u32, ) -> Weight; + fn vote_new(r: u32, ) -> Weight; + fn vote_existing(r: u32, ) -> Weight; + fn emergency_cancel() -> Weight; + fn blacklist(p: u32, ) -> Weight; + fn external_propose(v: u32, ) -> Weight; + fn external_propose_majority() -> Weight; + fn external_propose_default() -> Weight; + fn fast_track() -> Weight; + fn veto_external(v: u32, ) -> Weight; + fn cancel_proposal(p: u32, ) -> Weight; + fn cancel_referendum() -> Weight; + fn cancel_queued(r: u32, ) -> Weight; + fn on_initialize_base(r: u32, ) -> Weight; + fn on_initialize_base_with_launch_period(r: u32, ) -> Weight; + fn delegate(r: u32, ) -> Weight; + fn undelegate(r: u32, ) -> Weight; + fn clear_public_proposals() -> Weight; + fn note_preimage(b: u32, ) -> Weight; + fn note_imminent_preimage(b: u32, ) -> Weight; + fn reap_preimage(b: u32, ) -> Weight; + fn unlock_remove(r: u32, ) -> Weight; + fn unlock_set(r: u32, ) -> Weight; + fn remove_vote(r: u32, ) -> Weight; + fn remove_other_vote(r: u32, ) -> Weight; +} + +/// Weights for pallet_democracy using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Democracy PublicPropCount (r:1 w:1) + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:0) + // Storage: Democracy DepositOf (r:0 w:1) + fn propose() -> Weight { + (67_388_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy DepositOf (r:1 w:1) + fn second(s: u32, ) -> Weight { + (41_157_000 as Weight) + // Standard Error: 0 + .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn vote_new(r: u32, ) -> Weight { + (46_406_000 as Weight) + // Standard Error: 1_000 + .saturating_add((170_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn vote_existing(r: u32, ) -> Weight { + (46_071_000 as Weight) + // Standard Error: 1_000 + .saturating_add((166_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy Cancellations (r:1 w:1) + fn emergency_cancel() -> Weight { + (27_699_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy Blacklist (r:0 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn blacklist(p: u32, ) -> Weight { + (82_703_000 as Weight) + // Standard Error: 4_000 + .saturating_add((500_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:0) + fn external_propose(v: u32, ) -> Weight { + (13_747_000 as Weight) + // Standard Error: 0 + .saturating_add((76_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:0 w:1) + fn external_propose_majority() -> Weight { + (3_070_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:0 w:1) + fn external_propose_default() -> Weight { + (3_080_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy ReferendumCount (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:0 w:1) + fn fast_track() -> Weight { + (29_129_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:1) + fn veto_external(v: u32, ) -> Weight { + (30_105_000 as Weight) + // Standard Error: 0 + .saturating_add((104_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn cancel_proposal(p: u32, ) -> Weight { + (55_228_000 as Weight) + // Standard Error: 1_000 + .saturating_add((457_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:0 w:1) + fn cancel_referendum() -> Weight { + (17_319_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn cancel_queued(r: u32, ) -> Weight { + (29_738_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_153_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy LowestUnbaked (r:1 w:0) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:1 w:0) + fn on_initialize_base(r: u32, ) -> Weight { + (2_165_000 as Weight) + // Standard Error: 3_000 + .saturating_add((5_577_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy LowestUnbaked (r:1 w:0) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy LastTabledWasExternal (r:1 w:0) + // Storage: Democracy NextExternal (r:1 w:0) + // Storage: Democracy PublicProps (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:1 w:0) + fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { + (9_396_000 as Weight) + // Standard Error: 4_000 + .saturating_add((5_604_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy VotingOf (r:3 w:3) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn delegate(r: u32, ) -> Weight { + (57_783_000 as Weight) + // Standard Error: 4_000 + .saturating_add((7_623_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy VotingOf (r:2 w:2) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + fn undelegate(r: u32, ) -> Weight { + (26_027_000 as Weight) + // Standard Error: 4_000 + .saturating_add((7_593_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy PublicProps (r:0 w:1) + fn clear_public_proposals() -> Weight { + (2_780_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + fn note_preimage(b: u32, ) -> Weight { + (46_416_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + fn note_imminent_preimage(b: u32, ) -> Weight { + (29_735_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + // Storage: System Account (r:1 w:0) + fn reap_preimage(b: u32, ) -> Weight { + (41_276_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn unlock_remove(r: u32, ) -> Weight { + (40_348_000 as Weight) + // Standard Error: 1_000 + .saturating_add((60_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn unlock_set(r: u32, ) -> Weight { + (37_475_000 as Weight) + // Standard Error: 1_000 + .saturating_add((151_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + fn remove_vote(r: u32, ) -> Weight { + (19_970_000 as Weight) + // Standard Error: 1_000 + .saturating_add((153_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + fn remove_other_vote(r: u32, ) -> Weight { + (20_094_000 as Weight) + // Standard Error: 1_000 + .saturating_add((157_000 as Weight).saturating_mul(r 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: Democracy PublicPropCount (r:1 w:1) + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:0) + // Storage: Democracy DepositOf (r:0 w:1) + fn propose() -> Weight { + (67_388_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy DepositOf (r:1 w:1) + fn second(s: u32, ) -> Weight { + (41_157_000 as Weight) + // Standard Error: 0 + .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn vote_new(r: u32, ) -> Weight { + (46_406_000 as Weight) + // Standard Error: 1_000 + .saturating_add((170_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn vote_existing(r: u32, ) -> Weight { + (46_071_000 as Weight) + // Standard Error: 1_000 + .saturating_add((166_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy Cancellations (r:1 w:1) + fn emergency_cancel() -> Weight { + (27_699_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy Blacklist (r:0 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn blacklist(p: u32, ) -> Weight { + (82_703_000 as Weight) + // Standard Error: 4_000 + .saturating_add((500_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:0) + fn external_propose(v: u32, ) -> Weight { + (13_747_000 as Weight) + // Standard Error: 0 + .saturating_add((76_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:0 w:1) + fn external_propose_majority() -> Weight { + (3_070_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:0 w:1) + fn external_propose_default() -> Weight { + (3_080_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy ReferendumCount (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:0 w:1) + fn fast_track() -> Weight { + (29_129_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy NextExternal (r:1 w:1) + // Storage: Democracy Blacklist (r:1 w:1) + fn veto_external(v: u32, ) -> Weight { + (30_105_000 as Weight) + // Standard Error: 0 + .saturating_add((104_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn cancel_proposal(p: u32, ) -> Weight { + (55_228_000 as Weight) + // Standard Error: 1_000 + .saturating_add((457_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:0 w:1) + fn cancel_referendum() -> Weight { + (17_319_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn cancel_queued(r: u32, ) -> Weight { + (29_738_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_153_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy LowestUnbaked (r:1 w:0) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:1 w:0) + fn on_initialize_base(r: u32, ) -> Weight { + (2_165_000 as Weight) + // Standard Error: 3_000 + .saturating_add((5_577_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy LowestUnbaked (r:1 w:0) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy LastTabledWasExternal (r:1 w:0) + // Storage: Democracy NextExternal (r:1 w:0) + // Storage: Democracy PublicProps (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:1 w:0) + fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { + (9_396_000 as Weight) + // Standard Error: 4_000 + .saturating_add((5_604_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy VotingOf (r:3 w:3) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn delegate(r: u32, ) -> Weight { + (57_783_000 as Weight) + // Standard Error: 4_000 + .saturating_add((7_623_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy VotingOf (r:2 w:2) + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + fn undelegate(r: u32, ) -> Weight { + (26_027_000 as Weight) + // Standard Error: 4_000 + .saturating_add((7_593_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + } + // Storage: Democracy PublicProps (r:0 w:1) + fn clear_public_proposals() -> Weight { + (2_780_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + fn note_preimage(b: u32, ) -> Weight { + (46_416_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + fn note_imminent_preimage(b: u32, ) -> Weight { + (29_735_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy Preimages (r:1 w:1) + // Storage: System Account (r:1 w:0) + fn reap_preimage(b: u32, ) -> Weight { + (41_276_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn unlock_remove(r: u32, ) -> Weight { + (40_348_000 as Weight) + // Standard Error: 1_000 + .saturating_add((60_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy VotingOf (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn unlock_set(r: u32, ) -> Weight { + (37_475_000 as Weight) + // Standard Error: 1_000 + .saturating_add((151_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + fn remove_vote(r: u32, ) -> Weight { + (19_970_000 as Weight) + // Standard Error: 1_000 + .saturating_add((153_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy VotingOf (r:1 w:1) + fn remove_other_vote(r: u32, ) -> Weight { + (20_094_000 as Weight) + // Standard Error: 1_000 + .saturating_add((157_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } +} diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 011fd2cb74bba..4b6bc9984ad04 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -30,6 +30,7 @@ use frame_support::{ ensure, BoundedVec, traits::{ schedule::{DispatchTime, Named as ScheduleNamed}, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, + Referenda, VoteTally, }, }; use scale_info::TypeInfo; @@ -39,17 +40,14 @@ use sp_runtime::{ }; use sp_std::{prelude::*, fmt::Debug}; -mod conviction; mod types; -mod vote; pub mod weights; -pub use conviction::Conviction; pub use pallet::*; pub use types::{ - Delegations, ReferendumInfo, ReferendumStatus, Tally, UnvoteScope, TrackInfo, TracksInfo, - Curve, DecidingStatus, Deposit, AtOrAfter, + ReferendumInfo, ReferendumStatus, TrackInfo, TracksInfo, Curve, DecidingStatus, Deposit, + AtOrAfter, BalanceOf, NegativeImbalanceOf, CallOf, OriginOf, VotesOf, TallyOf, ReferendumInfoOf, + ReferendumStatusOf, DecidingStatusOf, TrackInfoOf, TrackIdOf, InsertSorted, ReferendumIndex, }; -pub use vote::{AccountVote, Vote, Voting}; pub use weights::WeightInfo; #[cfg(test)] @@ -60,86 +58,10 @@ pub mod benchmarking; const ASSEMBLY_ID: LockIdentifier = *b"assembly"; -/// A referendum index. -pub type ReferendumIndex = u32; - -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId, ->>::NegativeImbalance; -type CallOf = ::Call; -type OriginOf = ::Origin; -type ReferendumInfoOf = ReferendumInfo< - TrackIdOf, - OriginOf, - ::BlockNumber, - ::Hash, - BalanceOf, - BalanceOf, - ::AccountId, ->; -type ReferendumStatusOf = ReferendumStatus< - TrackIdOf, - OriginOf, - ::BlockNumber, - ::Hash, - BalanceOf, - BalanceOf, - ::AccountId, ->; -type DecidingStatusOf = DecidingStatus< - ::BlockNumber, ->; -type VotingOf = Voting< - BalanceOf, - ::AccountId, - ::BlockNumber, ->; -type TrackInfoOf = TrackInfo< - BalanceOf, - ::BlockNumber, ->; -type TrackIdOf = < - ::Tracks as TracksInfo< - BalanceOf, - ::BlockNumber, - > ->::Id; - -pub trait InsertSorted { - /// 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 + Ord, - >(&mut self, t: T, f: F,) -> bool; -} -impl> InsertSorted for BoundedVec { - fn insert_sorted_by_key< - F: FnMut(&T) -> K, - K: PartialOrd + Ord, - >(&mut self, t: T, mut f: F,) -> bool { - let index = self.binary_search_by_key::(&f(&t), f).unwrap_or_else(|x| x); - if index >= S::get() as usize { - return false - } - self.force_insert(index, t); - true - } -} - #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{ - pallet_prelude::*, - traits::EnsureOrigin, - weights::Pays, - Parameter, - }; + use frame_support::{Parameter, pallet_prelude::*, traits::EnsureOrigin, weights::Pays}; use frame_system::pallet_prelude::*; use sp_runtime::DispatchResult; @@ -168,19 +90,16 @@ pub mod pallet { type KillOrigin: EnsureOrigin>; /// Handler for the unbalanced reduction when slashing a preimage deposit. type Slash: OnUnbalanced>; + /// The counting type for votes. Usually just balance. + type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member; + /// The tallying type. + type Tally: VoteTally + Default + Clone + Codec + Eq + Debug + TypeInfo; // Constants /// The minimum amount to be used as a deposit for a public referendum proposal. #[pallet::constant] type SubmissionDeposit: Get>; - /// 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; - /// Maximum size of the referendum queue for a single track. #[pallet::constant] type MaxQueued: Get; @@ -190,13 +109,6 @@ pub mod pallet { #[pallet::constant] type UndecidingTimeout: Get; - /// 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; - /// Quantization level for the referendum wakeup scheduler. A higher number will result in /// fewer storage reads/writes needed for smaller voters, but also result in delays to the /// automatic referendum status changes. Explicit servicing instructions are unaffected. @@ -221,7 +133,7 @@ pub mod pallet { _, Twox64Concat, TrackIdOf, - BoundedVec<(ReferendumIndex, BalanceOf), T::MaxQueued>, + BoundedVec<(ReferendumIndex, T::Votes), T::MaxQueued>, ValueQuery, >; @@ -246,19 +158,6 @@ pub mod pallet { ReferendumInfoOf, >; - /// All votes for a particular voter. We store the balance for the number of votes that we - /// have recorded. The second item is the total amount of delegations, that will be added. - /// - /// TWOX-NOTE: SAFE as `AccountId`s are crypto hashes anyway. - #[pallet::storage] - pub type VotingFor = StorageMap< - _, - Twox64Concat, - T::AccountId, - VotingOf, - 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. /// @@ -307,14 +206,14 @@ pub mod pallet { /// The hash of the proposal up for referendum. proposal_hash: T::Hash, /// The current tally of votes in this referendum. - tally: Tally>, + tally: T::Tally, }, /// A referendum has ended its confirmation phase and is ready for approval. Confirmed { /// Index of the referendum. index: ReferendumIndex, /// The final tally of votes in this referendum. - tally: Tally>, + tally: T::Tally, }, /// A referendum has been approved and its proposal has been scheduled. Approved { @@ -326,39 +225,33 @@ pub mod pallet { /// Index of the referendum. index: ReferendumIndex, /// The final tally of votes in this referendum. - tally: Tally>, + tally: T::Tally, }, /// A referendum has been timed out without being decided. TimedOut { /// Index of the referendum. index: ReferendumIndex, /// The final tally of votes in this referendum. - tally: Tally>, + tally: T::Tally, }, /// A referendum has been cancelled. Cancelled { /// Index of the referendum. index: ReferendumIndex, /// The final tally of votes in this referendum. - tally: Tally>, + tally: T::Tally, }, /// A referendum has been killed. Killed { /// Index of the referendum. index: ReferendumIndex, /// The final tally of votes in this referendum. - tally: Tally>, + tally: T::Tally, }, - /// 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 { - /// Value too low - ValueLow, /// Referendum is not ongoing. NotOngoing, /// Referendum's decision deposit is already paid. @@ -388,8 +281,6 @@ pub mod pallet { Nonsense, /// Invalid upper bound. WrongUpperBound, - /// Maximum number of votes reached. - MaxVotesReached, /// The track identifier given was invalid. BadTrack, /// There are already a full complement of referendums in progress for this track. @@ -405,6 +296,10 @@ pub mod pallet { } // TODO: bans + // TODO: cancel_referendum + // TODO: kill_referendum + // TODO: check events cover everything useful + // TODO: remove unused errors #[pallet::call] impl Pallet { @@ -438,7 +333,7 @@ pub mod pallet { submission_deposit: Self::take_deposit(who, T::SubmissionDeposit::get())?, decision_deposit: None, deciding: None, - tally: Tally::default(), + tally: Default::default(), ayes_in_queue: None, }; ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); @@ -475,9 +370,6 @@ pub mod pallet { Ok(()) } - // TODO: cancel_referendum - // TODO: kill_referendum - /// Advance a referendum onto its next logical state. This will happen eventually anyway, /// but you can nudge it #[pallet::weight(0)] @@ -503,6 +395,11 @@ pub mod pallet { } } +impl Referenda for Pallet { + type Index = ReferendumIndex; + type Votes = VotesOf; +} + impl Pallet { pub fn ensure_ongoing(index: ReferendumIndex) -> Result, DispatchError> @@ -601,8 +498,8 @@ impl Pallet { DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); } else { // Add to queue. - let item = (index, status.tally.ayes); - status.ayes_in_queue = Some(status.tally.ayes); + let item = (index, status.tally.ayes()); + status.ayes_in_queue = Some(status.tally.ayes()); TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)); } } @@ -610,7 +507,7 @@ impl Pallet { /// Grab the index and status for the referendum which is the highest priority of those for the /// given track which are ready for being decided. fn next_for_deciding( - track_queue: &mut BoundedVec<(u32, BalanceOf), T::MaxQueued>, + track_queue: &mut BoundedVec<(u32, VotesOf), T::MaxQueued>, ) -> Option<(ReferendumIndex, ReferendumStatusOf)> { loop { let (index, _) = track_queue.pop()?; @@ -715,7 +612,7 @@ impl Pallet { // Are we already queued for deciding? if let Some(_) = status.ayes_in_queue.as_ref() { // Does our position in the queue need updating? - let ayes = status.tally.ayes; + let ayes = status.tally.ayes(); let mut queue = TrackQueue::::get(status.track); let maybe_old_pos = queue.iter().position(|(x, _)| *x == index); let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x); @@ -746,12 +643,12 @@ impl Pallet { } } if let Some(deciding) = &mut status.deciding { - let is_passing = status.tally.is_passing( + let is_passing = Self::is_passing( + &status.tally, now, deciding.period, - T::Currency::total_issuance(), &track.min_turnout, - &track.min_approvals, + &track.min_approval, ); if is_passing { if deciding.confirming.map_or(false, |c| now >= c) { @@ -794,15 +691,15 @@ impl Pallet { fn decision_time( deciding: &DecidingStatusOf, - tally: &Tally>, + tally: &T::Tally, track: &TrackInfoOf, ) -> T::BlockNumber { // Set alarm to the point where the current voting would make it pass. - let approvals = Perbill::from_rational(tally.ayes, tally.ayes + tally.nays); - let turnout = Perbill::from_rational(tally.turnout, T::Currency::total_issuance()); - let until_approvals = track.min_approvals.delay(approvals) * deciding.period; + let approval = tally.approval(); + let turnout = tally.turnout(); + let until_approval = track.min_approval.delay(approval) * deciding.period; let until_turnout = track.min_turnout.delay(turnout) * deciding.period; - deciding.ending.min(until_turnout.max(until_approvals)) + deciding.ending.min(until_turnout.max(until_approval)) } /// Reserve a deposit and return the `Deposit` instance. @@ -826,386 +723,16 @@ impl Pallet { .unwrap_or_else(|x| x); Some(&tracks[index].1) } -} -/* - /// Vote in a referendum. If `vote.is_aye()`, the vote is to enact the proposal; - /// otherwise it is a vote to keep the status quo. - /// - /// The dispatch origin of this call must be _Signed_. - /// - /// - `ref_index`: The index of the referendum to vote for. - /// - `vote`: The vote configuration. - /// - /// Weight: `O(R)` where R is the number of referenda the voter has voted on. - #[pallet::weight( - T::WeightInfo::vote_new(T::MaxVotes::get()) - .max(T::WeightInfo::vote_existing(T::MaxVotes::get())) - )] - pub fn vote( - origin: OriginFor, - #[pallet::compact] ref_index: ReferendumIndex, - vote: AccountVote>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::try_vote(&who, ref_index, vote) - } - - /// Delegate the voting power (with some given conviction) of the sending account. - /// - /// The balance delegated is locked for as long as it's delegated, and thereafter for the - /// time appropriate for the conviction's lock period. - /// - /// The dispatch origin of this call must be _Signed_, and the signing account must either: - /// - be delegating already; or - /// - have no voting activity (if there is, then it will need to be removed/consolidated - /// through `reap_vote` or `unvote`). - /// - /// - `to`: The account whose voting the `target` account's voting power will follow. - /// - `conviction`: The conviction that will be attached to the delegated votes. When the - /// account is undelegated, the funds will be locked for the corresponding period. - /// - `balance`: The amount of the account's balance to be used in delegating. This must not - /// be more than the account's current balance. - /// - /// Emits `Delegated`. - /// - /// Weight: `O(R)` where R is the number of referenda the voter delegating to has - /// voted on. Weight is charged as if maximum votes. - // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure - // because a valid delegation cover decoding a direct voting with max votes. - #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] - pub fn delegate( - origin: OriginFor, - to: T::AccountId, - conviction: Conviction, - balance: BalanceOf, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let votes = Self::try_delegate(who, to, conviction, balance)?; - - Ok(Some(T::WeightInfo::delegate(votes)).into()) - } - - /// Undelegate the voting power of the sending account. - /// - /// Tokens may be unlocked following once an amount of time consistent with the lock period - /// of the conviction with which the delegation was issued. - /// - /// The dispatch origin of this call must be _Signed_ and the signing account must be - /// currently delegating. - /// - /// Emits `Undelegated`. - /// - /// Weight: `O(R)` where R is the number of referenda the voter delegating to has - /// voted on. Weight is charged as if maximum votes. - // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure - // because a valid delegation cover decoding a direct voting with max votes. - #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] - pub fn undelegate(origin: OriginFor) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let votes = Self::try_undelegate(who)?; - Ok(Some(T::WeightInfo::undelegate(votes)).into()) - } - - /// Unlock tokens that have an expired lock. - /// - /// The dispatch origin of this call must be _Signed_. - /// - /// - `target`: The account to remove the lock on. - /// - /// Weight: `O(R)` with R number of vote of target. - #[pallet::weight( - T::WeightInfo::unlock_set(T::MaxVotes::get()) - .max(T::WeightInfo::unlock_remove(T::MaxVotes::get())) - )] - pub fn unlock(origin: OriginFor, target: T::AccountId) -> DispatchResult { - ensure_signed(origin)?; - Self::update_lock(&target); - Ok(()) - } - - /// Remove a vote for a referendum. - /// - /// If: - /// - the referendum was cancelled, or - /// - the referendum is ongoing, or - /// - the referendum has ended such that - /// - the vote of the account was in opposition to the result; or - /// - there was no conviction to the account's vote; or - /// - the account made a split vote - /// ...then the vote is removed cleanly and a following call to `unlock` may result in more - /// funds being available. - /// - /// If, however, the referendum has ended and: - /// - it finished corresponding to the vote of the account, and - /// - the account made a standard vote with conviction, and - /// - the lock period of the conviction is not over - /// ...then the lock will be aggregated into the overall account's lock, which may involve - /// *overlocking* (where the two locks are combined into a single lock that is the maximum - /// of both the amount locked and the time is it locked for). - /// - /// The dispatch origin of this call must be _Signed_, and the signer must have a vote - /// registered for referendum `index`. - /// - /// - `index`: The index of referendum of the vote to be removed. - /// - /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. - /// Weight is calculated for the maximum number of vote. - #[pallet::weight(T::WeightInfo::remove_vote(T::MaxVotes::get()))] - pub fn remove_vote(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::try_remove_vote(&who, index, UnvoteScope::Any) - } - - /// Remove a vote for a referendum. - /// - /// If the `target` is equal to the signer, then this function is exactly equivalent to - /// `remove_vote`. If not equal to the signer, then the vote must have expired, - /// either because the referendum was cancelled, because the voter lost the referendum or - /// because the conviction period is over. - /// - /// The dispatch origin of this call must be _Signed_. - /// - /// - `target`: The account of the vote to be removed; this account must have voted for - /// referendum `index`. - /// - `index`: The index of referendum of the vote to be removed. - /// - /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. - /// Weight is calculated for the maximum number of vote. - #[pallet::weight(T::WeightInfo::remove_other_vote(T::MaxVotes::get()))] - pub fn remove_other_vote( - origin: OriginFor, - target: T::AccountId, - index: ReferendumIndex, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; - Self::try_remove_vote(&target, index, scope)?; - Ok(()) - } - -impl Pallet { - /// Actually enact a vote, if legit. - fn try_vote( - who: &T::AccountId, - ref_index: ReferendumIndex, - vote: AccountVote>, - ) -> DispatchResult { - let mut status = Self::referendum_status(ref_index)?; - ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); - VotingFor::::try_mutate(who, |voting| -> DispatchResult { - if let Voting::Direct { ref mut votes, delegations, .. } = voting { - match votes.binary_search_by_key(&ref_index, |i| i.0) { - Ok(i) => { - // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; - if let Some(approve) = votes[i].1.as_standard() { - status.tally.reduce(approve, *delegations); - } - votes[i].1 = vote; - }, - Err(i) => { - ensure!( - votes.len() as u32 <= T::MaxVotes::get(), - Error::::MaxVotesReached - ); - votes.insert(i, (ref_index, vote)); - }, - } - // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.add(vote).ok_or(ArithmeticError::Overflow)?; - if let Some(approve) = vote.as_standard() { - status.tally.increase(approve, *delegations); - } - Ok(()) - } else { - Err(Error::::AlreadyDelegating.into()) - } - })?; - // Extend the lock to `balance` (rather than setting it) since we don't know what other - // votes are in place. - T::Currency::extend_lock(ASSEMBLY_ID, who, vote.balance(), WithdrawReasons::TRANSFER); - ReferendumInfoFor::::insert(ref_index, ReferendumInfo::Ongoing(status)); - Ok(()) - } - - /// Remove the account's vote for the given referendum if possible. This is possible when: - /// - The referendum has not finished. - /// - The referendum has finished and the voter lost their direction. - /// - The referendum has finished and the voter's lock period is up. - /// - /// This will generally be combined with a call to `unlock`. - fn try_remove_vote( - who: &T::AccountId, - ref_index: ReferendumIndex, - scope: UnvoteScope, - ) -> DispatchResult { - let info = ReferendumInfoFor::::get(ref_index); - VotingFor::::try_mutate(who, |voting| -> DispatchResult { - if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting { - let i = votes - .binary_search_by_key(&ref_index, |i| i.0) - .map_err(|_| Error::::NotVoter)?; - match info { - Some(ReferendumInfo::Ongoing(mut status)) => { - ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); - // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; - if let Some(approve) = votes[i].1.as_standard() { - status.tally.reduce(approve, *delegations); - } - ReferendumInfoFor::::insert(ref_index, ReferendumInfo::Ongoing(status)); - }, - Some(ReferendumInfo::Finished { end, approved }) => { - if let Some((lock_periods, balance)) = votes[i].1.locked_if(approved) { - let unlock_at = end + T::VoteLockingPeriod::get() * lock_periods.into(); - let now = frame_system::Pallet::::block_number(); - if now < unlock_at { - ensure!( - matches!(scope, UnvoteScope::Any), - Error::::NoPermission - ); - prior.accumulate(unlock_at, balance) - } - } - }, - None => {}, // Referendum was cancelled. - } - votes.remove(i); - } - Ok(()) - })?; - Ok(()) - } - - /// Return the number of votes for `who` - fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { - VotingFor::::mutate(who, |voting| match voting { - Voting::Delegating { delegations, .. } => { - // We don't support second level delegating, so we don't need to do anything more. - *delegations = delegations.saturating_add(amount); - 1 - }, - Voting::Direct { votes, delegations, .. } => { - *delegations = delegations.saturating_add(amount); - for &(ref_index, account_vote) in votes.iter() { - if let AccountVote::Standard { vote, .. } = account_vote { - ReferendumInfoFor::::mutate(ref_index, |maybe_info| { - if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { - status.tally.increase(vote.aye, amount); - } - }); - } - } - votes.len() as u32 - }, - }) - } - - /// Return the number of votes for `who` - fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { - VotingFor::::mutate(who, |voting| match voting { - Voting::Delegating { delegations, .. } => { - // We don't support second level delegating, so we don't need to do anything more. - *delegations = delegations.saturating_sub(amount); - 1 - }, - Voting::Direct { votes, delegations, .. } => { - *delegations = delegations.saturating_sub(amount); - for &(ref_index, account_vote) in votes.iter() { - if let AccountVote::Standard { vote, .. } = account_vote { - ReferendumInfoFor::::mutate(ref_index, |maybe_info| { - if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { - status.tally.reduce(vote.aye, amount); - } - }); - } - } - votes.len() as u32 - }, - }) - } - - /// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`. - /// - /// Return the upstream number of votes. - fn try_delegate( - who: T::AccountId, - target: T::AccountId, - conviction: Conviction, - balance: BalanceOf, - ) -> Result { - ensure!(who != target, Error::::Nonsense); - ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); - let votes = VotingFor::::try_mutate(&who, |voting| -> Result { - let mut old = Voting::Delegating { - balance, - target: target.clone(), - conviction, - delegations: Default::default(), - prior: Default::default(), - }; - sp_std::mem::swap(&mut old, voting); - match old { - Voting::Delegating { balance, target, conviction, delegations, prior, .. } => { - // remove any delegation votes to our current target. - Self::reduce_upstream_delegation(&target, conviction.votes(balance)); - voting.set_common(delegations, prior); - }, - Voting::Direct { votes, delegations, prior } => { - // here we just ensure that we're currently idling with no votes recorded. - ensure!(votes.is_empty(), Error::::VotesExist); - voting.set_common(delegations, prior); - }, - } - let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance)); - // Extend the lock to `balance` (rather than setting it) since we don't know what other - // votes are in place. - T::Currency::extend_lock(ASSEMBLY_ID, &who, balance, WithdrawReasons::TRANSFER); - Ok(votes) - })?; - Self::deposit_event(Event::::Delegated(who, target)); - Ok(votes) - } - - /// Attempt to end the current delegation. - /// - /// Return the number of votes of upstream. - fn try_undelegate(who: T::AccountId) -> Result { - let votes = VotingFor::::try_mutate(&who, |voting| -> Result { - let mut old = Voting::default(); - sp_std::mem::swap(&mut old, voting); - match old { - Voting::Delegating { balance, target, conviction, delegations, mut prior } => { - // remove any delegation votes to our current target. - let votes = - Self::reduce_upstream_delegation(&target, conviction.votes(balance)); - let now = frame_system::Pallet::::block_number(); - let lock_periods = conviction.lock_periods().into(); - prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); - voting.set_common(delegations, prior); - - Ok(votes) - }, - Voting::Direct { .. } => Err(Error::::NotDelegating.into()), - } - })?; - Self::deposit_event(Event::::Undelegated(who)); - Ok(votes) - } - - /// Rejig the lock on an account. It will never get more stringent (since that would indicate - /// a security hole) but may be reduced from what they are currently. - fn update_lock(who: &T::AccountId) { - let lock_needed = VotingFor::::mutate(who, |voting| { - voting.rejig(frame_system::Pallet::::block_number()); - voting.locked_balance() - }); - if lock_needed.is_zero() { - T::Currency::remove_lock(ASSEMBLY_ID, who); - } else { - T::Currency::set_lock(ASSEMBLY_ID, who, lock_needed, WithdrawReasons::TRANSFER); - } + fn is_passing( + tally: &T::Tally, + now: T::BlockNumber, + period: T::BlockNumber, + turnout_needed: &Curve, + approval_needed: &Curve, + ) -> bool { + let x = Perbill::from_rational(now.min(period), period); + turnout_needed.passing(x, tally.turnout()) + && approval_needed.passing(x, tally.approval()) } } -*/ \ No newline at end of file diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 9a6d479abfa1e..a51a1035638ca 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -18,152 +18,78 @@ //! Miscellaneous additional datatypes. use super::*; -use crate::{AccountVote, Conviction, Vote}; use codec::{Decode, Encode}; use frame_support::Parameter; use scale_info::TypeInfo; -use sp_runtime::{traits::{Saturating, Zero}, RuntimeDebug}; - -/// Info regarding an ongoing referendum. -#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct Tally { - /// The number of aye votes, expressed in terms of post-conviction lock-vote. - pub ayes: Balance, - /// The number of nay votes, expressed in terms of post-conviction lock-vote. - pub nays: Balance, - /// The amount of funds currently expressing its opinion. Pre-conviction. - pub turnout: Balance, -} - -/// Amount of votes and capital placed in delegation for an account. -#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct Delegations { - /// The number of votes (this is post-conviction). - pub votes: Balance, - /// The amount of raw capital, used for the turnout. - pub capital: Balance, +use sp_runtime::RuntimeDebug; + +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +pub type NegativeImbalanceOf = <::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; +pub type CallOf = ::Call; +pub type OriginOf = ::Origin; +pub type VotesOf = ::Votes; +pub type TallyOf = ::Tally; +pub type ReferendumInfoOf = ReferendumInfo< + TrackIdOf, + OriginOf, + ::BlockNumber, + ::Hash, + BalanceOf, + VotesOf, + TallyOf, + ::AccountId, +>; +pub type ReferendumStatusOf = ReferendumStatus< + TrackIdOf, + OriginOf, + ::BlockNumber, + ::Hash, + BalanceOf, + VotesOf, + TallyOf, + ::AccountId, +>; +pub type DecidingStatusOf = DecidingStatus< + ::BlockNumber, +>; +pub type TrackInfoOf = TrackInfo< + BalanceOf, + ::BlockNumber, +>; +pub type TrackIdOf = < + ::Tracks as TracksInfo< + BalanceOf, + ::BlockNumber, + > +>::Id; + +/// A referendum index. +pub type ReferendumIndex = u32; + +pub trait InsertSorted { + /// 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 + Ord, + >(&mut self, t: T, f: F,) -> bool; } - -impl Saturating for Delegations { - 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) } - } -} - -impl Tally { - /// Create a new tally. - pub fn new(vote: Vote, balance: Balance) -> 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, - } - } - - /// Add an account's vote into the tally. - pub fn add(&mut self, vote: AccountVote) -> 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) -> 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)?; - }, +impl> InsertSorted for BoundedVec { + fn insert_sorted_by_key< + F: FnMut(&T) -> K, + K: PartialOrd + Ord, + >(&mut self, t: T, mut f: F,) -> bool { + let index = self.binary_search_by_key::(&f(&t), f).unwrap_or_else(|x| x); + if index >= S::get() as usize { + return false } - Some(()) - } - - /// Increment some amount of votes. - pub fn increase(&mut self, approve: bool, delegations: Delegations) -> Option<()> { - 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), - } - Some(()) - } - - /// Decrement some amount of votes. - pub fn reduce(&mut self, approve: bool, delegations: Delegations) -> Option<()> { - 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), - } - Some(()) - } - - pub fn turnout(&self, total: Balance) -> Perbill { - Perbill::from_rational(self.turnout, total) - } - - pub fn approval(&self) -> Perbill { - Perbill::from_rational(self.ayes, self.ayes.saturating_add(self.nays)) - } - - pub fn is_passing( - &self, - t: Moment, - period: Moment, - total: Balance, - turnout_needed: &Curve, - approval_needed: &Curve, - ) -> bool { - let x = Perbill::from_rational(t.min(period), period); - turnout_needed.passing(x, self.turnout(total)) && approval_needed.passing(x, self.approval()) + self.force_insert(index, t); + true } } @@ -204,7 +130,7 @@ pub struct TrackInfo { pub(crate) 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(crate) min_approvals: Curve, + pub(crate) min_approval: Curve, /// Minimum turnout as percentage of overall population that is needed for /// approval as a function of time into decision period. pub(crate) min_turnout: Curve, @@ -239,7 +165,7 @@ impl AtOrAfter { /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct ReferendumStatus { +pub struct ReferendumStatus { /// The track of this referendum. pub(crate) track: TrackId, /// The origin for this referendum. @@ -258,7 +184,7 @@ pub struct ReferendumStatus>, /// The current tally of votes in this referendum. - pub(crate) tally: Tally, + pub(crate) tally: Tally, /// The number of aye votes we are in the track queue for, if any. `None` if we're not /// yet in the deciding queue or are already deciding. If a vote results in fewer ayes /// in the `tally` than this, then the voter is required to pay to reorder the track queue. @@ -267,8 +193,8 @@ pub struct ReferendumStatus, } -impl - ReferendumStatus +impl + ReferendumStatus { pub fn begin_deciding(&mut self, now: Moment, decision_period: Moment) { self.ayes_in_queue = None; @@ -282,9 +208,9 @@ impl { +pub enum ReferendumInfo { /// Referendum has been submitted and is being voted on. - Ongoing(ReferendumStatus), + Ongoing(ReferendumStatus), /// Referendum finished at `end` with approval. Submission deposit is held. Approved(Deposit, Option>), /// Referendum finished at `end` with rejection. Submission deposit is held. @@ -293,8 +219,8 @@ pub enum ReferendumInfo, Option>), } -impl - ReferendumInfo +impl + ReferendumInfo { pub fn take_decision_deposit(&mut self) -> Option> { use ReferendumInfo::*; @@ -306,16 +232,6 @@ impl } } -/// 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, -} - - #[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] pub enum Curve { /// Linear curve starting at `(0, begin)`, ending at `(period, begin - delta)`. diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index bb990e25646db..d102617a3b4af 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -87,4 +87,6 @@ mod dispatch; pub use dispatch::{EnsureOrigin, OriginTrait, UnfilteredDispatchable}; mod voting; -pub use voting::{CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote}; +pub use voting::{ + CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, Referenda, +}; diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 62c6217ad59bc..c501608765b44 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -18,7 +18,9 @@ //! Traits and associated data structures concerned with voting, and moving between tokens and //! votes. -use sp_arithmetic::traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}; +use sp_arithmetic::{Perbill, traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}}; +use sp_runtime::traits::Member; +use crate::dispatch::Parameter; /// 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 +89,17 @@ impl + UniqueSaturatedFrom> CurrencyToVote B::unique_saturated_from(value) } } + +pub trait VoteTally { + fn ayes(&self) -> Votes; + fn turnout(&self) -> Perbill; + fn approval(&self) -> Perbill; +} + +pub trait Referenda { + type Index: Parameter + Member + Ord + PartialOrd + Copy; + type Votes: Parameter + Member + Ord + PartialOrd + Copy; + + // TODO +} + From cef2d82166e7a6dab9ade02f64abe42a9cb2e50b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 8 Nov 2021 17:05:26 +0100 Subject: [PATCH 06/66] Basic build --- frame/conviction-voting/src/lib.rs | 145 ++++--- frame/conviction-voting/src/tests.rs | 124 ++---- .../src/tests/cancellation.rs | 96 ----- frame/conviction-voting/src/tests/decoders.rs | 85 ---- .../conviction-voting/src/tests/delegation.rs | 179 --------- .../src/tests/external_proposing.rs | 303 -------------- .../src/tests/fast_tracking.rs | 98 ----- .../src/tests/lock_voting.rs | 377 ------------------ frame/conviction-voting/src/tests/preimage.rs | 219 ---------- .../src/tests/public_proposals.rs | 149 ------- .../conviction-voting/src/tests/scheduling.rs | 156 -------- frame/conviction-voting/src/tests/voting.rs | 169 -------- frame/conviction-voting/src/types.rs | 2 +- frame/conviction-voting/src/vote.rs | 12 +- frame/referenda/src/lib.rs | 42 +- frame/referenda/src/tests.rs | 4 +- frame/referenda/src/types.rs | 14 +- frame/support/src/traits.rs | 2 +- frame/support/src/traits/voting.rs | 36 +- 19 files changed, 200 insertions(+), 2012 deletions(-) delete mode 100644 frame/conviction-voting/src/tests/cancellation.rs delete mode 100644 frame/conviction-voting/src/tests/decoders.rs delete mode 100644 frame/conviction-voting/src/tests/delegation.rs delete mode 100644 frame/conviction-voting/src/tests/external_proposing.rs delete mode 100644 frame/conviction-voting/src/tests/fast_tracking.rs delete mode 100644 frame/conviction-voting/src/tests/lock_voting.rs delete mode 100644 frame/conviction-voting/src/tests/preimage.rs delete mode 100644 frame/conviction-voting/src/tests/public_proposals.rs delete mode 100644 frame/conviction-voting/src/tests/scheduling.rs delete mode 100644 frame/conviction-voting/src/tests/voting.rs diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index b417e3168e548..d7a5e1c83de9e 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -26,7 +26,13 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Encode, Codec}; -use frame_support::{BoundedVec, ensure, traits::{Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, Referenda, ReservableCurrency, WithdrawReasons, schedule::{DispatchTime, Named as ScheduleNamed}}}; +use frame_support::{ + BoundedVec, ensure, traits::{ + Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, + WithdrawReasons, PollStatus, Referenda, + schedule::{DispatchTime, Named as ScheduleNamed}, + } +}; use scale_info::TypeInfo; use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, Perbill, traits::{Dispatchable, Saturating, One, AtLeast32BitUnsigned}}; use sp_std::{prelude::*, fmt::Debug}; @@ -58,8 +64,10 @@ type VotingOf = Voting< BalanceOf, ::AccountId, ::BlockNumber, + ReferendumIndexOf, >; -type ReferendumIndexOf = <::Referenda as Referenda>::Index; +type TallyOf = Tally, ::MaxTurnout>; +type ReferendumIndexOf = <::Referenda as Referenda>>::Index; #[frame_support::pallet] pub mod pallet { @@ -88,7 +96,16 @@ pub mod pallet { + LockableCurrency; /// The implementation of the logic which conducts referenda. - type Referenda: Referenda>; + type Referenda: Referenda< + TallyOf, + Votes = BalanceOf, + 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>; /// The maximum number of concurrent votes an account may have. /// @@ -338,44 +355,43 @@ impl Pallet { ref_index: ReferendumIndexOf, vote: AccountVote>, ) -> DispatchResult { - let mut status = Self::referendum_status(ref_index)?; ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); - VotingFor::::try_mutate(who, |voting| -> DispatchResult { - if let Voting::Direct { ref mut votes, delegations, .. } = voting { - match votes.binary_search_by_key(&ref_index, |i| i.0) { - Ok(i) => { - // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; - if let Some(approve) = votes[i].1.as_standard() { - status.tally.reduce(approve, *delegations); - } - votes[i].1 = vote; - }, - Err(i) => { - ensure!( - votes.len() as u32 <= T::MaxVotes::get(), - Error::::MaxVotesReached - ); - votes.insert(i, (ref_index, vote)); - }, - } - // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.add(vote).ok_or(ArithmeticError::Overflow)?; - if let Some(approve) = vote.as_standard() { - status.tally.increase(approve, *delegations); + T::Referenda::access_poll(ref_index, |poll_status| { + let tally = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; + VotingFor::::try_mutate(who, |voting| { + if let Voting::Direct { ref mut votes, delegations, .. } = voting { + match votes.binary_search_by_key(&ref_index, |i| i.0) { + Ok(i) => { + // Shouldn't be possible to fail, but we handle it gracefully. + 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) => { + ensure!( + votes.len() as u32 <= T::MaxVotes::get(), + Error::::MaxVotesReached + ); + votes.insert(i, (ref_index, vote)); + }, + } + // 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::::AlreadyDelegating.into()) } + // Extend the lock to `balance` (rather than setting it) since we don't know what other + // votes are in place. + T::Currency::extend_lock(CONVICTION_VOTING_ID, who, vote.balance(), WithdrawReasons::TRANSFER); Ok(()) - } else { - Err(Error::::AlreadyDelegating.into()) - } - })?; - // Extend the lock to `balance` (rather than setting it) since we don't know what other - // votes are in place. - T::Currency::extend_lock(CONVICTION_VOTING_ID, who, vote.balance(), WithdrawReasons::TRANSFER); - ReferendumInfoFor::::insert(ref_index, ReferendumInfo::Ongoing(status)); - Ok(()) + }) + }) } - /* /// Remove the account's vote for the given referendum if possible. This is possible when: /// - The referendum has not finished. @@ -388,24 +404,25 @@ impl Pallet { ref_index: ReferendumIndexOf, scope: UnvoteScope, ) -> DispatchResult { - let info = ReferendumInfoFor::::get(ref_index); - VotingFor::::try_mutate(who, |voting| -> DispatchResult { + VotingFor::::try_mutate(who, |voting| { if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting { let i = votes .binary_search_by_key(&ref_index, |i| i.0) .map_err(|_| Error::::NotVoter)?; - match info { - Some(ReferendumInfo::Ongoing(mut status)) => { + let v = votes.remove(i); + + T::Referenda::access_poll(ref_index, |poll_status| match poll_status { + PollStatus::Ongoing(tally) => { ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; - if let Some(approve) = votes[i].1.as_standard() { - status.tally.reduce(approve, *delegations); + tally.remove(v.1).ok_or(ArithmeticError::Underflow)?; + if let Some(approve) = v.1.as_standard() { + tally.reduce(approve, *delegations); } - ReferendumInfoFor::::insert(ref_index, ReferendumInfo::Ongoing(status)); + Ok(()) }, - Some(ReferendumInfo::Finished { end, approved }) => { - if let Some((lock_periods, balance)) = votes[i].1.locked_if(approved) { + PollStatus::Done(end, approved) => { + if let Some((lock_periods, balance)) = v.1.locked_if(approved) { let unlock_at = end + T::VoteLockingPeriod::get() * lock_periods.into(); let now = frame_system::Pallet::::block_number(); if now < unlock_at { @@ -416,18 +433,20 @@ impl Pallet { prior.accumulate(unlock_at, balance) } } + Ok(()) }, - None => {}, // Referendum was cancelled. - } - votes.remove(i); + PollStatus::None => Ok(()), // Referendum was cancelled. + }) + } else { + Ok(()) } - Ok(()) - })?; - Ok(()) + }) } /// Return the number of votes for `who` fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { + todo!() + /* VotingFor::::mutate(who, |voting| match voting { Voting::Delegating { delegations, .. } => { // We don't support second level delegating, so we don't need to do anything more. @@ -447,11 +466,13 @@ impl Pallet { } votes.len() as u32 }, - }) + })*/ } /// Return the number of votes for `who` fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { + todo!() + /* VotingFor::::mutate(who, |voting| match voting { Voting::Delegating { delegations, .. } => { // We don't support second level delegating, so we don't need to do anything more. @@ -471,9 +492,9 @@ impl Pallet { } votes.len() as u32 }, - }) + })*/ } -*/ + /// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`. /// /// Return the upstream number of votes. @@ -483,6 +504,8 @@ impl Pallet { conviction: Conviction, balance: BalanceOf, ) -> Result { + todo!() + /* ensure!(who != target, Error::::Nonsense); ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); let votes = VotingFor::::try_mutate(&who, |voting| -> Result { @@ -513,13 +536,15 @@ impl Pallet { Ok(votes) })?; Self::deposit_event(Event::::Delegated(who, target)); - Ok(votes) + Ok(votes)*/ } /// Attempt to end the current delegation. /// /// Return the number of votes of upstream. fn try_undelegate(who: T::AccountId) -> Result { + todo!() + /* let votes = VotingFor::::try_mutate(&who, |voting| -> Result { let mut old = Voting::default(); sp_std::mem::swap(&mut old, voting); @@ -539,12 +564,14 @@ impl Pallet { } })?; Self::deposit_event(Event::::Undelegated(who)); - Ok(votes) + Ok(votes)*/ } /// Rejig the lock on an account. It will never get more stringent (since that would indicate /// a security hole) but may be reduced from what they are currently. fn update_lock(who: &T::AccountId) { + todo!() + /* let lock_needed = VotingFor::::mutate(who, |voting| { voting.rejig(frame_system::Pallet::::block_number()); voting.locked_balance() @@ -553,6 +580,6 @@ impl Pallet { T::Currency::remove_lock(CONVICTION_VOTING_ID, who); } else { T::Currency::set_lock(CONVICTION_VOTING_ID, who, lock_needed, WithdrawReasons::TRANSFER); - } + }*/ } } diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 06c4ac666cfba..d1ef0edbf9be1 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -18,33 +18,20 @@ //! The crate's tests. use super::*; -use crate as pallet_democracy; -use codec::Encode; +use crate as pallet_conviction_voting; use frame_support::{ - assert_noop, assert_ok, ord_parameter_types, parameter_types, + ord_parameter_types, parameter_types, traits::{Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers}, weights::Weight, }; -use frame_system::{EnsureRoot, EnsureSignedBy}; -use pallet_balances::{BalanceLock, Error as BalancesError}; +use frame_system::EnsureRoot; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, IdentityLookup}, Perbill, }; -mod cancellation; -mod decoders; -mod delegation; -mod external_proposing; -mod fast_tracking; -mod lock_voting; -mod preimage; -mod public_proposals; -mod scheduling; -mod voting; - const AYE: Vote = Vote { aye: true, conviction: Conviction::None }; const NAY: Vote = Vote { aye: false, conviction: Conviction::None }; const BIG_AYE: Vote = Vote { aye: true, conviction: Conviction::Locked1x }; @@ -64,7 +51,7 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, - Democracy: pallet_democracy::{Pallet, Call, Storage, Config, Event}, + Voting: pallet_conviction_voting::{Pallet, Call, Storage, Event}, } ); @@ -165,35 +152,38 @@ impl SortedMembers for OneToFive { fn add(_m: &u64) {} } +pub struct TotalIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); +impl, A> Get for TotalIssuanceOf { + fn get() -> C::Balance { + C::total_issuance() + } +} + +pub struct TestReferenda; +impl Referenda> for TestReferenda { + type Index = u8; + type Votes = u64; + type Moment = u64; + fn is_active(_index: u8) -> bool { false } + fn access_poll( + _index: Self::Index, + _f: impl FnOnce(PollStatus<&mut TallyOf, u64>) -> Result, + ) -> Result { + Err(DispatchError::Other("Unimplemented")) + } + fn tally(_index: Self::Index) -> Option> { + None + } +} + impl Config for Test { - type Proposal = Call; type Event = Event; type Currency = pallet_balances::Pallet; - type EnactmentPeriod = EnactmentPeriod; - type LaunchPeriod = LaunchPeriod; - type VotingPeriod = VotingPeriod; type VoteLockingPeriod = VoteLockingPeriod; - type FastTrackVotingPeriod = FastTrackVotingPeriod; - type MinimumDeposit = MinimumDeposit; - type ExternalOrigin = EnsureSignedBy; - type ExternalMajorityOrigin = EnsureSignedBy; - type ExternalDefaultOrigin = EnsureSignedBy; - type FastTrackOrigin = EnsureSignedBy; - type CancellationOrigin = EnsureSignedBy; - type BlacklistOrigin = EnsureRoot; - type CancelProposalOrigin = EnsureRoot; - type VetoOrigin = EnsureSignedBy; - type CooloffPeriod = CooloffPeriod; - type PreimageByteDeposit = PreimageByteDeposit; - type Slash = (); - type InstantOrigin = EnsureSignedBy; - type InstantAllowed = InstantAllowed; - type Scheduler = Scheduler; type MaxVotes = MaxVotes; - type OperationalPreimageOrigin = EnsureSignedBy; - type PalletsOrigin = OriginCaller; type WeightInfo = (); - type MaxProposals = MaxProposals; + type MaxTurnout = TotalIssuanceOf; + type Referenda = TestReferenda; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -203,9 +193,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { } .assimilate_storage(&mut t) .unwrap(); - pallet_democracy::GenesisConfig::::default() - .assimilate_storage(&mut t) - .unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext @@ -220,52 +207,14 @@ pub fn new_test_ext_execute_with_cond(execute: impl FnOnce(bool) -> () + Clone) #[test] fn params_should_work() { new_test_ext().execute_with(|| { - assert_eq!(Democracy::referendum_count(), 0); assert_eq!(Balances::free_balance(42), 0); assert_eq!(Balances::total_issuance(), 210); }); } -fn set_balance_proposal(value: u64) -> Vec { - Call::Balances(pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 }) - .encode() -} - -#[test] -fn set_balance_proposal_is_correctly_filtered_out() { - for i in 0..10 { - let call = Call::decode(&mut &set_balance_proposal(i)[..]).unwrap(); - assert!(!::BaseCallFilter::contains(&call)); - } -} - -fn set_balance_proposal_hash(value: u64) -> H256 { - BlakeTwo256::hash(&set_balance_proposal(value)[..]) -} - -fn set_balance_proposal_hash_and_note(value: u64) -> H256 { - let p = set_balance_proposal(value); - let h = BlakeTwo256::hash(&p[..]); - match Democracy::note_preimage(Origin::signed(6), p) { - Ok(_) => (), - Err(x) if x == Error::::DuplicatePreimage.into() => (), - Err(x) => panic!("{:?}", x), - } - h -} - -fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { - Democracy::propose(Origin::signed(who), set_balance_proposal_hash(value), delay) -} - -fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchResult { - Democracy::propose(Origin::signed(who), set_balance_proposal_hash_and_note(value), delay) -} - fn next_block() { System::set_block_number(System::block_number() + 1); Scheduler::on_initialize(System::block_number()); - Democracy::begin_block(System::block_number()); } fn fast_forward_to(n: u64) { @@ -274,13 +223,6 @@ fn fast_forward_to(n: u64) { } } -fn begin_referendum() -> ReferendumIndex { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - fast_forward_to(2); - 0 -} - fn aye(who: u64) -> AccountVote { AccountVote::Standard { vote: AYE, balance: Balances::free_balance(&who) } } @@ -297,6 +239,6 @@ fn big_nay(who: u64) -> AccountVote { AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } } -fn tally(r: ReferendumIndex) -> Tally { - Democracy::referendum_status(r).unwrap().tally +fn tally(r: u32) -> TallyOf { + todo!() } diff --git a/frame/conviction-voting/src/tests/cancellation.rs b/frame/conviction-voting/src/tests/cancellation.rs deleted file mode 100644 index 83822bf51829f..0000000000000 --- a/frame/conviction-voting/src/tests/cancellation.rs +++ /dev/null @@ -1,96 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for cancelation functionality. - -use super::*; - -#[test] -fn cancel_referendum_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::cancel_referendum(Origin::root(), r.into())); - assert_eq!(Democracy::lowest_unbaked(), 0); - - next_block(); - - next_block(); - - assert_eq!(Democracy::lowest_unbaked(), 1); - assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn cancel_queued_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - - assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); - - fast_forward_to(4); - - assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); - - assert_noop!(Democracy::cancel_queued(Origin::root(), 1), Error::::ProposalMissing); - assert_ok!(Democracy::cancel_queued(Origin::root(), 0)); - assert!(pallet_scheduler::Agenda::::get(6)[0].is_none()); - }); -} - -#[test] -fn emergency_cancel_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 2, - ); - assert!(Democracy::referendum_status(r).is_ok()); - - assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); - assert_ok!(Democracy::emergency_cancel(Origin::signed(4), r)); - assert!(Democracy::referendum_info(r).is_none()); - - // some time later... - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 2, - ); - assert!(Democracy::referendum_status(r).is_ok()); - assert_noop!( - Democracy::emergency_cancel(Origin::signed(4), r), - Error::::AlreadyCanceled, - ); - }); -} diff --git a/frame/conviction-voting/src/tests/decoders.rs b/frame/conviction-voting/src/tests/decoders.rs deleted file mode 100644 index 3c1729c4355c0..0000000000000 --- a/frame/conviction-voting/src/tests/decoders.rs +++ /dev/null @@ -1,85 +0,0 @@ -// 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. - -//! The for various partial storage decoders - -use super::*; -use frame_support::storage::{migration, unhashed}; - -#[test] -fn test_decode_compact_u32_at() { - new_test_ext().execute_with(|| { - let v = codec::Compact(u64::MAX); - migration::put_storage_value(b"test", b"", &[], v); - assert_eq!(decode_compact_u32_at(b"test"), None); - - for v in vec![0, 10, u32::MAX] { - let compact_v = codec::Compact(v); - unhashed::put(b"test", &compact_v); - assert_eq!(decode_compact_u32_at(b"test"), Some(v)); - } - - unhashed::kill(b"test"); - assert_eq!(decode_compact_u32_at(b"test"), None); - }) -} - -#[test] -fn len_of_deposit_of() { - new_test_ext().execute_with(|| { - for l in vec![0, 1, 200, 1000] { - let value: (Vec, u64) = ((0..l).map(|_| Default::default()).collect(), 3u64); - DepositOf::::insert(2, value); - assert_eq!(Democracy::len_of_deposit_of(2), Some(l)); - } - - DepositOf::::remove(2); - assert_eq!(Democracy::len_of_deposit_of(2), None); - }) -} - -#[test] -fn pre_image() { - new_test_ext().execute_with(|| { - let key = Default::default(); - let missing = PreimageStatus::Missing(0); - Preimages::::insert(key, missing); - assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); - assert_eq!(Democracy::check_pre_image_is_missing(key), Ok(())); - - Preimages::::remove(key); - assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); - assert_noop!(Democracy::check_pre_image_is_missing(key), Error::::NotImminent); - - for l in vec![0, 10, 100, 1000u32] { - let available = PreimageStatus::Available { - data: (0..l).map(|i| i as u8).collect(), - provider: 0, - deposit: 0, - since: 0, - expiry: None, - }; - - Preimages::::insert(key, available); - assert_eq!(Democracy::pre_image_data_len(key), Ok(l)); - assert_noop!( - Democracy::check_pre_image_is_missing(key), - Error::::DuplicatePreimage - ); - } - }) -} diff --git a/frame/conviction-voting/src/tests/delegation.rs b/frame/conviction-voting/src/tests/delegation.rs deleted file mode 100644 index d3afa1c13f90b..0000000000000 --- a/frame/conviction-voting/src/tests/delegation.rs +++ /dev/null @@ -1,179 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for functionality concerning delegation. - -use super::*; - -#[test] -fn single_proposal_should_work_with_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - - // Delegate first vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); - - // Delegate a second vote. - assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); - assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 60 }); - - // Reduce first vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); - assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 50 }); - - // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. - assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); - assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 20 }); - - // Main voter cancels their vote - assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); - assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); - - // First delegator delegates half funds with conviction; nothing changes yet. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked1x, 10)); - assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); - - // Main voter reinstates their vote - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 20 }); - }); -} - -#[test] -fn self_delegation_not_allowed() { - new_test_ext().execute_with(|| { - assert_noop!( - Democracy::delegate(Origin::signed(1), 1, Conviction::None, 10), - Error::::Nonsense, - ); - }); -} - -#[test] -fn cyclic_delegation_should_unwind() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - - // Check behavior with cycle. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); - assert_ok!(Democracy::delegate(Origin::signed(1), 3, Conviction::None, 10)); - let r = 0; - assert_ok!(Democracy::undelegate(Origin::signed(3))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); - assert_ok!(Democracy::undelegate(Origin::signed(1))); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); - - // Delegated vote is counted. - assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 60 }); - }); -} - -#[test] -fn single_proposal_should_work_with_vote_and_delegation() { - // If transactor already voted, delegated vote is overwritten. - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, nay(2))); - assert_eq!(tally(r), Tally { ayes: 1, nays: 2, turnout: 30 }); - - // Delegate vote. - assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - // Delegated vote replaces the explicit vote. - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); - }); -} - -#[test] -fn single_proposal_should_work_with_undelegation() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - // Delegate and undelegate vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_ok!(Democracy::undelegate(Origin::signed(2))); - - fast_forward_to(2); - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - // Delegated vote is not counted. - assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); - }); -} - -#[test] -fn single_proposal_should_work_with_delegation_and_vote() { - // If transactor voted, delegated vote is overwritten. - new_test_ext().execute_with(|| { - let r = begin_referendum(); - // Delegate, undelegate and vote. - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); - assert_ok!(Democracy::undelegate(Origin::signed(2))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); - // Delegated vote is not counted. - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); - }); -} - -#[test] -fn conviction_should_be_honored_in_delegation() { - // If transactor voted, delegated vote is overwritten. - new_test_ext().execute_with(|| { - let r = begin_referendum(); - // Delegate, undelegate and vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - // Delegated vote is huge. - assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 }); - }); -} - -#[test] -fn split_vote_delegation_should_be_ignored() { - // If transactor voted, delegated vote is overwritten. - new_test_ext().execute_with(|| { - let r = begin_referendum(); - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); - assert_ok!(Democracy::vote(Origin::signed(1), r, AccountVote::Split { aye: 10, nay: 0 })); - // Delegated vote is huge. - assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); - }); -} diff --git a/frame/conviction-voting/src/tests/external_proposing.rs b/frame/conviction-voting/src/tests/external_proposing.rs deleted file mode 100644 index 7442964584fa9..0000000000000 --- a/frame/conviction-voting/src/tests/external_proposing.rs +++ /dev/null @@ -1,303 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for functionality concerning the "external" origin. - -use super::*; - -#[test] -fn veto_external_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert!(>::exists()); - - let h = set_balance_proposal_hash_and_note(2); - assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone())); - // cancelled. - assert!(!>::exists()); - // fails - same proposal can't be resubmitted. - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), - Error::::ProposalBlacklisted - ); - - fast_forward_to(1); - // fails as we're still in cooloff period. - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), - Error::::ProposalBlacklisted - ); - - fast_forward_to(2); - // works; as we're out of the cooloff period. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert!(>::exists()); - - // 3 can't veto the same thing twice. - assert_noop!( - Democracy::veto_external(Origin::signed(3), h.clone()), - Error::::AlreadyVetoed - ); - - // 4 vetoes. - assert_ok!(Democracy::veto_external(Origin::signed(4), h.clone())); - // cancelled again. - assert!(!>::exists()); - - fast_forward_to(3); - // same proposal fails as we're still in cooloff - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), - Error::::ProposalBlacklisted - ); - // different proposal works fine. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - }); -} - -#[test] -fn external_blacklisting_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - - let hash = set_balance_proposal_hash(2); - assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); - - fast_forward_to(2); - assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); - - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash_and_note(2),), - Error::::ProposalBlacklisted, - ); - }); -} - -#[test] -fn external_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_noop!( - Democracy::external_propose(Origin::signed(1), set_balance_proposal_hash(2),), - BadOrigin, - ); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(1),), - Error::::DuplicateProposal - ); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn external_majority_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_noop!( - Democracy::external_propose_majority(Origin::signed(1), set_balance_proposal_hash(2)), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SimpleMajority, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn external_default_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_noop!( - Democracy::external_propose_default(Origin::signed(3), set_balance_proposal_hash(2)), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_default( - Origin::signed(1), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SuperMajorityAgainst, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn external_and_public_interleaving_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(1), - )); - assert_ok!(propose_set_balance_and_note(6, 2, 2)); - - fast_forward_to(2); - - // both waiting: external goes first. - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash_and_note(1), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - - fast_forward_to(4); - - // both waiting: public goes next. - assert_eq!( - Democracy::referendum_status(1), - Ok(ReferendumStatus { - end: 6, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // don't replenish public - - fast_forward_to(6); - - // it's external "turn" again, though since public is empty that doesn't really matter - assert_eq!( - Democracy::referendum_status(2), - Ok(ReferendumStatus { - end: 8, - proposal_hash: set_balance_proposal_hash_and_note(3), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(5), - )); - - fast_forward_to(8); - - // external goes again because there's no public waiting. - assert_eq!( - Democracy::referendum_status(3), - Ok(ReferendumStatus { - end: 10, - proposal_hash: set_balance_proposal_hash_and_note(5), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish both - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(7), - )); - assert_ok!(propose_set_balance_and_note(6, 4, 2)); - - fast_forward_to(10); - - // public goes now since external went last time. - assert_eq!( - Democracy::referendum_status(4), - Ok(ReferendumStatus { - end: 12, - proposal_hash: set_balance_proposal_hash_and_note(4), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish public again - assert_ok!(propose_set_balance_and_note(6, 6, 2)); - // cancel external - let h = set_balance_proposal_hash_and_note(7); - assert_ok!(Democracy::veto_external(Origin::signed(3), h)); - - fast_forward_to(12); - - // public goes again now since there's no external waiting. - assert_eq!( - Democracy::referendum_status(5), - Ok(ReferendumStatus { - end: 14, - proposal_hash: set_balance_proposal_hash_and_note(6), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} diff --git a/frame/conviction-voting/src/tests/fast_tracking.rs b/frame/conviction-voting/src/tests/fast_tracking.rs deleted file mode 100644 index 9b2f2760bde1c..0000000000000 --- a/frame/conviction-voting/src/tests/fast_tracking.rs +++ /dev/null @@ -1,98 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for fast-tracking functionality. - -use super::*; - -#[test] -fn fast_track_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), - Error::::ProposalMissing - ); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_ok!(Democracy::fast_track(Origin::signed(5), h, 2, 0)); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 2, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SimpleMajority, - delay: 0, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn instant_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), - Error::::ProposalMissing - ); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_noop!(Democracy::fast_track(Origin::signed(5), h, 1, 0), BadOrigin); - assert_noop!( - Democracy::fast_track(Origin::signed(6), h, 1, 0), - Error::::InstantNotAllowed - ); - INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true); - assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0)); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 1, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SimpleMajority, - delay: 0, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn fast_track_referendum_fails_when_no_simple_majority() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), - Error::::NotSimpleMajority - ); - }); -} diff --git a/frame/conviction-voting/src/tests/lock_voting.rs b/frame/conviction-voting/src/tests/lock_voting.rs deleted file mode 100644 index 8b80b39c14aab..0000000000000 --- a/frame/conviction-voting/src/tests/lock_voting.rs +++ /dev/null @@ -1,377 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for functionality concerning locking and lock-voting. - -use super::*; -use std::convert::TryFrom; - -fn aye(x: u8, balance: u64) -> AccountVote { - AccountVote::Standard { - vote: Vote { aye: true, conviction: Conviction::try_from(x).unwrap() }, - balance, - } -} - -fn nay(x: u8, balance: u64) -> AccountVote { - AccountVote::Standard { - vote: Vote { aye: false, conviction: Conviction::try_from(x).unwrap() }, - balance, - } -} - -fn the_lock(amount: u64) -> BalanceLock { - BalanceLock { id: DEMOCRACY_ID, amount, reasons: pallet_balances::Reasons::Misc } -} - -#[test] -fn lock_voting_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); - assert_ok!(Democracy::vote(Origin::signed(4), r, aye(2, 40))); - assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); - assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); - - // All balances are currently locked. - for i in 1..=5 { - assert_eq!(Balances::locks(i), vec![the_lock(i * 10)]); - } - - fast_forward_to(2); - - // Referendum passed; 1 and 5 didn't get their way and can now reap and unlock. - assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); - assert_ok!(Democracy::unlock(Origin::signed(1), 1)); - // Anyone can reap and unlock anyone else's in this context. - assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 5, r)); - assert_ok!(Democracy::unlock(Origin::signed(2), 5)); - - // 2, 3, 4 got their way with the vote, so they cannot be reaped by others. - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 2, r), - Error::::NoPermission - ); - // However, they can be unvoted by the owner, though it will make no difference to the lock. - assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); - assert_ok!(Democracy::unlock(Origin::signed(2), 2)); - - assert_eq!(Balances::locks(1), vec![]); - assert_eq!(Balances::locks(2), vec![the_lock(20)]); - assert_eq!(Balances::locks(3), vec![the_lock(30)]); - assert_eq!(Balances::locks(4), vec![the_lock(40)]); - assert_eq!(Balances::locks(5), vec![]); - assert_eq!(Balances::free_balance(42), 2); - - fast_forward_to(7); - // No change yet... - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 4, r), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(1), 4)); - assert_eq!(Balances::locks(4), vec![the_lock(40)]); - fast_forward_to(8); - // 4 should now be able to reap and unlock - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 4, r)); - assert_ok!(Democracy::unlock(Origin::signed(1), 4)); - assert_eq!(Balances::locks(4), vec![]); - - fast_forward_to(13); - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 3, r), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(1), 3)); - assert_eq!(Balances::locks(3), vec![the_lock(30)]); - fast_forward_to(14); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 3, r)); - assert_ok!(Democracy::unlock(Origin::signed(1), 3)); - assert_eq!(Balances::locks(3), vec![]); - - // 2 doesn't need to reap_vote here because it was already done before. - fast_forward_to(25); - assert_ok!(Democracy::unlock(Origin::signed(1), 2)); - assert_eq!(Balances::locks(2), vec![the_lock(20)]); - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(1), 2)); - assert_eq!(Balances::locks(2), vec![]); - }); -} - -#[test] -fn no_locks_without_conviction_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(0, 10))); - - fast_forward_to(2); - - assert_eq!(Balances::free_balance(42), 2); - assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 1, r)); - assert_ok!(Democracy::unlock(Origin::signed(2), 1)); - assert_eq!(Balances::locks(1), vec![]); - }); -} - -#[test] -fn lock_voting_should_work_with_delegation() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); - assert_ok!(Democracy::delegate(Origin::signed(4), 2, Conviction::Locked2x, 40)); - assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); - - assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -fn setup_three_referenda() -> (u32, u32, u32) { - System::set_block_number(0); - let r1 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r1, aye(4, 10))); - - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r2, aye(3, 20))); - - let r3 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r3, aye(2, 50))); - - fast_forward_to(2); - - (r1, r2, r3) -} - -#[test] -fn prior_lockvotes_should_be_enforced() { - new_test_ext().execute_with(|| { - let r = setup_three_referenda(); - // r.0 locked 10 until 2 + 8 * 3 = #26 - // r.1 locked 20 until 2 + 4 * 3 = #14 - // r.2 locked 50 until 2 + 2 * 3 = #8 - - fast_forward_to(7); - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 5, r.2), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(50)]); - fast_forward_to(8); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(20)]); - fast_forward_to(13); - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 5, r.1), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(20)]); - fast_forward_to(14); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(10)]); - fast_forward_to(25); - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 5, r.0), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(10)]); - fast_forward_to(26); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn single_consolidation_of_lockvotes_should_work_as_before() { - new_test_ext().execute_with(|| { - let r = setup_three_referenda(); - // r.0 locked 10 until 2 + 8 * 3 = #26 - // r.1 locked 20 until 2 + 4 * 3 = #14 - // r.2 locked 50 until 2 + 2 * 3 = #8 - - fast_forward_to(7); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(50)]); - fast_forward_to(8); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(20)]); - - fast_forward_to(13); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(20)]); - fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(10)]); - - fast_forward_to(25); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(10)]); - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn multi_consolidation_of_lockvotes_should_be_conservative() { - new_test_ext().execute_with(|| { - let r = setup_three_referenda(); - // r.0 locked 10 until 2 + 8 * 3 = #26 - // r.1 locked 20 until 2 + 4 * 3 = #14 - // r.2 locked 50 until 2 + 2 * 3 = #8 - - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); - - fast_forward_to(8); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 20); - - fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 10); - - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn locks_should_persist_from_voting_to_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r, aye(4, 10))); - fast_forward_to(2); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); - // locked 10 until #26. - - assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked3x, 20)); - // locked 20. - assert!(Balances::locks(5)[0].amount == 20); - - assert_ok!(Democracy::undelegate(Origin::signed(5))); - // locked 20 until #14 - - fast_forward_to(13); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount == 20); - - fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 10); - - fast_forward_to(25); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 10); - - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn locks_should_persist_from_delegation_to_voting() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked5x, 5)); - assert_ok!(Democracy::undelegate(Origin::signed(5))); - // locked 5 until 16 * 3 = #48 - - let r = setup_three_referenda(); - // r.0 locked 10 until 2 + 8 * 3 = #26 - // r.1 locked 20 until 2 + 4 * 3 = #14 - // r.2 locked 50 until 2 + 2 * 3 = #8 - - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); - - fast_forward_to(8); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 20); - - fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 10); - - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 5); - - fast_forward_to(48); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} diff --git a/frame/conviction-voting/src/tests/preimage.rs b/frame/conviction-voting/src/tests/preimage.rs deleted file mode 100644 index 6d478fcaa68c7..0000000000000 --- a/frame/conviction-voting/src/tests/preimage.rs +++ /dev/null @@ -1,219 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The preimage tests. - -use super::*; - -#[test] -fn missing_preimage_should_fail() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn preimage_deposit_should_be_required_and_returned() { - new_test_ext_execute_with_cond(|operational| { - // fee of 100 is too much. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); - assert_noop!( - if operational { - Democracy::note_preimage_operational(Origin::signed(6), vec![0; 500]) - } else { - Democracy::note_preimage(Origin::signed(6), vec![0; 500]) - }, - BalancesError::::InsufficientBalance, - ); - // fee of 1 is reasonable. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn preimage_deposit_should_be_reapable_earlier_by_owner() { - new_test_ext_execute_with_cond(|operational| { - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(if operational { - Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) - } else { - Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) - }); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2), u32::MAX), - Error::::TooEarly - ); - next_block(); - assert_ok!(Democracy::reap_preimage( - Origin::signed(6), - set_balance_proposal_hash(2), - u32::MAX - )); - - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::reserved_balance(6), 0); - }); -} - -#[test] -fn preimage_deposit_should_be_reapable() { - new_test_ext_execute_with_cond(|operational| { - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), - Error::::PreimageMissing - ); - - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(if operational { - Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) - } else { - Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) - }); - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), - Error::::TooEarly - ); - - next_block(); - assert_ok!(Democracy::reap_preimage( - Origin::signed(5), - set_balance_proposal_hash(2), - u32::MAX - )); - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 48); - assert_eq!(Balances::free_balance(5), 62); - }); -} - -#[test] -fn noting_imminent_preimage_for_free_should_work() { - new_test_ext_execute_with_cond(|operational| { - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 1, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_noop!( - if operational { - Democracy::note_imminent_preimage_operational( - Origin::signed(6), - set_balance_proposal(2), - ) - } else { - Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)) - }, - Error::::NotImminent - ); - - next_block(); - - // Now we're in the dispatch queue it's all good. - assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); - - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn reaping_imminent_preimage_should_fail() { - new_test_ext().execute_with(|| { - let h = set_balance_proposal_hash_and_note(2); - let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - next_block(); - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(6), h, u32::MAX), - Error::::Imminent - ); - }); -} - -#[test] -fn note_imminent_preimage_can_only_be_successful_once() { - new_test_ext().execute_with(|| { - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 1, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - next_block(); - - // First time works - assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); - - // Second time fails - assert_noop!( - Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)), - Error::::DuplicatePreimage - ); - - // Fails from any user - assert_noop!( - Democracy::note_imminent_preimage(Origin::signed(5), set_balance_proposal(2)), - Error::::DuplicatePreimage - ); - }); -} diff --git a/frame/conviction-voting/src/tests/public_proposals.rs b/frame/conviction-voting/src/tests/public_proposals.rs deleted file mode 100644 index 34713c3e15725..0000000000000 --- a/frame/conviction-voting/src/tests/public_proposals.rs +++ /dev/null @@ -1,149 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for the public proposal queue. - -use super::*; - -#[test] -fn backing_for_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(1, 3, 3)); - assert_eq!(Democracy::backing_for(0), Some(2)); - assert_eq!(Democracy::backing_for(1), Some(4)); - assert_eq!(Democracy::backing_for(2), Some(3)); - }); -} - -#[test] -fn deposit_for_proposals_should_be_taken() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!(Balances::free_balance(2), 15); - assert_eq!(Balances::free_balance(5), 35); - }); -} - -#[test] -fn deposit_for_proposals_should_be_returned() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - fast_forward_to(3); - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::free_balance(2), 20); - assert_eq!(Balances::free_balance(5), 50); - }); -} - -#[test] -fn proposal_with_deposit_below_minimum_should_not_work() { - new_test_ext().execute_with(|| { - assert_noop!(propose_set_balance(1, 2, 0), Error::::ValueLow); - }); -} - -#[test] -fn poor_proposer_should_not_work() { - new_test_ext().execute_with(|| { - assert_noop!(propose_set_balance(1, 2, 11), BalancesError::::InsufficientBalance); - }); -} - -#[test] -fn poor_seconder_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(2, 2, 11)); - assert_noop!( - Democracy::second(Origin::signed(1), 0, u32::MAX), - BalancesError::::InsufficientBalance - ); - }); -} - -#[test] -fn invalid_seconds_upper_bound_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_noop!(Democracy::second(Origin::signed(2), 0, 0), Error::::WrongUpperBound); - }); -} - -#[test] -fn cancel_proposal_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_noop!(Democracy::cancel_proposal(Origin::signed(1), 0), BadOrigin); - assert_ok!(Democracy::cancel_proposal(Origin::root(), 0)); - assert_eq!(Democracy::backing_for(0), None); - assert_eq!(Democracy::backing_for(1), Some(4)); - }); -} - -#[test] -fn blacklisting_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let hash = set_balance_proposal_hash(2); - - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - - assert_noop!(Democracy::blacklist(Origin::signed(1), hash.clone(), None), BadOrigin); - assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); - - assert_eq!(Democracy::backing_for(0), None); - assert_eq!(Democracy::backing_for(1), Some(4)); - - assert_noop!(propose_set_balance_and_note(1, 2, 2), Error::::ProposalBlacklisted); - - fast_forward_to(2); - - let hash = set_balance_proposal_hash(4); - assert_ok!(Democracy::referendum_status(0)); - assert_ok!(Democracy::blacklist(Origin::root(), hash, Some(0))); - assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); - }); -} - -#[test] -fn runners_up_should_come_after() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(1, 3, 3)); - fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); - fast_forward_to(4); - assert_ok!(Democracy::vote(Origin::signed(1), 1, aye(1))); - fast_forward_to(6); - assert_ok!(Democracy::vote(Origin::signed(1), 2, aye(1))); - }); -} diff --git a/frame/conviction-voting/src/tests/scheduling.rs b/frame/conviction-voting/src/tests/scheduling.rs deleted file mode 100644 index 5c857a632b97b..0000000000000 --- a/frame/conviction-voting/src/tests/scheduling.rs +++ /dev/null @@ -1,156 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for functionality concerning normal starting, ending and enacting of referenda. - -use super::*; - -#[test] -fn simple_passing_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); - assert_eq!(Democracy::lowest_unbaked(), 0); - next_block(); - next_block(); - assert_eq!(Democracy::lowest_unbaked(), 1); - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn simple_failing_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); - assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 10 }); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn ooo_inject_referendums_should_work() { - new_test_ext().execute_with(|| { - let r1 = Democracy::inject_referendum( - 3, - set_balance_proposal_hash_and_note(3), - VoteThreshold::SuperMajorityApprove, - 0, - ); - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - - assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); - assert_eq!(tally(r2), Tally { ayes: 1, nays: 0, turnout: 10 }); - - next_block(); - assert_eq!(Balances::free_balance(42), 2); - - assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); - assert_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 10 }); - - next_block(); - assert_eq!(Balances::free_balance(42), 3); - }); -} - -#[test] -fn delayed_enactment_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 1, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); - assert_ok!(Democracy::vote(Origin::signed(4), r, aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, aye(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, aye(6))); - - assert_eq!(tally(r), Tally { ayes: 21, nays: 0, turnout: 210 }); - - next_block(); - assert_eq!(Balances::free_balance(42), 0); - - next_block(); - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn lowest_unbaked_should_be_sensible() { - new_test_ext().execute_with(|| { - let r1 = Democracy::inject_referendum( - 3, - set_balance_proposal_hash_and_note(1), - VoteThreshold::SuperMajorityApprove, - 0, - ); - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - let r3 = Democracy::inject_referendum( - 10, - set_balance_proposal_hash_and_note(3), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); - assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); - // r3 is canceled - assert_ok!(Democracy::cancel_referendum(Origin::root(), r3.into())); - assert_eq!(Democracy::lowest_unbaked(), 0); - - next_block(); - - // r2 is approved - assert_eq!(Balances::free_balance(42), 2); - assert_eq!(Democracy::lowest_unbaked(), 0); - - next_block(); - - // r1 is approved - assert_eq!(Balances::free_balance(42), 1); - assert_eq!(Democracy::lowest_unbaked(), 3); - assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); - }); -} diff --git a/frame/conviction-voting/src/tests/voting.rs b/frame/conviction-voting/src/tests/voting.rs deleted file mode 100644 index e035c2d46c1b6..0000000000000 --- a/frame/conviction-voting/src/tests/voting.rs +++ /dev/null @@ -1,169 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for normal voting functionality. - -use super::*; - -#[test] -fn overvoting_should_fail() { - new_test_ext().execute_with(|| { - let r = begin_referendum(); - assert_noop!( - Democracy::vote(Origin::signed(1), r, aye(2)), - Error::::InsufficientFunds - ); - }); -} - -#[test] -fn split_voting_should_work() { - new_test_ext().execute_with(|| { - let r = begin_referendum(); - let v = AccountVote::Split { aye: 40, nay: 20 }; - assert_noop!(Democracy::vote(Origin::signed(5), r, v), Error::::InsufficientFunds); - let v = AccountVote::Split { aye: 30, nay: 20 }; - assert_ok!(Democracy::vote(Origin::signed(5), r, v)); - - assert_eq!(tally(r), Tally { ayes: 3, nays: 2, turnout: 50 }); - }); -} - -#[test] -fn split_vote_cancellation_should_work() { - new_test_ext().execute_with(|| { - let r = begin_referendum(); - let v = AccountVote::Split { aye: 30, nay: 20 }; - assert_ok!(Democracy::vote(Origin::signed(5), r, v)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); - assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn single_proposal_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - let r = 0; - assert!(Democracy::referendum_info(r).is_none()); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_eq!(Democracy::referendum_count(), 1); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 1, nays: 0, turnout: 10 }, - }) - ); - - fast_forward_to(3); - - // referendum still running - assert_ok!(Democracy::referendum_status(0)); - - // referendum runs during 2 and 3, ends @ start of 4. - fast_forward_to(4); - - assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); - assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); - - // referendum passes and wait another two blocks for enactment. - fast_forward_to(6); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn controversial_voting_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - - assert_ok!(Democracy::vote(Origin::signed(1), r, big_aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, big_nay(2))); - assert_ok!(Democracy::vote(Origin::signed(3), r, big_nay(3))); - assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); - - assert_eq!(tally(r), Tally { ayes: 110, nays: 100, turnout: 210 }); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn controversial_low_turnout_voting_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); - - assert_eq!(tally(r), Tally { ayes: 60, nays: 50, turnout: 110 }); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn passing_low_turnout_voting_should_work() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_eq!(Balances::total_issuance(), 210); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); - assert_eq!(tally(r), Tally { ayes: 100, nays: 50, turnout: 150 }); - - next_block(); - next_block(); - assert_eq!(Balances::free_balance(42), 2); - }); -} diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 19ee8baf609e6..e8ffd950cb6da 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -53,7 +53,7 @@ impl> VoteTally } } -impl> Tally { +impl> Tally { /// Create a new tally. pub fn new(vote: Vote, balance: Votes) -> Self { let Delegations { votes, capital } = vote.conviction.votes(balance); diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index 03ca020ca0949..2c09d93cf3c0b 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -17,7 +17,7 @@ //! The vote datatype. -use crate::{Conviction, Delegations, ReferendumIndex}; +use crate::{Conviction, Delegations}; use codec::{Decode, Encode, EncodeLike, Input, Output}; use scale_info::TypeInfo; use sp_runtime::{ @@ -132,7 +132,7 @@ impl PriorLock { +pub enum Voting { /// The account is voting directly. `delegations` is the total amount of post-conviction voting /// weight that it controls from those that have delegated to it. Direct { @@ -155,8 +155,8 @@ pub enum Voting { }, } -impl Default - for Voting +impl Default + for Voting { fn default() -> Self { Voting::Direct { @@ -167,8 +167,8 @@ impl Default } } -impl - Voting +impl + Voting { pub fn rejig(&mut self, now: BlockNumber) { match self { diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 4b6bc9984ad04..9be29aa37c6ce 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -30,7 +30,7 @@ use frame_support::{ ensure, BoundedVec, traits::{ schedule::{DispatchTime, Named as ScheduleNamed}, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, - Referenda, VoteTally, + Referenda, VoteTally, PollStatus, }, }; use scale_info::TypeInfo; @@ -50,11 +50,11 @@ pub use types::{ }; pub use weights::WeightInfo; -#[cfg(test)] -mod tests; +//#[cfg(test)] +//mod tests; -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmarking; +//#[cfg(feature = "runtime-benchmarks")] +//pub mod benchmarking; const ASSEMBLY_ID: LockIdentifier = *b"assembly"; @@ -395,9 +395,33 @@ pub mod pallet { } } -impl Referenda for Pallet { +impl Referenda for Pallet { type Index = ReferendumIndex; type Votes = VotesOf; + type Moment = T::BlockNumber; + fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber>) -> Result, + ) -> Result { + match ReferendumInfoFor::::get(index) { + Some(ReferendumInfo::Ongoing(mut status)) => { + let result = f(PollStatus::Ongoing(&mut status.tally))?; + let now = frame_system::Pallet::::block_number(); + let (info, _) = Self::service_referendum(now, index, status); + ReferendumInfoFor::::insert(index, info); + Ok(result) + }, + Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Done(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Done(end, false)), + _ => f(PollStatus::None), + } + } + fn tally(index: Self::Index) -> Option { + Some(Self::ensure_ongoing(index).ok()?.tally) + } + fn is_active(index: Self::Index) -> bool { + Self::ensure_ongoing(index).is_ok() + } } impl Pallet { @@ -658,7 +682,7 @@ impl Pallet { let (desired, call_hash) = (status.enactment, status.proposal_hash); Self::schedule_enactment(index, track, desired, status.origin, call_hash); Self::deposit_event(Event::::Confirmed { index, tally: status.tally }); - return (ReferendumInfo::Approved(status.submission_deposit, status.decision_deposit), true) + return (ReferendumInfo::Approved(now, status.submission_deposit, status.decision_deposit), true) } dirty = deciding.confirming.is_none(); let confirmation = deciding.confirming @@ -671,7 +695,7 @@ impl Pallet { Self::cancel_referendum_alarm(index); Self::note_one_fewer_deciding(status.track, track); Self::deposit_event(Event::::Rejected { index, tally: status.tally }); - return (ReferendumInfo::Rejected(status.submission_deposit, status.decision_deposit), true) + return (ReferendumInfo::Rejected(now, status.submission_deposit, status.decision_deposit), true) } // Cannot be confirming dirty = deciding.confirming.is_some(); @@ -682,7 +706,7 @@ impl Pallet { // Too long without being decided - end it. Self::cancel_referendum_alarm(index); Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); - return (ReferendumInfo::TimedOut(status.submission_deposit, status.decision_deposit), true) + return (ReferendumInfo::TimedOut(now, status.submission_deposit, status.decision_deposit), true) } Self::set_referendum_alarm(index, alarm); diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 06c4ac666cfba..a2d758529988d 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -33,7 +33,7 @@ use sp_runtime::{ traits::{BadOrigin, BlakeTwo256, IdentityLookup}, Perbill, }; - +/* mod cancellation; mod decoders; mod delegation; @@ -44,7 +44,7 @@ mod preimage; mod public_proposals; mod scheduling; mod voting; - +*/ const AYE: Vote = Vote { aye: true, conviction: Conviction::None }; const NAY: Vote = Vote { aye: false, conviction: Conviction::None }; const BIG_AYE: Vote = Vote { aye: true, conviction: Conviction::Locked1x }; diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index a51a1035638ca..d7a9db2f2e5ae 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -211,12 +211,12 @@ impl { /// Referendum has been submitted and is being voted on. Ongoing(ReferendumStatus), - /// Referendum finished at `end` with approval. Submission deposit is held. - Approved(Deposit, Option>), - /// Referendum finished at `end` with rejection. Submission deposit is held. - Rejected(Deposit, Option>), - /// Referendum finished at `end` and was never decided. Submission deposit is held. - TimedOut(Deposit, Option>), + /// Referendum finished with approval. Submission deposit is held. + Approved(Moment, Deposit, Option>), + /// Referendum finished with rejection. Submission deposit is held. + Rejected(Moment, Deposit, Option>), + /// Referendum finished and was never decided. Submission deposit is held. + TimedOut(Moment, Deposit, Option>), } impl @@ -225,7 +225,7 @@ impl pub fn take_decision_deposit(&mut self) -> Option> { use ReferendumInfo::*; match self { - Approved(_, d) | Rejected(_, d) | TimedOut(_, d) => d.take(), + Approved(_, _, d) | Rejected(_, _, d) | TimedOut(_, _, d) => d.take(), // Cannot refund deposit if Ongoing as this breaks assumptions. _ => None, } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index d102617a3b4af..a0526c2fca7ea 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -88,5 +88,5 @@ pub use dispatch::{EnsureOrigin, OriginTrait, UnfilteredDispatchable}; mod voting; pub use voting::{ - CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, Referenda, + CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, Referenda, PollStatus, }; diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index c501608765b44..fe2e11c688eab 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -18,9 +18,10 @@ //! Traits and associated data structures concerned with voting, and moving between tokens and //! votes. +use codec::HasCompact; use sp_arithmetic::{Perbill, traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}}; use sp_runtime::traits::Member; -use crate::dispatch::Parameter; +use crate::dispatch::{Parameter, DispatchError}; /// 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 @@ -96,10 +97,35 @@ pub trait VoteTally { fn approval(&self) -> Perbill; } -pub trait Referenda { - type Index: Parameter + Member + Ord + PartialOrd + Copy; - type Votes: Parameter + Member + Ord + PartialOrd + Copy; +pub enum PollStatus { + None, + Ongoing(Tally), + Done(Moment, bool), +} - // TODO +impl PollStatus { + pub fn ensure_ongoing(self) -> Option { + match self { + Self::Ongoing(t) => Some(t), + _ => None, + } + } } +pub trait Referenda { + type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; + type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; + type Moment; + + /// `true` if the referendum `index` can be voted on. Once this is `false`, existing votes may + /// be cancelled permissionlessly. + fn is_active(index: Self::Index) -> bool; + + /// Don't use this if you might mutate - use `try_mutate_tally` instead. + fn tally(index: Self::Index) -> Option; + + fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut Tally, Self::Moment>) -> Result, + ) -> Result; +} From fdf3220a7a5cf618dfcc5373b11d2081c23b8424 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 10 Nov 2021 16:24:41 +0100 Subject: [PATCH 07/66] Building --- frame/conviction-voting/src/lib.rs | 65 ++++++++++------------------ frame/conviction-voting/src/tests.rs | 26 +++++++++-- frame/referenda/src/lib.rs | 17 ++++++++ frame/support/src/traits/voting.rs | 5 +++ 4 files changed, 66 insertions(+), 47 deletions(-) diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index d7a5e1c83de9e..035dcbf675cdd 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -25,17 +25,14 @@ #![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Encode, Codec}; -use frame_support::{ - BoundedVec, ensure, traits::{ - Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, - WithdrawReasons, PollStatus, Referenda, - schedule::{DispatchTime, Named as ScheduleNamed}, - } -}; -use scale_info::TypeInfo; -use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, Perbill, traits::{Dispatchable, Saturating, One, AtLeast32BitUnsigned}}; -use sp_std::{prelude::*, fmt::Debug}; +use frame_support::{ensure, traits::{ + Currency, Get, LockIdentifier, LockableCurrency, ReservableCurrency, WithdrawReasons, + PollStatus, Referenda, +}}; +use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, Perbill, traits::{ + Saturating, Zero, AtLeast32BitUnsigned +}}; +use sp_std::prelude::*; mod conviction; mod types; @@ -57,9 +54,6 @@ const CONVICTION_VOTING_ID: LockIdentifier = *b"pyconvot"; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId, ->>::NegativeImbalance; type VotingOf = Voting< BalanceOf, ::AccountId, @@ -72,12 +66,7 @@ type ReferendumIndexOf = <::Referenda as Referenda>>: #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{ - pallet_prelude::*, - traits::EnsureOrigin, - weights::Pays, - Parameter, - }; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use sp_runtime::DispatchResult; @@ -356,7 +345,7 @@ impl Pallet { vote: AccountVote>, ) -> DispatchResult { ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); - T::Referenda::access_poll(ref_index, |poll_status| { + T::Referenda::try_access_poll(ref_index, |poll_status| { let tally = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; VotingFor::::try_mutate(who, |voting| { if let Voting::Direct { ref mut votes, delegations, .. } = voting { @@ -411,7 +400,7 @@ impl Pallet { .map_err(|_| Error::::NotVoter)?; let v = votes.remove(i); - T::Referenda::access_poll(ref_index, |poll_status| match poll_status { + T::Referenda::try_access_poll(ref_index, |poll_status| match poll_status { PollStatus::Ongoing(tally) => { ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); // Shouldn't be possible to fail, but we handle it gracefully. @@ -445,8 +434,6 @@ impl Pallet { /// Return the number of votes for `who` fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { - todo!() - /* VotingFor::::mutate(who, |voting| match voting { Voting::Delegating { delegations, .. } => { // We don't support second level delegating, so we don't need to do anything more. @@ -457,22 +444,20 @@ impl Pallet { *delegations = delegations.saturating_add(amount); for &(ref_index, account_vote) in votes.iter() { if let AccountVote::Standard { vote, .. } = account_vote { - ReferendumInfoFor::::mutate(ref_index, |maybe_info| { - if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { - status.tally.increase(vote.aye, amount); + T::Referenda::access_poll(ref_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, amount: Delegations>) -> u32 { - todo!() - /* VotingFor::::mutate(who, |voting| match voting { Voting::Delegating { delegations, .. } => { // We don't support second level delegating, so we don't need to do anything more. @@ -483,16 +468,16 @@ impl Pallet { *delegations = delegations.saturating_sub(amount); for &(ref_index, account_vote) in votes.iter() { if let AccountVote::Standard { vote, .. } = account_vote { - ReferendumInfoFor::::mutate(ref_index, |maybe_info| { - if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { - status.tally.reduce(vote.aye, amount); + T::Referenda::access_poll(ref_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`. @@ -504,8 +489,6 @@ impl Pallet { conviction: Conviction, balance: BalanceOf, ) -> Result { - todo!() - /* ensure!(who != target, Error::::Nonsense); ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); let votes = VotingFor::::try_mutate(&who, |voting| -> Result { @@ -536,15 +519,13 @@ impl Pallet { Ok(votes) })?; Self::deposit_event(Event::::Delegated(who, target)); - Ok(votes)*/ + Ok(votes) } /// Attempt to end the current delegation. /// /// Return the number of votes of upstream. fn try_undelegate(who: T::AccountId) -> Result { - todo!() - /* let votes = VotingFor::::try_mutate(&who, |voting| -> Result { let mut old = Voting::default(); sp_std::mem::swap(&mut old, voting); @@ -564,14 +545,12 @@ impl Pallet { } })?; Self::deposit_event(Event::::Undelegated(who)); - Ok(votes)*/ + Ok(votes) } /// Rejig the lock on an account. It will never get more stringent (since that would indicate /// a security hole) but may be reduced from what they are currently. fn update_lock(who: &T::AccountId) { - todo!() - /* let lock_needed = VotingFor::::mutate(who, |voting| { voting.rejig(frame_system::Pallet::::block_number()); voting.locked_balance() @@ -580,6 +559,6 @@ impl Pallet { T::Currency::remove_lock(CONVICTION_VOTING_ID, who); } else { T::Currency::set_lock(CONVICTION_VOTING_ID, who, lock_needed, WithdrawReasons::TRANSFER); - }*/ + } } } diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index d1ef0edbf9be1..c2d1cff35377b 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -21,7 +21,7 @@ use super::*; use crate as pallet_conviction_voting; use frame_support::{ ord_parameter_types, parameter_types, - traits::{Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers}, + traits::{Contains, EqualPrivilegeOnly, OnInitialize, SortedMembers}, weights::Weight, }; use frame_system::EnsureRoot; @@ -167,9 +167,15 @@ impl Referenda> for TestReferenda { fn is_active(_index: u8) -> bool { false } fn access_poll( _index: Self::Index, - _f: impl FnOnce(PollStatus<&mut TallyOf, u64>) -> Result, + f: impl FnOnce(PollStatus<&mut TallyOf, u64>) -> R, + ) -> R { + f(PollStatus::None) + } + fn try_access_poll( + _index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, u64>) -> Result, ) -> Result { - Err(DispatchError::Other("Unimplemented")) + f(PollStatus::None) } fn tally(_index: Self::Index) -> Option> { None @@ -239,6 +245,18 @@ fn big_nay(who: u64) -> AccountVote { AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } } -fn tally(r: u32) -> TallyOf { +fn tally(_r: u32) -> TallyOf { todo!() } + +#[test] +fn basic_stuff() { + new_test_ext_execute_with_cond(|_x| { + let _ = tally(0); + let _ = aye(0); + let _ = nay(0); + let _ = big_aye(0); + let _ = big_nay(0); + fast_forward_to(1); + }); +} \ No newline at end of file diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 9be29aa37c6ce..6f10831f3e234 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -400,6 +400,23 @@ impl Referenda for Pallet { type Votes = VotesOf; type Moment = T::BlockNumber; fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber>) -> R, + ) -> R { + match ReferendumInfoFor::::get(index) { + Some(ReferendumInfo::Ongoing(mut status)) => { + let result = f(PollStatus::Ongoing(&mut status.tally)); + let now = frame_system::Pallet::::block_number(); + let (info, _) = Self::service_referendum(now, index, status); + ReferendumInfoFor::::insert(index, info); + result + }, + Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Done(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Done(end, false)), + _ => f(PollStatus::None), + } + } + fn try_access_poll( index: Self::Index, f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber>) -> Result, ) -> Result { diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index fe2e11c688eab..6b63cf861516b 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -125,6 +125,11 @@ pub trait Referenda { fn tally(index: Self::Index) -> Option; fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut Tally, Self::Moment>) -> R, + ) -> R; + + fn try_access_poll( index: Self::Index, f: impl FnOnce(PollStatus<&mut Tally, Self::Moment>) -> Result, ) -> Result; From d785cc8c30d682400cfb119d01c87df7b6a6a989 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 10 Nov 2021 17:19:19 +0100 Subject: [PATCH 08/66] Some TODOs --- frame/referenda/src/lib.rs | 80 +++++++++++++++++++++--------------- frame/referenda/src/types.rs | 4 ++ 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 6f10831f3e234..55da41462a939 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -85,9 +85,9 @@ pub mod pallet { // Origins and unbalances. /// Origin from which any vote may be cancelled. - type CancelOrigin: EnsureOrigin>; + type CancelOrigin: EnsureOrigin<::Origin>; /// Origin from which any vote may be killed. - type KillOrigin: EnsureOrigin>; + type KillOrigin: EnsureOrigin<::Origin>; /// Handler for the unbalanced reduction when slashing a preimage deposit. type Slash: OnUnbalanced>; /// The counting type for votes. Usually just balance. @@ -256,31 +256,6 @@ pub mod pallet { NotOngoing, /// Referendum's decision deposit is already paid. HaveDeposit, - /// Proposal does not exist - ProposalMissing, - /// Cannot cancel the same proposal twice - AlreadyCanceled, - /// Proposal already made - DuplicateProposal, - /// Vote given for invalid referendum - ReferendumInvalid, - /// The given account did not vote on the referendum. - NotVoter, - /// The actor has no permission to conduct the action. - NoPermission, - /// The account is already delegating. - AlreadyDelegating, - /// Too high a balance was provided that the account cannot afford. - InsufficientFunds, - /// The account is not currently delegating. - NotDelegating, - /// The account currently has votes attached to it and the operation cannot succeed until - /// these are removed, either through `unvote` or `reap_vote`. - VotesExist, - /// Delegation to oneself makes no sense. - Nonsense, - /// Invalid upper bound. - WrongUpperBound, /// The track identifier given was invalid. BadTrack, /// There are already a full complement of referendums in progress for this track. @@ -295,12 +270,6 @@ pub mod pallet { NoTrack, } - // TODO: bans - // TODO: cancel_referendum - // TODO: kill_referendum - // TODO: check events cover everything useful - // TODO: remove unused errors - #[pallet::call] impl Pallet { /// Propose a referendum on a privileged action. @@ -370,6 +339,44 @@ pub mod pallet { Ok(()) } + #[pallet::weight(0)] + pub fn cancel( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResult { + T::CancelOrigin::ensure_origin(origin)?; + let status = Self::ensure_ongoing(index)?; + let track = Self::track(status.track).ok_or(Error::::BadTrack)?; + Self::cancel_referendum_alarm(index); + Self::note_one_fewer_deciding(status.track, track); + Self::deposit_event(Event::::Cancelled { index, tally: status.tally }); + let info = ReferendumInfo::Cancelled( + frame_system::Pallet::::block_number(), + status.submission_deposit, + status.decision_deposit, + ); + ReferendumInfoFor::::insert(index, info); + Ok(()) + } + + #[pallet::weight(0)] + pub fn kill( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResult { + T::CancelOrigin::ensure_origin(origin)?; + let status = Self::ensure_ongoing(index)?; + let track = Self::track(status.track).ok_or(Error::::BadTrack)?; + Self::cancel_referendum_alarm(index); + Self::note_one_fewer_deciding(status.track, track); + Self::deposit_event(Event::::Killed { index, tally: status.tally }); + Self::slash_deposit(Some(status.submission_deposit)); + Self::slash_deposit(status.decision_deposit); + let info = ReferendumInfo::Killed(frame_system::Pallet::::block_number()); + ReferendumInfoFor::::insert(index, info); + Ok(()) + } + /// Advance a referendum onto its next logical state. This will happen eventually anyway, /// but you can nudge it #[pallet::weight(0)] @@ -758,6 +765,13 @@ impl Pallet { } } + /// Slash a deposit, if `Some`. + fn slash_deposit(deposit: Option>>) { + if let Some(Deposit { who, amount }) = deposit { + T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); + } + } + fn track(id: TrackIdOf) -> Option<&'static TrackInfoOf> { let tracks = T::Tracks::tracks(); let index = tracks.binary_search_by_key(&id, |x| x.0) diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index d7a9db2f2e5ae..2b7b7f1326cf5 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -215,8 +215,12 @@ pub enum ReferendumInfo, Option>), /// Referendum finished with rejection. Submission deposit is held. Rejected(Moment, Deposit, Option>), + /// Referendum finished with cancelation. Submission deposit is held. + Cancelled(Moment, Deposit, Option>), /// Referendum finished and was never decided. Submission deposit is held. TimedOut(Moment, Deposit, Option>), + /// Referendum finished with a kill. + Killed(Moment), } impl From 7d6a01b81b5d72ae88ac9efde0bcd21374f1b3be Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 11 Nov 2021 15:44:53 +0100 Subject: [PATCH 09/66] Tests building --- frame/conviction-voting/src/lib.rs | 2 +- frame/referenda/src/lib.rs | 40 +- frame/referenda/src/tests.rs | 272 +------------ frame/referenda/src/tests/cancellation.rs | 96 ----- frame/referenda/src/tests/decoders.rs | 85 ---- frame/referenda/src/tests/delegation.rs | 179 --------- .../referenda/src/tests/external_proposing.rs | 303 -------------- frame/referenda/src/tests/fast_tracking.rs | 98 ----- frame/referenda/src/tests/lock_voting.rs | 377 ------------------ frame/referenda/src/tests/preimage.rs | 219 ---------- frame/referenda/src/tests/public_proposals.rs | 149 ------- frame/referenda/src/tests/scheduling.rs | 156 -------- frame/referenda/src/tests/voting.rs | 169 -------- frame/referenda/src/types.rs | 58 ++- frame/support/src/dispatch.rs | 28 +- frame/support/src/traits/dispatch.rs | 6 +- frame/support/src/traits/voting.rs | 2 +- frame/system/src/lib.rs | 23 +- 18 files changed, 107 insertions(+), 2155 deletions(-) delete mode 100644 frame/referenda/src/tests/cancellation.rs delete mode 100644 frame/referenda/src/tests/decoders.rs delete mode 100644 frame/referenda/src/tests/delegation.rs delete mode 100644 frame/referenda/src/tests/external_proposing.rs delete mode 100644 frame/referenda/src/tests/fast_tracking.rs delete mode 100644 frame/referenda/src/tests/lock_voting.rs delete mode 100644 frame/referenda/src/tests/preimage.rs delete mode 100644 frame/referenda/src/tests/public_proposals.rs delete mode 100644 frame/referenda/src/tests/scheduling.rs delete mode 100644 frame/referenda/src/tests/voting.rs diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 035dcbf675cdd..55a34dbefd4d6 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -410,7 +410,7 @@ impl Pallet { } Ok(()) }, - PollStatus::Done(end, approved) => { + PollStatus::Completed(end, approved) => { if let Some((lock_periods, balance)) = v.1.locked_if(approved) { let unlock_at = end + T::VoteLockingPeriod::get() * lock_periods.into(); let now = frame_system::Pallet::::block_number(); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 55da41462a939..92005476304c9 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -30,7 +30,7 @@ use frame_support::{ ensure, BoundedVec, traits::{ schedule::{DispatchTime, Named as ScheduleNamed}, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, - Referenda, VoteTally, PollStatus, + Referenda, VoteTally, PollStatus, OriginTrait, }, }; use scale_info::TypeInfo; @@ -45,13 +45,16 @@ pub mod weights; pub use pallet::*; pub use types::{ ReferendumInfo, ReferendumStatus, TrackInfo, TracksInfo, Curve, DecidingStatus, Deposit, - AtOrAfter, BalanceOf, NegativeImbalanceOf, CallOf, OriginOf, VotesOf, TallyOf, ReferendumInfoOf, + AtOrAfter, BalanceOf, NegativeImbalanceOf, CallOf, VotesOf, TallyOf, ReferendumInfoOf, ReferendumStatusOf, DecidingStatusOf, TrackInfoOf, TrackIdOf, InsertSorted, ReferendumIndex, + PalletsOriginOf, }; pub use weights::WeightInfo; -//#[cfg(test)] -//mod tests; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; //#[cfg(feature = "runtime-benchmarks")] //pub mod benchmarking; @@ -72,22 +75,21 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config + Sized { // System level stuff. - type Call: Parameter + Dispatchable> + From>; - type Origin: From> + Codec + Clone + Eq + TypeInfo + Debug; + type Call: Parameter + Dispatchable + From>; type Event: From> + IsType<::Event>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// The Scheduler. - type Scheduler: ScheduleNamed, OriginOf>; + type Scheduler: ScheduleNamed, PalletsOriginOf>; /// Currency type for this pallet. type Currency: ReservableCurrency + LockableCurrency; // Origins and unbalances. /// Origin from which any vote may be cancelled. - type CancelOrigin: EnsureOrigin<::Origin>; + type CancelOrigin: EnsureOrigin; /// Origin from which any vote may be killed. - type KillOrigin: EnsureOrigin<::Origin>; + type KillOrigin: EnsureOrigin; /// Handler for the unbalanced reduction when slashing a preimage deposit. type Slash: OnUnbalanced>; /// The counting type for votes. Usually just balance. @@ -117,7 +119,11 @@ pub mod pallet { // The other stuff. /// Information concerning the different referendum tracks. - type Tracks: TracksInfo, Self::BlockNumber, Origin = OriginOf>; + type Tracks: TracksInfo< + BalanceOf, + Self::BlockNumber, + Origin = ::PalletsOrigin, + >; } /// The next free referendum index, aka the number of referenda started so far. @@ -285,7 +291,7 @@ pub mod pallet { #[pallet::weight(0)] pub fn submit( origin: OriginFor, - proposal_origin: OriginOf, + proposal_origin: PalletsOriginOf, proposal_hash: T::Hash, enactment_moment: AtOrAfter, ) -> DispatchResult { @@ -418,8 +424,8 @@ impl Referenda for Pallet { ReferendumInfoFor::::insert(index, info); result }, - Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Done(end, true)), - Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Done(end, false)), + Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)), _ => f(PollStatus::None), } } @@ -435,8 +441,8 @@ impl Referenda for Pallet { ReferendumInfoFor::::insert(index, info); Ok(result) }, - Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Done(end, true)), - Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Done(end, false)), + Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)), _ => f(PollStatus::None), } } @@ -463,7 +469,7 @@ impl Pallet { index: ReferendumIndex, track: &TrackInfoOf, desired: AtOrAfter, - origin: OriginOf, + origin: PalletsOriginOf, call_hash: T::Hash, ) { let now = frame_system::Pallet::::block_number(); @@ -525,7 +531,7 @@ impl Pallet { DispatchTime::At(when), None, 128u8, - OriginOf::::from(frame_system::Origin::::Root), + frame_system::RawOrigin::Root.into(), call.into(), ).is_ok(); debug_assert!(ok, "Unable to schedule a new referendum?!"); diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index a2d758529988d..8d7e05b4c5780 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -17,286 +17,24 @@ //! The crate's tests. +use codec::Decode; +use frame_support::traits::Contains; use super::*; -use crate as pallet_democracy; -use codec::Encode; -use frame_support::{ - assert_noop, assert_ok, ord_parameter_types, parameter_types, - traits::{Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers}, - weights::Weight, -}; -use frame_system::{EnsureRoot, EnsureSignedBy}; -use pallet_balances::{BalanceLock, Error as BalancesError}; -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - Perbill, -}; -/* -mod cancellation; -mod decoders; -mod delegation; -mod external_proposing; -mod fast_tracking; -mod lock_voting; -mod preimage; -mod public_proposals; -mod scheduling; -mod voting; -*/ -const AYE: Vote = Vote { aye: true, conviction: Conviction::None }; -const NAY: Vote = Vote { aye: false, conviction: Conviction::None }; -const BIG_AYE: Vote = Vote { aye: true, conviction: Conviction::Locked1x }; -const BIG_NAY: Vote = Vote { aye: false, conviction: Conviction::Locked1x }; - -const MAX_PROPOSALS: u32 = 100; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, - Democracy: pallet_democracy::{Pallet, Call, Storage, Config, Event}, - } -); - -// Test that a fitlered call can be dispatched. -pub struct BaseFilter; -impl Contains 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; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} -parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; -} -impl pallet_scheduler::Config for Test { - type Event = Event; - type Origin = Origin; - type PalletsOrigin = OriginCaller; - type Call = Call; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureRoot; - type MaxScheduledPerBlock = (); - type WeightInfo = (); - type OriginPrivilegeCmp = EqualPrivilegeOnly; -} -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 const LaunchPeriod: u64 = 2; - pub const VotingPeriod: u64 = 2; - pub const FastTrackVotingPeriod: u64 = 2; - pub const MinimumDeposit: u64 = 1; - pub const EnactmentPeriod: u64 = 2; - pub const VoteLockingPeriod: u64 = 3; - pub const CooloffPeriod: u64 = 2; - pub const MaxVotes: u32 = 100; - pub const MaxProposals: u32 = MAX_PROPOSALS; - pub static PreimageByteDeposit: u64 = 0; - pub static InstantAllowed: bool = false; -} -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 for OneToFive { - fn sorted_members() -> Vec { - vec![1, 2, 3, 4, 5] - } - #[cfg(feature = "runtime-benchmarks")] - fn add(_m: &u64) {} -} - -impl Config for Test { - type Proposal = Call; - type Event = Event; - type Currency = pallet_balances::Pallet; - type EnactmentPeriod = EnactmentPeriod; - type LaunchPeriod = LaunchPeriod; - type VotingPeriod = VotingPeriod; - type VoteLockingPeriod = VoteLockingPeriod; - type FastTrackVotingPeriod = FastTrackVotingPeriod; - type MinimumDeposit = MinimumDeposit; - type ExternalOrigin = EnsureSignedBy; - type ExternalMajorityOrigin = EnsureSignedBy; - type ExternalDefaultOrigin = EnsureSignedBy; - type FastTrackOrigin = EnsureSignedBy; - type CancellationOrigin = EnsureSignedBy; - type BlacklistOrigin = EnsureRoot; - type CancelProposalOrigin = EnsureRoot; - type VetoOrigin = EnsureSignedBy; - type CooloffPeriod = CooloffPeriod; - type PreimageByteDeposit = PreimageByteDeposit; - type Slash = (); - type InstantOrigin = EnsureSignedBy; - type InstantAllowed = InstantAllowed; - type Scheduler = Scheduler; - type MaxVotes = MaxVotes; - type OperationalPreimageOrigin = EnsureSignedBy; - type PalletsOrigin = OriginCaller; - type WeightInfo = (); - type MaxProposals = MaxProposals; -} - -pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], - } - .assimilate_storage(&mut t) - .unwrap(); - pallet_democracy::GenesisConfig::::default() - .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`. -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)); -} +use crate::mock::*; #[test] fn params_should_work() { new_test_ext().execute_with(|| { - assert_eq!(Democracy::referendum_count(), 0); + assert_eq!(ReferendumCount::::get(), 0); assert_eq!(Balances::free_balance(42), 0); assert_eq!(Balances::total_issuance(), 210); }); } -fn set_balance_proposal(value: u64) -> Vec { - Call::Balances(pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 }) - .encode() -} - #[test] fn set_balance_proposal_is_correctly_filtered_out() { for i in 0..10 { - let call = Call::decode(&mut &set_balance_proposal(i)[..]).unwrap(); + let call = crate::mock::Call::decode(&mut &set_balance_proposal(i)[..]).unwrap(); assert!(!::BaseCallFilter::contains(&call)); } } - -fn set_balance_proposal_hash(value: u64) -> H256 { - BlakeTwo256::hash(&set_balance_proposal(value)[..]) -} - -fn set_balance_proposal_hash_and_note(value: u64) -> H256 { - let p = set_balance_proposal(value); - let h = BlakeTwo256::hash(&p[..]); - match Democracy::note_preimage(Origin::signed(6), p) { - Ok(_) => (), - Err(x) if x == Error::::DuplicatePreimage.into() => (), - Err(x) => panic!("{:?}", x), - } - h -} - -fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { - Democracy::propose(Origin::signed(who), set_balance_proposal_hash(value), delay) -} - -fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchResult { - Democracy::propose(Origin::signed(who), set_balance_proposal_hash_and_note(value), delay) -} - -fn next_block() { - System::set_block_number(System::block_number() + 1); - Scheduler::on_initialize(System::block_number()); - Democracy::begin_block(System::block_number()); -} - -fn fast_forward_to(n: u64) { - while System::block_number() < n { - next_block(); - } -} - -fn begin_referendum() -> ReferendumIndex { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - fast_forward_to(2); - 0 -} - -fn aye(who: u64) -> AccountVote { - AccountVote::Standard { vote: AYE, balance: Balances::free_balance(&who) } -} - -fn nay(who: u64) -> AccountVote { - AccountVote::Standard { vote: NAY, balance: Balances::free_balance(&who) } -} - -fn big_aye(who: u64) -> AccountVote { - AccountVote::Standard { vote: BIG_AYE, balance: Balances::free_balance(&who) } -} - -fn big_nay(who: u64) -> AccountVote { - AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } -} - -fn tally(r: ReferendumIndex) -> Tally { - Democracy::referendum_status(r).unwrap().tally -} diff --git a/frame/referenda/src/tests/cancellation.rs b/frame/referenda/src/tests/cancellation.rs deleted file mode 100644 index 83822bf51829f..0000000000000 --- a/frame/referenda/src/tests/cancellation.rs +++ /dev/null @@ -1,96 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for cancelation functionality. - -use super::*; - -#[test] -fn cancel_referendum_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::cancel_referendum(Origin::root(), r.into())); - assert_eq!(Democracy::lowest_unbaked(), 0); - - next_block(); - - next_block(); - - assert_eq!(Democracy::lowest_unbaked(), 1); - assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn cancel_queued_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - - assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); - - fast_forward_to(4); - - assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); - - assert_noop!(Democracy::cancel_queued(Origin::root(), 1), Error::::ProposalMissing); - assert_ok!(Democracy::cancel_queued(Origin::root(), 0)); - assert!(pallet_scheduler::Agenda::::get(6)[0].is_none()); - }); -} - -#[test] -fn emergency_cancel_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 2, - ); - assert!(Democracy::referendum_status(r).is_ok()); - - assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); - assert_ok!(Democracy::emergency_cancel(Origin::signed(4), r)); - assert!(Democracy::referendum_info(r).is_none()); - - // some time later... - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 2, - ); - assert!(Democracy::referendum_status(r).is_ok()); - assert_noop!( - Democracy::emergency_cancel(Origin::signed(4), r), - Error::::AlreadyCanceled, - ); - }); -} diff --git a/frame/referenda/src/tests/decoders.rs b/frame/referenda/src/tests/decoders.rs deleted file mode 100644 index 3c1729c4355c0..0000000000000 --- a/frame/referenda/src/tests/decoders.rs +++ /dev/null @@ -1,85 +0,0 @@ -// 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. - -//! The for various partial storage decoders - -use super::*; -use frame_support::storage::{migration, unhashed}; - -#[test] -fn test_decode_compact_u32_at() { - new_test_ext().execute_with(|| { - let v = codec::Compact(u64::MAX); - migration::put_storage_value(b"test", b"", &[], v); - assert_eq!(decode_compact_u32_at(b"test"), None); - - for v in vec![0, 10, u32::MAX] { - let compact_v = codec::Compact(v); - unhashed::put(b"test", &compact_v); - assert_eq!(decode_compact_u32_at(b"test"), Some(v)); - } - - unhashed::kill(b"test"); - assert_eq!(decode_compact_u32_at(b"test"), None); - }) -} - -#[test] -fn len_of_deposit_of() { - new_test_ext().execute_with(|| { - for l in vec![0, 1, 200, 1000] { - let value: (Vec, u64) = ((0..l).map(|_| Default::default()).collect(), 3u64); - DepositOf::::insert(2, value); - assert_eq!(Democracy::len_of_deposit_of(2), Some(l)); - } - - DepositOf::::remove(2); - assert_eq!(Democracy::len_of_deposit_of(2), None); - }) -} - -#[test] -fn pre_image() { - new_test_ext().execute_with(|| { - let key = Default::default(); - let missing = PreimageStatus::Missing(0); - Preimages::::insert(key, missing); - assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); - assert_eq!(Democracy::check_pre_image_is_missing(key), Ok(())); - - Preimages::::remove(key); - assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); - assert_noop!(Democracy::check_pre_image_is_missing(key), Error::::NotImminent); - - for l in vec![0, 10, 100, 1000u32] { - let available = PreimageStatus::Available { - data: (0..l).map(|i| i as u8).collect(), - provider: 0, - deposit: 0, - since: 0, - expiry: None, - }; - - Preimages::::insert(key, available); - assert_eq!(Democracy::pre_image_data_len(key), Ok(l)); - assert_noop!( - Democracy::check_pre_image_is_missing(key), - Error::::DuplicatePreimage - ); - } - }) -} diff --git a/frame/referenda/src/tests/delegation.rs b/frame/referenda/src/tests/delegation.rs deleted file mode 100644 index d3afa1c13f90b..0000000000000 --- a/frame/referenda/src/tests/delegation.rs +++ /dev/null @@ -1,179 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for functionality concerning delegation. - -use super::*; - -#[test] -fn single_proposal_should_work_with_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - - // Delegate first vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); - - // Delegate a second vote. - assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); - assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 60 }); - - // Reduce first vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); - assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 50 }); - - // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. - assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); - assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 20 }); - - // Main voter cancels their vote - assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); - assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); - - // First delegator delegates half funds with conviction; nothing changes yet. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked1x, 10)); - assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); - - // Main voter reinstates their vote - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 20 }); - }); -} - -#[test] -fn self_delegation_not_allowed() { - new_test_ext().execute_with(|| { - assert_noop!( - Democracy::delegate(Origin::signed(1), 1, Conviction::None, 10), - Error::::Nonsense, - ); - }); -} - -#[test] -fn cyclic_delegation_should_unwind() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - - // Check behavior with cycle. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); - assert_ok!(Democracy::delegate(Origin::signed(1), 3, Conviction::None, 10)); - let r = 0; - assert_ok!(Democracy::undelegate(Origin::signed(3))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); - assert_ok!(Democracy::undelegate(Origin::signed(1))); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); - - // Delegated vote is counted. - assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 60 }); - }); -} - -#[test] -fn single_proposal_should_work_with_vote_and_delegation() { - // If transactor already voted, delegated vote is overwritten. - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, nay(2))); - assert_eq!(tally(r), Tally { ayes: 1, nays: 2, turnout: 30 }); - - // Delegate vote. - assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - // Delegated vote replaces the explicit vote. - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); - }); -} - -#[test] -fn single_proposal_should_work_with_undelegation() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - // Delegate and undelegate vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_ok!(Democracy::undelegate(Origin::signed(2))); - - fast_forward_to(2); - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - // Delegated vote is not counted. - assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); - }); -} - -#[test] -fn single_proposal_should_work_with_delegation_and_vote() { - // If transactor voted, delegated vote is overwritten. - new_test_ext().execute_with(|| { - let r = begin_referendum(); - // Delegate, undelegate and vote. - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); - assert_ok!(Democracy::undelegate(Origin::signed(2))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); - // Delegated vote is not counted. - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); - }); -} - -#[test] -fn conviction_should_be_honored_in_delegation() { - // If transactor voted, delegated vote is overwritten. - new_test_ext().execute_with(|| { - let r = begin_referendum(); - // Delegate, undelegate and vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - // Delegated vote is huge. - assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 }); - }); -} - -#[test] -fn split_vote_delegation_should_be_ignored() { - // If transactor voted, delegated vote is overwritten. - new_test_ext().execute_with(|| { - let r = begin_referendum(); - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); - assert_ok!(Democracy::vote(Origin::signed(1), r, AccountVote::Split { aye: 10, nay: 0 })); - // Delegated vote is huge. - assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); - }); -} diff --git a/frame/referenda/src/tests/external_proposing.rs b/frame/referenda/src/tests/external_proposing.rs deleted file mode 100644 index 7442964584fa9..0000000000000 --- a/frame/referenda/src/tests/external_proposing.rs +++ /dev/null @@ -1,303 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for functionality concerning the "external" origin. - -use super::*; - -#[test] -fn veto_external_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert!(>::exists()); - - let h = set_balance_proposal_hash_and_note(2); - assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone())); - // cancelled. - assert!(!>::exists()); - // fails - same proposal can't be resubmitted. - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), - Error::::ProposalBlacklisted - ); - - fast_forward_to(1); - // fails as we're still in cooloff period. - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), - Error::::ProposalBlacklisted - ); - - fast_forward_to(2); - // works; as we're out of the cooloff period. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert!(>::exists()); - - // 3 can't veto the same thing twice. - assert_noop!( - Democracy::veto_external(Origin::signed(3), h.clone()), - Error::::AlreadyVetoed - ); - - // 4 vetoes. - assert_ok!(Democracy::veto_external(Origin::signed(4), h.clone())); - // cancelled again. - assert!(!>::exists()); - - fast_forward_to(3); - // same proposal fails as we're still in cooloff - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), - Error::::ProposalBlacklisted - ); - // different proposal works fine. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - }); -} - -#[test] -fn external_blacklisting_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - - let hash = set_balance_proposal_hash(2); - assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); - - fast_forward_to(2); - assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); - - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash_and_note(2),), - Error::::ProposalBlacklisted, - ); - }); -} - -#[test] -fn external_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_noop!( - Democracy::external_propose(Origin::signed(1), set_balance_proposal_hash(2),), - BadOrigin, - ); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(1),), - Error::::DuplicateProposal - ); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn external_majority_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_noop!( - Democracy::external_propose_majority(Origin::signed(1), set_balance_proposal_hash(2)), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SimpleMajority, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn external_default_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_noop!( - Democracy::external_propose_default(Origin::signed(3), set_balance_proposal_hash(2)), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_default( - Origin::signed(1), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SuperMajorityAgainst, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn external_and_public_interleaving_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(1), - )); - assert_ok!(propose_set_balance_and_note(6, 2, 2)); - - fast_forward_to(2); - - // both waiting: external goes first. - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash_and_note(1), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - - fast_forward_to(4); - - // both waiting: public goes next. - assert_eq!( - Democracy::referendum_status(1), - Ok(ReferendumStatus { - end: 6, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // don't replenish public - - fast_forward_to(6); - - // it's external "turn" again, though since public is empty that doesn't really matter - assert_eq!( - Democracy::referendum_status(2), - Ok(ReferendumStatus { - end: 8, - proposal_hash: set_balance_proposal_hash_and_note(3), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(5), - )); - - fast_forward_to(8); - - // external goes again because there's no public waiting. - assert_eq!( - Democracy::referendum_status(3), - Ok(ReferendumStatus { - end: 10, - proposal_hash: set_balance_proposal_hash_and_note(5), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish both - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(7), - )); - assert_ok!(propose_set_balance_and_note(6, 4, 2)); - - fast_forward_to(10); - - // public goes now since external went last time. - assert_eq!( - Democracy::referendum_status(4), - Ok(ReferendumStatus { - end: 12, - proposal_hash: set_balance_proposal_hash_and_note(4), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish public again - assert_ok!(propose_set_balance_and_note(6, 6, 2)); - // cancel external - let h = set_balance_proposal_hash_and_note(7); - assert_ok!(Democracy::veto_external(Origin::signed(3), h)); - - fast_forward_to(12); - - // public goes again now since there's no external waiting. - assert_eq!( - Democracy::referendum_status(5), - Ok(ReferendumStatus { - end: 14, - proposal_hash: set_balance_proposal_hash_and_note(6), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} diff --git a/frame/referenda/src/tests/fast_tracking.rs b/frame/referenda/src/tests/fast_tracking.rs deleted file mode 100644 index 9b2f2760bde1c..0000000000000 --- a/frame/referenda/src/tests/fast_tracking.rs +++ /dev/null @@ -1,98 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for fast-tracking functionality. - -use super::*; - -#[test] -fn fast_track_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), - Error::::ProposalMissing - ); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_ok!(Democracy::fast_track(Origin::signed(5), h, 2, 0)); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 2, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SimpleMajority, - delay: 0, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn instant_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), - Error::::ProposalMissing - ); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_noop!(Democracy::fast_track(Origin::signed(5), h, 1, 0), BadOrigin); - assert_noop!( - Democracy::fast_track(Origin::signed(6), h, 1, 0), - Error::::InstantNotAllowed - ); - INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true); - assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0)); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 1, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SimpleMajority, - delay: 0, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn fast_track_referendum_fails_when_no_simple_majority() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), - Error::::NotSimpleMajority - ); - }); -} diff --git a/frame/referenda/src/tests/lock_voting.rs b/frame/referenda/src/tests/lock_voting.rs deleted file mode 100644 index 8b80b39c14aab..0000000000000 --- a/frame/referenda/src/tests/lock_voting.rs +++ /dev/null @@ -1,377 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for functionality concerning locking and lock-voting. - -use super::*; -use std::convert::TryFrom; - -fn aye(x: u8, balance: u64) -> AccountVote { - AccountVote::Standard { - vote: Vote { aye: true, conviction: Conviction::try_from(x).unwrap() }, - balance, - } -} - -fn nay(x: u8, balance: u64) -> AccountVote { - AccountVote::Standard { - vote: Vote { aye: false, conviction: Conviction::try_from(x).unwrap() }, - balance, - } -} - -fn the_lock(amount: u64) -> BalanceLock { - BalanceLock { id: DEMOCRACY_ID, amount, reasons: pallet_balances::Reasons::Misc } -} - -#[test] -fn lock_voting_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); - assert_ok!(Democracy::vote(Origin::signed(4), r, aye(2, 40))); - assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); - assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); - - // All balances are currently locked. - for i in 1..=5 { - assert_eq!(Balances::locks(i), vec![the_lock(i * 10)]); - } - - fast_forward_to(2); - - // Referendum passed; 1 and 5 didn't get their way and can now reap and unlock. - assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); - assert_ok!(Democracy::unlock(Origin::signed(1), 1)); - // Anyone can reap and unlock anyone else's in this context. - assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 5, r)); - assert_ok!(Democracy::unlock(Origin::signed(2), 5)); - - // 2, 3, 4 got their way with the vote, so they cannot be reaped by others. - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 2, r), - Error::::NoPermission - ); - // However, they can be unvoted by the owner, though it will make no difference to the lock. - assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); - assert_ok!(Democracy::unlock(Origin::signed(2), 2)); - - assert_eq!(Balances::locks(1), vec![]); - assert_eq!(Balances::locks(2), vec![the_lock(20)]); - assert_eq!(Balances::locks(3), vec![the_lock(30)]); - assert_eq!(Balances::locks(4), vec![the_lock(40)]); - assert_eq!(Balances::locks(5), vec![]); - assert_eq!(Balances::free_balance(42), 2); - - fast_forward_to(7); - // No change yet... - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 4, r), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(1), 4)); - assert_eq!(Balances::locks(4), vec![the_lock(40)]); - fast_forward_to(8); - // 4 should now be able to reap and unlock - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 4, r)); - assert_ok!(Democracy::unlock(Origin::signed(1), 4)); - assert_eq!(Balances::locks(4), vec![]); - - fast_forward_to(13); - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 3, r), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(1), 3)); - assert_eq!(Balances::locks(3), vec![the_lock(30)]); - fast_forward_to(14); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 3, r)); - assert_ok!(Democracy::unlock(Origin::signed(1), 3)); - assert_eq!(Balances::locks(3), vec![]); - - // 2 doesn't need to reap_vote here because it was already done before. - fast_forward_to(25); - assert_ok!(Democracy::unlock(Origin::signed(1), 2)); - assert_eq!(Balances::locks(2), vec![the_lock(20)]); - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(1), 2)); - assert_eq!(Balances::locks(2), vec![]); - }); -} - -#[test] -fn no_locks_without_conviction_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(0, 10))); - - fast_forward_to(2); - - assert_eq!(Balances::free_balance(42), 2); - assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 1, r)); - assert_ok!(Democracy::unlock(Origin::signed(2), 1)); - assert_eq!(Balances::locks(1), vec![]); - }); -} - -#[test] -fn lock_voting_should_work_with_delegation() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); - assert_ok!(Democracy::delegate(Origin::signed(4), 2, Conviction::Locked2x, 40)); - assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); - - assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -fn setup_three_referenda() -> (u32, u32, u32) { - System::set_block_number(0); - let r1 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r1, aye(4, 10))); - - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r2, aye(3, 20))); - - let r3 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r3, aye(2, 50))); - - fast_forward_to(2); - - (r1, r2, r3) -} - -#[test] -fn prior_lockvotes_should_be_enforced() { - new_test_ext().execute_with(|| { - let r = setup_three_referenda(); - // r.0 locked 10 until 2 + 8 * 3 = #26 - // r.1 locked 20 until 2 + 4 * 3 = #14 - // r.2 locked 50 until 2 + 2 * 3 = #8 - - fast_forward_to(7); - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 5, r.2), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(50)]); - fast_forward_to(8); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(20)]); - fast_forward_to(13); - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 5, r.1), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(20)]); - fast_forward_to(14); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(10)]); - fast_forward_to(25); - assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 5, r.0), - Error::::NoPermission - ); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(10)]); - fast_forward_to(26); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn single_consolidation_of_lockvotes_should_work_as_before() { - new_test_ext().execute_with(|| { - let r = setup_three_referenda(); - // r.0 locked 10 until 2 + 8 * 3 = #26 - // r.1 locked 20 until 2 + 4 * 3 = #14 - // r.2 locked 50 until 2 + 2 * 3 = #8 - - fast_forward_to(7); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(50)]); - fast_forward_to(8); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(20)]); - - fast_forward_to(13); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(20)]); - fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(10)]); - - fast_forward_to(25); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![the_lock(10)]); - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn multi_consolidation_of_lockvotes_should_be_conservative() { - new_test_ext().execute_with(|| { - let r = setup_three_referenda(); - // r.0 locked 10 until 2 + 8 * 3 = #26 - // r.1 locked 20 until 2 + 4 * 3 = #14 - // r.2 locked 50 until 2 + 2 * 3 = #8 - - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); - - fast_forward_to(8); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 20); - - fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 10); - - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn locks_should_persist_from_voting_to_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r, aye(4, 10))); - fast_forward_to(2); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); - // locked 10 until #26. - - assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked3x, 20)); - // locked 20. - assert!(Balances::locks(5)[0].amount == 20); - - assert_ok!(Democracy::undelegate(Origin::signed(5))); - // locked 20 until #14 - - fast_forward_to(13); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount == 20); - - fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 10); - - fast_forward_to(25); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 10); - - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn locks_should_persist_from_delegation_to_voting() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked5x, 5)); - assert_ok!(Democracy::undelegate(Origin::signed(5))); - // locked 5 until 16 * 3 = #48 - - let r = setup_three_referenda(); - // r.0 locked 10 until 2 + 8 * 3 = #26 - // r.1 locked 20 until 2 + 4 * 3 = #14 - // r.2 locked 50 until 2 + 2 * 3 = #8 - - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); - - fast_forward_to(8); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 20); - - fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 10); - - fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert!(Balances::locks(5)[0].amount >= 5); - - fast_forward_to(48); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} diff --git a/frame/referenda/src/tests/preimage.rs b/frame/referenda/src/tests/preimage.rs deleted file mode 100644 index 6d478fcaa68c7..0000000000000 --- a/frame/referenda/src/tests/preimage.rs +++ /dev/null @@ -1,219 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The preimage tests. - -use super::*; - -#[test] -fn missing_preimage_should_fail() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn preimage_deposit_should_be_required_and_returned() { - new_test_ext_execute_with_cond(|operational| { - // fee of 100 is too much. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); - assert_noop!( - if operational { - Democracy::note_preimage_operational(Origin::signed(6), vec![0; 500]) - } else { - Democracy::note_preimage(Origin::signed(6), vec![0; 500]) - }, - BalancesError::::InsufficientBalance, - ); - // fee of 1 is reasonable. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn preimage_deposit_should_be_reapable_earlier_by_owner() { - new_test_ext_execute_with_cond(|operational| { - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(if operational { - Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) - } else { - Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) - }); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2), u32::MAX), - Error::::TooEarly - ); - next_block(); - assert_ok!(Democracy::reap_preimage( - Origin::signed(6), - set_balance_proposal_hash(2), - u32::MAX - )); - - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::reserved_balance(6), 0); - }); -} - -#[test] -fn preimage_deposit_should_be_reapable() { - new_test_ext_execute_with_cond(|operational| { - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), - Error::::PreimageMissing - ); - - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(if operational { - Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) - } else { - Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) - }); - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), - Error::::TooEarly - ); - - next_block(); - assert_ok!(Democracy::reap_preimage( - Origin::signed(5), - set_balance_proposal_hash(2), - u32::MAX - )); - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 48); - assert_eq!(Balances::free_balance(5), 62); - }); -} - -#[test] -fn noting_imminent_preimage_for_free_should_work() { - new_test_ext_execute_with_cond(|operational| { - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 1, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_noop!( - if operational { - Democracy::note_imminent_preimage_operational( - Origin::signed(6), - set_balance_proposal(2), - ) - } else { - Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)) - }, - Error::::NotImminent - ); - - next_block(); - - // Now we're in the dispatch queue it's all good. - assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); - - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn reaping_imminent_preimage_should_fail() { - new_test_ext().execute_with(|| { - let h = set_balance_proposal_hash_and_note(2); - let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - next_block(); - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(6), h, u32::MAX), - Error::::Imminent - ); - }); -} - -#[test] -fn note_imminent_preimage_can_only_be_successful_once() { - new_test_ext().execute_with(|| { - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 1, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - next_block(); - - // First time works - assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); - - // Second time fails - assert_noop!( - Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)), - Error::::DuplicatePreimage - ); - - // Fails from any user - assert_noop!( - Democracy::note_imminent_preimage(Origin::signed(5), set_balance_proposal(2)), - Error::::DuplicatePreimage - ); - }); -} diff --git a/frame/referenda/src/tests/public_proposals.rs b/frame/referenda/src/tests/public_proposals.rs deleted file mode 100644 index 34713c3e15725..0000000000000 --- a/frame/referenda/src/tests/public_proposals.rs +++ /dev/null @@ -1,149 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for the public proposal queue. - -use super::*; - -#[test] -fn backing_for_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(1, 3, 3)); - assert_eq!(Democracy::backing_for(0), Some(2)); - assert_eq!(Democracy::backing_for(1), Some(4)); - assert_eq!(Democracy::backing_for(2), Some(3)); - }); -} - -#[test] -fn deposit_for_proposals_should_be_taken() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!(Balances::free_balance(2), 15); - assert_eq!(Balances::free_balance(5), 35); - }); -} - -#[test] -fn deposit_for_proposals_should_be_returned() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - fast_forward_to(3); - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::free_balance(2), 20); - assert_eq!(Balances::free_balance(5), 50); - }); -} - -#[test] -fn proposal_with_deposit_below_minimum_should_not_work() { - new_test_ext().execute_with(|| { - assert_noop!(propose_set_balance(1, 2, 0), Error::::ValueLow); - }); -} - -#[test] -fn poor_proposer_should_not_work() { - new_test_ext().execute_with(|| { - assert_noop!(propose_set_balance(1, 2, 11), BalancesError::::InsufficientBalance); - }); -} - -#[test] -fn poor_seconder_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(2, 2, 11)); - assert_noop!( - Democracy::second(Origin::signed(1), 0, u32::MAX), - BalancesError::::InsufficientBalance - ); - }); -} - -#[test] -fn invalid_seconds_upper_bound_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_noop!(Democracy::second(Origin::signed(2), 0, 0), Error::::WrongUpperBound); - }); -} - -#[test] -fn cancel_proposal_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_noop!(Democracy::cancel_proposal(Origin::signed(1), 0), BadOrigin); - assert_ok!(Democracy::cancel_proposal(Origin::root(), 0)); - assert_eq!(Democracy::backing_for(0), None); - assert_eq!(Democracy::backing_for(1), Some(4)); - }); -} - -#[test] -fn blacklisting_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let hash = set_balance_proposal_hash(2); - - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - - assert_noop!(Democracy::blacklist(Origin::signed(1), hash.clone(), None), BadOrigin); - assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); - - assert_eq!(Democracy::backing_for(0), None); - assert_eq!(Democracy::backing_for(1), Some(4)); - - assert_noop!(propose_set_balance_and_note(1, 2, 2), Error::::ProposalBlacklisted); - - fast_forward_to(2); - - let hash = set_balance_proposal_hash(4); - assert_ok!(Democracy::referendum_status(0)); - assert_ok!(Democracy::blacklist(Origin::root(), hash, Some(0))); - assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); - }); -} - -#[test] -fn runners_up_should_come_after() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(1, 3, 3)); - fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); - fast_forward_to(4); - assert_ok!(Democracy::vote(Origin::signed(1), 1, aye(1))); - fast_forward_to(6); - assert_ok!(Democracy::vote(Origin::signed(1), 2, aye(1))); - }); -} diff --git a/frame/referenda/src/tests/scheduling.rs b/frame/referenda/src/tests/scheduling.rs deleted file mode 100644 index 5c857a632b97b..0000000000000 --- a/frame/referenda/src/tests/scheduling.rs +++ /dev/null @@ -1,156 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for functionality concerning normal starting, ending and enacting of referenda. - -use super::*; - -#[test] -fn simple_passing_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); - assert_eq!(Democracy::lowest_unbaked(), 0); - next_block(); - next_block(); - assert_eq!(Democracy::lowest_unbaked(), 1); - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn simple_failing_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); - assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 10 }); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn ooo_inject_referendums_should_work() { - new_test_ext().execute_with(|| { - let r1 = Democracy::inject_referendum( - 3, - set_balance_proposal_hash_and_note(3), - VoteThreshold::SuperMajorityApprove, - 0, - ); - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - - assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); - assert_eq!(tally(r2), Tally { ayes: 1, nays: 0, turnout: 10 }); - - next_block(); - assert_eq!(Balances::free_balance(42), 2); - - assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); - assert_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 10 }); - - next_block(); - assert_eq!(Balances::free_balance(42), 3); - }); -} - -#[test] -fn delayed_enactment_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 1, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); - assert_ok!(Democracy::vote(Origin::signed(4), r, aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, aye(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, aye(6))); - - assert_eq!(tally(r), Tally { ayes: 21, nays: 0, turnout: 210 }); - - next_block(); - assert_eq!(Balances::free_balance(42), 0); - - next_block(); - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn lowest_unbaked_should_be_sensible() { - new_test_ext().execute_with(|| { - let r1 = Democracy::inject_referendum( - 3, - set_balance_proposal_hash_and_note(1), - VoteThreshold::SuperMajorityApprove, - 0, - ); - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - let r3 = Democracy::inject_referendum( - 10, - set_balance_proposal_hash_and_note(3), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); - assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); - // r3 is canceled - assert_ok!(Democracy::cancel_referendum(Origin::root(), r3.into())); - assert_eq!(Democracy::lowest_unbaked(), 0); - - next_block(); - - // r2 is approved - assert_eq!(Balances::free_balance(42), 2); - assert_eq!(Democracy::lowest_unbaked(), 0); - - next_block(); - - // r1 is approved - assert_eq!(Balances::free_balance(42), 1); - assert_eq!(Democracy::lowest_unbaked(), 3); - assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); - }); -} diff --git a/frame/referenda/src/tests/voting.rs b/frame/referenda/src/tests/voting.rs deleted file mode 100644 index e035c2d46c1b6..0000000000000 --- a/frame/referenda/src/tests/voting.rs +++ /dev/null @@ -1,169 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! The tests for normal voting functionality. - -use super::*; - -#[test] -fn overvoting_should_fail() { - new_test_ext().execute_with(|| { - let r = begin_referendum(); - assert_noop!( - Democracy::vote(Origin::signed(1), r, aye(2)), - Error::::InsufficientFunds - ); - }); -} - -#[test] -fn split_voting_should_work() { - new_test_ext().execute_with(|| { - let r = begin_referendum(); - let v = AccountVote::Split { aye: 40, nay: 20 }; - assert_noop!(Democracy::vote(Origin::signed(5), r, v), Error::::InsufficientFunds); - let v = AccountVote::Split { aye: 30, nay: 20 }; - assert_ok!(Democracy::vote(Origin::signed(5), r, v)); - - assert_eq!(tally(r), Tally { ayes: 3, nays: 2, turnout: 50 }); - }); -} - -#[test] -fn split_vote_cancellation_should_work() { - new_test_ext().execute_with(|| { - let r = begin_referendum(); - let v = AccountVote::Split { aye: 30, nay: 20 }; - assert_ok!(Democracy::vote(Origin::signed(5), r, v)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); - assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); - assert_eq!(Balances::locks(5), vec![]); - }); -} - -#[test] -fn single_proposal_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - let r = 0; - assert!(Democracy::referendum_info(r).is_none()); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_eq!(Democracy::referendum_count(), 1); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 1, nays: 0, turnout: 10 }, - }) - ); - - fast_forward_to(3); - - // referendum still running - assert_ok!(Democracy::referendum_status(0)); - - // referendum runs during 2 and 3, ends @ start of 4. - fast_forward_to(4); - - assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); - assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); - - // referendum passes and wait another two blocks for enactment. - fast_forward_to(6); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn controversial_voting_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - - assert_ok!(Democracy::vote(Origin::signed(1), r, big_aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, big_nay(2))); - assert_ok!(Democracy::vote(Origin::signed(3), r, big_nay(3))); - assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); - - assert_eq!(tally(r), Tally { ayes: 110, nays: 100, turnout: 210 }); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn controversial_low_turnout_voting_should_work() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); - - assert_eq!(tally(r), Tally { ayes: 60, nays: 50, turnout: 110 }); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn passing_low_turnout_voting_should_work() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_eq!(Balances::total_issuance(), 210); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); - assert_eq!(tally(r), Tally { ayes: 100, nays: 50, turnout: 150 }); - - next_block(); - next_block(); - assert_eq!(Balances::free_balance(42), 2); - }); -} diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 2b7b7f1326cf5..e8c67e1e4ad96 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -18,7 +18,7 @@ //! Miscellaneous additional datatypes. use super::*; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, EncodeLike}; use frame_support::Parameter; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; @@ -29,12 +29,12 @@ pub type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; pub type CallOf = ::Call; -pub type OriginOf = ::Origin; pub type VotesOf = ::Votes; pub type TallyOf = ::Tally; +pub type PalletsOriginOf = <::Origin as OriginTrait>::PalletsOrigin; pub type ReferendumInfoOf = ReferendumInfo< TrackIdOf, - OriginOf, + PalletsOriginOf, ::BlockNumber, ::Hash, BalanceOf, @@ -44,7 +44,7 @@ pub type ReferendumInfoOf = ReferendumInfo< >; pub type ReferendumStatusOf = ReferendumStatus< TrackIdOf, - OriginOf, + PalletsOriginOf, ::BlockNumber, ::Hash, BalanceOf, @@ -137,7 +137,7 @@ pub struct TrackInfo { } pub trait TracksInfo { - type Id: Copy + Eq + Codec + TypeInfo + Parameter + Ord + PartialOrd; + type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync; type Origin; fn tracks() -> &'static [(Self::Id, TrackInfo)]; fn track_for(id: &Self::Origin) -> Result; @@ -145,7 +145,7 @@ pub trait TracksInfo { /// Indication of either a specific moment or a delay from a implicitly defined moment. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub enum AtOrAfter { +pub enum AtOrAfter { /// 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 @@ -154,7 +154,7 @@ pub enum AtOrAfter { After(Moment), } -impl AtOrAfter { +impl AtOrAfter { pub fn evaluate(&self, since: Moment) -> Moment { match &self { Self::At(m) => *m, @@ -165,7 +165,16 @@ impl AtOrAfter { /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct ReferendumStatus { +pub struct ReferendumStatus< + TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Moment: Parameter + Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone +> { /// The track of this referendum. pub(crate) track: TrackId, /// The origin for this referendum. @@ -193,7 +202,16 @@ pub struct ReferendumStatus, } -impl +impl< + TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Moment: Parameter + Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + AtLeast32BitUnsigned + Copy + EncodeLike, + Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone +> ReferendumStatus { pub fn begin_deciding(&mut self, now: Moment, decision_period: Moment) { @@ -208,7 +226,16 @@ impl { +pub enum ReferendumInfo< + TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Moment: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone +> { /// Referendum has been submitted and is being voted on. Ongoing(ReferendumStatus), /// Referendum finished with approval. Submission deposit is held. @@ -223,7 +250,16 @@ pub enum ReferendumInfo +impl< + TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Moment: Parameter + Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone +> ReferendumInfo { pub fn take_decision_deposit(&mut self) -> Option> { diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index a492bc12f6a38..fbfd9476ab9f3 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -32,8 +32,9 @@ pub use crate::{ ClassifyDispatch, DispatchInfo, GetDispatchInfo, PaysFee, PostDispatchInfo, TransactionPriority, WeighData, Weight, WithPostDispatchInfo, }, + scale_info::TypeInfo, }; -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 { // https://github.com/rust-lang/rust/issues/51331 pub type CallableCallFor = >::Call; +/// Origin for the System pallet. +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo)] +pub enum RawOrigin { + /// 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 From> for RawOrigin { + fn from(s: Option) -> RawOrigin { + 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. @@ -2638,7 +2661,7 @@ mod tests { } } - #[derive(scale_info::TypeInfo)] + #[derive(Eq, PartialEq, Clone, crate::RuntimeDebug, scale_info::TypeInfo)] pub struct TraitImpl {} impl Config for TraitImpl {} @@ -2679,6 +2702,7 @@ mod tests { } } + #[derive(TypeInfo, crate::RuntimeDebug, Eq, PartialEq, Clone, Encode, Decode)] pub struct OuterOrigin; impl crate::traits::OriginTrait for OuterOrigin { diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 92b832ba32961..a264b5b2905e2 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -17,8 +17,8 @@ //! Traits for dealing with dispatching calls and the origin from which they are dispatched. -use crate::dispatch::DispatchResultWithPostInfo; -use sp_runtime::traits::BadOrigin; +use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; +use sp_runtime::traits::{BadOrigin, Member}; /// Some sort of check on the origin is performed by this object. pub trait EnsureOrigin { @@ -56,7 +56,7 @@ pub trait OriginTrait: Sized { type Call; /// The caller origin, overarching type of all pallets origins. - type PalletsOrigin; + type PalletsOrigin: Parameter + Member + Into + From>; /// The AccountId used across the system. type AccountId; diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 6b63cf861516b..28b0d1f54b7a6 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -100,7 +100,7 @@ pub trait VoteTally { pub enum PollStatus { None, Ongoing(Tally), - Done(Moment, bool), + Completed(Moment, bool), } impl PollStatus { diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index b0f385914ac1a..85f35d703f59d 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -122,6 +122,7 @@ pub use extensions::{ // Backward compatible re-export. pub use extensions::check_mortality::CheckMortality as CheckEra; pub use weights::WeightInfo; +pub use frame_support::dispatch::RawOrigin; /// Compute the trie root of a list of extrinsics. pub fn extrinsics_root(extrinsics: &[E]) -> H::Output { @@ -793,28 +794,6 @@ pub struct EventRecord { pub topics: Vec, } -/// Origin for the System pallet. -#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo)] -pub enum RawOrigin { - /// 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 From> for RawOrigin { - fn from(s: Option) -> RawOrigin { - 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")] From cddd73769ab9fd28f32cf606decfa8f1855b952e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 11 Nov 2021 15:47:02 +0100 Subject: [PATCH 10/66] Add missing file --- frame/referenda/src/mock.rs | 309 ++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 frame/referenda/src/mock.rs diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs new file mode 100644 index 0000000000000..64ba2b5b47113 --- /dev/null +++ b/frame/referenda/src/mock.rs @@ -0,0 +1,309 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The crate's tests. + +use super::*; +use crate as pallet_referenda; +use codec::{Encode, Decode}; +use frame_support::{ + assert_ok, ord_parameter_types, parameter_types, + traits::{Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers, OriginTrait}, + weights::Weight, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +const MAX_PROPOSALS: u32 = 100; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, + Referenda: pallet_referenda::{Pallet, Call, Storage, Config, Event}, + } +); + +// Test that a fitlered call can be dispatched. +pub struct BaseFilter; +impl Contains 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; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; +} +impl pallet_scheduler::Config for Test { + type Event = Event; + type Origin = Origin; + type PalletsOrigin = OriginCaller; + type Call = Call; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureRoot; + type MaxScheduledPerBlock = (); + type WeightInfo = (); + type OriginPrivilegeCmp = EqualPrivilegeOnly; +} +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 const LaunchPeriod: u64 = 2; + pub const VotingPeriod: u64 = 2; + pub const FastTrackVotingPeriod: u64 = 2; + pub const MinimumDeposit: u64 = 1; + pub const AlarmInterval: u64 = 2; + pub const VoteLockingPeriod: u64 = 3; + pub const CooloffPeriod: u64 = 2; + pub const MaxVotes: u32 = 100; + pub const MaxProposals: u32 = MAX_PROPOSALS; + pub static PreimageByteDeposit: u64 = 0; + pub static InstantAllowed: bool = false; + pub const SubmissionDeposit: u64 = 2; + pub const MaxQueued: u32 = 2; + pub const UndecidingTimeout: u64 = 10; +} +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 for OneToFive { + fn sorted_members() -> Vec { + vec![1, 2, 3, 4, 5] + } + #[cfg(feature = "runtime-benchmarks")] + fn add(_m: &u64) {} +} + +pub struct TestTracksInfo; +impl TracksInfo for TestTracksInfo { + type Id = u8; + type Origin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, TrackInfo)] { + static DATA: [(u8, TrackInfo); 2] = [ + (0u8, TrackInfo { + name: "root", + max_deciding: 1, + decision_deposit: 10, + prepare_period: 4, + decision_period: 4, + confirm_period: 4, + 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: 2, + 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 { + use sp_std::convert::TryFrom; + 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; + type CancelOrigin = EnsureSignedBy; + type KillOrigin = EnsureRoot; + 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::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + } + .assimilate_storage(&mut t) + .unwrap(); + pallet_referenda::GenesisConfig::::default() + .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`. +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)] +pub struct Tally { + pub ayes: u32, + pub nays: u32, +} + +impl VoteTally 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) + } +} + +pub fn set_balance_proposal(value: u64) -> Vec { + 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 { + use sp_core::Hasher; + BlakeTwo256::hash(&set_balance_proposal(value)[..]) +} + +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 fast_forward_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +pub fn begin_referendum() -> ReferendumIndex { + System::set_block_number(0); + assert_ok!(propose_set_balance(1, 2, 1)); + fast_forward_to(2); + 0 +} + +pub fn tally(r: ReferendumIndex) -> Tally { + Referenda::ensure_ongoing(r).unwrap().tally +} From 9d1ffb67144d2f5accfc396cca495b2af058b07f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 12 Nov 2021 19:11:10 +0100 Subject: [PATCH 11/66] Basic lifecycle test --- frame/referenda/src/lib.rs | 103 ++++++++++----------------- frame/referenda/src/mock.rs | 24 +++++-- frame/referenda/src/tests.rs | 24 ++++++- frame/referenda/src/types.rs | 30 +++++--- frame/support/src/traits.rs | 2 +- frame/support/src/traits/schedule.rs | 2 +- frame/support/src/traits/voting.rs | 2 +- 7 files changed, 102 insertions(+), 85 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 92005476304c9..b85885ea5cc5d 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -28,9 +28,9 @@ use codec::{Encode, Codec}; use frame_support::{ ensure, BoundedVec, traits::{ - schedule::{DispatchTime, Named as ScheduleNamed}, + schedule::{DispatchTime, Anon as ScheduleAnon, Named as ScheduleNamed}, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, - Referenda, VoteTally, PollStatus, OriginTrait, + Polls, VoteTally, PollStatus, OriginTrait, }, }; use scale_info::TypeInfo; @@ -47,7 +47,7 @@ pub use types::{ ReferendumInfo, ReferendumStatus, TrackInfo, TracksInfo, Curve, DecidingStatus, Deposit, AtOrAfter, BalanceOf, NegativeImbalanceOf, CallOf, VotesOf, TallyOf, ReferendumInfoOf, ReferendumStatusOf, DecidingStatusOf, TrackInfoOf, TrackIdOf, InsertSorted, ReferendumIndex, - PalletsOriginOf, + PalletsOriginOf, ScheduleAddressOf, }; pub use weights::WeightInfo; @@ -80,7 +80,8 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// The Scheduler. - type Scheduler: ScheduleNamed, PalletsOriginOf>; + type Scheduler: ScheduleAnon, PalletsOriginOf> + + ScheduleNamed, PalletsOriginOf>; /// Currency type for this pallet. type Currency: ReservableCurrency + LockableCurrency; @@ -310,6 +311,7 @@ pub mod pallet { deciding: None, tally: Default::default(), ayes_in_queue: None, + alarm: None, }; ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); @@ -353,7 +355,9 @@ pub mod pallet { T::CancelOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; let track = Self::track(status.track).ok_or(Error::::BadTrack)?; - Self::cancel_referendum_alarm(index); + if let Some(last_alarm) = status.alarm { + let _ = T::Scheduler::cancel(last_alarm); + } Self::note_one_fewer_deciding(status.track, track); Self::deposit_event(Event::::Cancelled { index, tally: status.tally }); let info = ReferendumInfo::Cancelled( @@ -373,7 +377,9 @@ pub mod pallet { T::CancelOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; let track = Self::track(status.track).ok_or(Error::::BadTrack)?; - Self::cancel_referendum_alarm(index); + if let Some(last_alarm) = status.alarm { + let _ = T::Scheduler::cancel(last_alarm); + } Self::note_one_fewer_deciding(status.track, track); Self::deposit_event(Event::::Killed { index, tally: status.tally }); Self::slash_deposit(Some(status.submission_deposit)); @@ -408,7 +414,7 @@ pub mod pallet { } } -impl Referenda for Pallet { +impl Polls for Pallet { type Index = ReferendumIndex; type Votes = VotesOf; type Moment = T::BlockNumber; @@ -486,57 +492,19 @@ impl Pallet { debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed"); } - /// Sets a referendum's alarm call. Returns `true` if there was not already an alarm call in - /// place for the same time. - fn set_referendum_alarm(index: ReferendumIndex, when: T::BlockNumber) -> bool { - let scheduler_id = (ASSEMBLY_ID, "referendum", index).encode(); - // NOTE: This only works because we know that that this ID will only ever be used for this - // call. - Self::set_alarm(scheduler_id, Call::nudge_referendum { index }, when) - } - - /// Cancels a referendum's alarm call. Returns `true` if there was an alarm scheduled. - fn cancel_referendum_alarm(index: ReferendumIndex) -> bool { - let id = (ASSEMBLY_ID, "referendum", index).encode(); - T::Scheduler::cancel_named(id).is_ok() - } - - /// Sets a track's alarm call. Returns `true` if there was not already an alarm call in place - /// for the same time. - #[allow(dead_code)] - fn set_track_alarm(track: TrackIdOf, when: T::BlockNumber) -> bool { - let scheduler_id = (ASSEMBLY_ID, "track", track).encode(); - // NOTE: This only works because we know that that this ID will only ever be used for this - // call. - Self::set_alarm(scheduler_id, Call::nudge_track { track }, when) - } - - /// Set or reset an alarm for a given ID. This assumes that an ID always uses the same - /// call. If it doesn't then this won't work! - /// - /// Returns `false` if there is no change. - fn set_alarm(id: Vec, call: impl Into>, when: T::BlockNumber) -> bool { - let alarm_interval = T::AlarmInterval::get(); - let when = (when / alarm_interval + One::one()) * alarm_interval; - if let Ok(t) = T::Scheduler::next_dispatch_time(id.clone()) { - if t == when { - return false - } - let ok = T::Scheduler::reschedule_named(id, DispatchTime::At(when)).is_ok(); - debug_assert!(ok, "Unable to reschedule an extant referendum?!"); - } else { - let _ = T::Scheduler::cancel_named(id.clone()); - let ok = T::Scheduler::schedule_named( - id, - DispatchTime::At(when), - None, - 128u8, - frame_system::RawOrigin::Root.into(), - call.into(), - ).is_ok(); - debug_assert!(ok, "Unable to schedule a new referendum?!"); - } - true + /// Set an alarm. + fn set_alarm(call: impl Into>, when: T::BlockNumber) -> Option> { + let alarm_interval = T::AlarmInterval::get().max(One::one()); + let when = (when + alarm_interval - One::one()) / alarm_interval * alarm_interval; + let maybe_address = T::Scheduler::schedule( + DispatchTime::At(when), + None, + 128u8, + frame_system::RawOrigin::Root.into(), + call.into(), + ).ok(); + debug_assert!(maybe_address.is_some(), "Unable to schedule a new referendum at #{} (now: #{})?!", when, frame_system::Pallet::::block_number()); + maybe_address } fn ready_for_deciding( @@ -654,6 +622,9 @@ impl Pallet { mut status: ReferendumStatusOf, ) -> (ReferendumInfoOf, bool) { let mut dirty = false; + if let Some(last_alarm) = status.alarm.take() { + let _ = T::Scheduler::cancel(last_alarm); + } // Should it begin being decided? let track = match Self::track(status.track) { Some(x) => x, @@ -707,7 +678,6 @@ impl Pallet { if is_passing { if deciding.confirming.map_or(false, |c| now >= c) { // Passed! - Self::cancel_referendum_alarm(index); Self::note_one_fewer_deciding(status.track, track); let (desired, call_hash) = (status.enactment, status.proposal_hash); Self::schedule_enactment(index, track, desired, status.origin, call_hash); @@ -722,7 +692,6 @@ impl Pallet { } else { if now >= deciding.ending { // Failed! - Self::cancel_referendum_alarm(index); Self::note_one_fewer_deciding(status.track, track); Self::deposit_event(Event::::Rejected { index, tally: status.tally }); return (ReferendumInfo::Rejected(now, status.submission_deposit, status.decision_deposit), true) @@ -730,16 +699,15 @@ impl Pallet { // Cannot be confirming dirty = deciding.confirming.is_some(); deciding.confirming = None; - alarm = Self::decision_time(&deciding, &status.tally, track); + let decision_time = Self::decision_time(&deciding, &status.tally, track); + alarm = decision_time; } } else if now >= timeout { // Too long without being decided - end it. - Self::cancel_referendum_alarm(index); Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); return (ReferendumInfo::TimedOut(now, status.submission_deposit, status.decision_deposit), true) } - - Self::set_referendum_alarm(index, alarm); + status.alarm = Self::set_alarm(Call::nudge_referendum { index }, alarm); (ReferendumInfo::Ongoing(status), dirty) } @@ -751,9 +719,10 @@ impl Pallet { // Set alarm to the point where the current voting would make it pass. let approval = tally.approval(); let turnout = tally.turnout(); - let until_approval = track.min_approval.delay(approval) * deciding.period; - let until_turnout = track.min_turnout.delay(turnout) * deciding.period; - deciding.ending.min(until_turnout.max(until_approval)) + let until_approval = track.min_approval.delay(approval); + let until_turnout = track.min_turnout.delay(turnout); + let offset = until_turnout.max(until_approval); + deciding.ending.saturating_sub(deciding.period).saturating_add(offset * deciding.period) } /// Reserve a deposit and return the `Deposit` instance. diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 64ba2b5b47113..7f660f5708fbb 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -47,7 +47,7 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, - Referenda: pallet_referenda::{Pallet, Call, Storage, Config, Event}, + Referenda: pallet_referenda::{Pallet, Call, Storage, Event}, } ); @@ -123,13 +123,13 @@ parameter_types! { pub const VotingPeriod: u64 = 2; pub const FastTrackVotingPeriod: u64 = 2; pub const MinimumDeposit: u64 = 1; - pub const AlarmInterval: u64 = 2; pub const VoteLockingPeriod: u64 = 3; pub const CooloffPeriod: u64 = 2; pub const MaxVotes: u32 = 100; pub const MaxProposals: u32 = MAX_PROPOSALS; pub static PreimageByteDeposit: u64 = 0; pub static InstantAllowed: bool = false; + pub static AlarmInterval: u64 = 1; pub const SubmissionDeposit: u64 = 2; pub const MaxQueued: u32 = 2; pub const UndecidingTimeout: u64 = 10; @@ -163,7 +163,7 @@ impl TracksInfo for TestTracksInfo { decision_deposit: 10, prepare_period: 4, decision_period: 4, - confirm_period: 4, + confirm_period: 2, min_enactment_period: 4, min_approval: Curve::LinearDecreasing { begin: Perbill::from_percent(100), @@ -180,7 +180,7 @@ impl TracksInfo for TestTracksInfo { decision_deposit: 1, prepare_period: 2, decision_period: 2, - confirm_period: 2, + confirm_period: 1, min_enactment_period: 2, min_approval: Curve::LinearDecreasing { begin: Perbill::from_percent(55), @@ -242,6 +242,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { } /// 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)); @@ -277,6 +278,7 @@ pub fn set_balance_proposal_hash(value: u64) -> H256 { BlakeTwo256::hash(&set_balance_proposal(value)[..]) } +#[allow(dead_code)] pub fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { Referenda::submit( Origin::signed(who), @@ -291,19 +293,29 @@ pub fn next_block() { Scheduler::on_initialize(System::block_number()); } -pub fn fast_forward_to(n: u64) { +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)); - fast_forward_to(2); + 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) { + >::access_poll(index, |status| { + let tally = status.ensure_ongoing().unwrap(); + tally.ayes = ayes; + tally.nays = nays; + }); +} \ No newline at end of file diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 8d7e05b4c5780..16624467dc7ea 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -18,7 +18,7 @@ //! The crate's tests. use codec::Decode; -use frame_support::traits::Contains; +use frame_support::{assert_ok, traits::Contains, dispatch::RawOrigin}; use super::*; use crate::mock::*; @@ -31,6 +31,28 @@ fn params_should_work() { }); } +#[test] +fn basic_lifecycle_works() { + new_test_ext().execute_with(|| { + assert_ok!(Referenda::submit( + Origin::signed(1), + RawOrigin::Root.into(), + set_balance_proposal_hash(1), + AtOrAfter::At(10), + )); + assert_eq!(ReferendumCount::::get(), 1); + assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); + run_to(6); + // Vote should now be deciding. + assert_eq!(DecidingCount::::get(0), 1); + set_tally(0, 100, 0); + // Vote should now be confirming. + run_to(8); + // Vote should now have ended. + assert_ok!(Referenda::refund_decision_deposit(Origin::signed(2), 0)); + }); +} + #[test] fn set_balance_proposal_is_correctly_filtered_out() { for i in 0..10 { diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index e8c67e1e4ad96..2e5e7c55e109a 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -19,7 +19,7 @@ use super::*; use codec::{Decode, Encode, EncodeLike}; -use frame_support::Parameter; +use frame_support::{Parameter, traits::schedule::Anon}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; @@ -41,6 +41,7 @@ pub type ReferendumInfoOf = ReferendumInfo< VotesOf, TallyOf, ::AccountId, + ScheduleAddressOf, >; pub type ReferendumStatusOf = ReferendumStatus< TrackIdOf, @@ -51,6 +52,7 @@ pub type ReferendumStatusOf = ReferendumStatus< VotesOf, TallyOf, ::AccountId, + ScheduleAddressOf, >; pub type DecidingStatusOf = DecidingStatus< ::BlockNumber, @@ -65,6 +67,13 @@ pub type TrackIdOf = < ::BlockNumber, > >::Id; +pub type ScheduleAddressOf = < + ::Scheduler as Anon< + ::BlockNumber, + CallOf, + PalletsOriginOf, + > +>::Address; /// A referendum index. pub type ReferendumIndex = u32; @@ -173,7 +182,8 @@ pub struct ReferendumStatus< Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, > { /// The track of this referendum. pub(crate) track: TrackId, @@ -200,6 +210,7 @@ pub struct ReferendumStatus< /// Automatic advancement is scheduled when ayes_in_queue is Some value greater than the /// ayes in `tally`. pub(crate) ayes_in_queue: Option, + pub(crate) alarm: Option, } impl< @@ -210,9 +221,10 @@ impl< Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, > - ReferendumStatus + ReferendumStatus { pub fn begin_deciding(&mut self, now: Moment, decision_period: Moment) { self.ayes_in_queue = None; @@ -234,10 +246,11 @@ pub enum ReferendumInfo< Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, > { /// Referendum has been submitted and is being voted on. - Ongoing(ReferendumStatus), + Ongoing(ReferendumStatus), /// Referendum finished with approval. Submission deposit is held. Approved(Moment, Deposit, Option>), /// Referendum finished with rejection. Submission deposit is held. @@ -258,9 +271,10 @@ impl< Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, > - ReferendumInfo + ReferendumInfo { pub fn take_decision_deposit(&mut self) -> Option> { use ReferendumInfo::*; diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index a0526c2fca7ea..cbb9a70506321 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -88,5 +88,5 @@ pub use dispatch::{EnsureOrigin, OriginTrait, UnfilteredDispatchable}; mod voting; pub use voting::{ - CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, Referenda, PollStatus, + CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, Polls, PollStatus, }; diff --git a/frame/support/src/traits/schedule.rs b/frame/support/src/traits/schedule.rs index 19f50a93c0681..d8895d09f134c 100644 --- a/frame/support/src/traits/schedule.rs +++ b/frame/support/src/traits/schedule.rs @@ -52,7 +52,7 @@ pub const LOWEST_PRIORITY: Priority = 255; /// A type that can be used as a scheduler. pub trait Anon { /// 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. /// diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 28b0d1f54b7a6..d4b966000521c 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -112,7 +112,7 @@ impl PollStatus { } } -pub trait Referenda { +pub trait Polls { type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; type Moment; From ab23a80c288216bed6c96b5d96e563dbe0a160ea Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 22 Nov 2021 17:59:46 +0100 Subject: [PATCH 12/66] Add couple of tests --- frame/referenda/src/lib.rs | 5 ++- frame/referenda/src/tests.rs | 81 +++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index b85885ea5cc5d..c5677250035d5 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -298,15 +298,16 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - let index = ReferendumCount::::mutate(|x| { let r = *x; *x += 1; r }); let track = T::Tracks::track_for(&proposal_origin).map_err(|_| Error::::NoTrack)?; + let submission_deposit = Self::take_deposit(who, T::SubmissionDeposit::get())?; + let index = ReferendumCount::::mutate(|x| { let r = *x; *x += 1; r }); let status = ReferendumStatus { track, origin: proposal_origin, proposal_hash: proposal_hash.clone(), enactment: enactment_moment, submitted: frame_system::Pallet::::block_number(), - submission_deposit: Self::take_deposit(who, T::SubmissionDeposit::get())?, + submission_deposit, decision_deposit: None, deciding: None, tally: Default::default(), diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 16624467dc7ea..b4112dc243d37 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -18,7 +18,8 @@ //! The crate's tests. use codec::Decode; -use frame_support::{assert_ok, traits::Contains, dispatch::RawOrigin}; +use frame_support::{assert_ok, assert_noop, traits::Contains, dispatch::RawOrigin}; +use pallet_balances::Error as BalancesError; use super::*; use crate::mock::*; @@ -32,7 +33,7 @@ fn params_should_work() { } #[test] -fn basic_lifecycle_works() { +fn basic_happy_path_works() { new_test_ext().execute_with(|| { assert_ok!(Referenda::submit( Origin::signed(1), @@ -40,6 +41,7 @@ fn basic_lifecycle_works() { set_balance_proposal_hash(1), AtOrAfter::At(10), )); + assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(ReferendumCount::::get(), 1); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); run_to(6); @@ -53,6 +55,81 @@ fn basic_lifecycle_works() { }); } +#[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::::iter().collect::>(); + 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 }, + ayes_in_queue: None, + alarm: Some((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 }, + ayes_in_queue: None, + alarm: Some((3, 0)), + })), + ]); + }); +} + +#[test] +fn submit_errors_work() { + new_test_ext().execute_with(|| { + // No track for Signed origins. + assert_noop!(Referenda::submit( + Origin::signed(1), + RawOrigin::Signed(2).into(), + set_balance_proposal_hash(1), + AtOrAfter::At(10), + ), Error::::NoTrack); + + // No funds for deposit + assert_noop!(Referenda::submit( + Origin::signed(10), + RawOrigin::Root.into(), + set_balance_proposal_hash(1), + AtOrAfter::At(10), + ), BalancesError::::InsufficientBalance); + }); +} + + + #[test] fn set_balance_proposal_is_correctly_filtered_out() { for i in 0..10 { From b09c2927395a3b98dd6ad7121f7fdd35d188f720 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 22 Nov 2021 18:35:16 +0100 Subject: [PATCH 13/66] Another test --- frame/referenda/src/tests.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index b4112dc243d37..3cadb03698939 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -128,7 +128,26 @@ fn submit_errors_work() { }); } +#[test] +fn decision_deposit_errors_work() { + new_test_ext().execute_with(|| { + let e = Error::::NotOngoing; + assert_noop!(Referenda::place_decision_deposit(Origin::signed(2), 0), e); + + assert_ok!(Referenda::submit( + Origin::signed(1), + RawOrigin::Root.into(), + set_balance_proposal_hash(1), + AtOrAfter::At(10), + )); + let e = BalancesError::::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::::HaveDeposit; + assert_noop!(Referenda::place_decision_deposit(Origin::signed(2), 0), e); + }); +} #[test] fn set_balance_proposal_is_correctly_filtered_out() { From a7139e233918fbbc0098d7957c70139da36e508c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 23 Nov 2021 15:36:59 +0100 Subject: [PATCH 14/66] More tests --- Cargo.lock | 1 + frame/referenda/Cargo.toml | 1 + frame/referenda/src/lib.rs | 13 ++++- frame/referenda/src/tests.rs | 105 ++++++++++++++++++++++++++++++++++- frame/referenda/src/types.rs | 8 ++- 5 files changed, 122 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6552d6811ae3c..bdde76c514f33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5876,6 +5876,7 @@ dependencies = [ name = "pallet-referenda" version = "4.0.0-dev" dependencies = [ + "assert_matches", "frame-benchmarking", "frame-support", "frame-system", diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index e53a71da7acd1..71d3bc7ba90fd 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -26,6 +26,7 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } [dev-dependencies] +assert_matches = "1.5" sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index c5677250035d5..b806da7e95f48 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -275,6 +275,12 @@ pub mod pallet { NothingToDo, /// No track exists for the proposal origin. NoTrack, + /// Any deposit cannot be refunded until after the decision is over. + Unfinished, + /// The deposit refunder is not the depositor. + NoPermission, + /// The deposit cannot be refunded since none was made. + NoDeposit, } #[pallet::call] @@ -343,7 +349,10 @@ pub mod pallet { ) -> DispatchResult { ensure_signed_or_root(origin)?; let mut info = ReferendumInfoFor::::get(index).ok_or(Error::::BadReferendum)?; - Self::refund_deposit(info.take_decision_deposit()); + let deposit = info.take_decision_deposit() + .map_err(|_| Error::::Unfinished)? + .ok_or(Error::::NoDeposit)?; + Self::refund_deposit(Some(deposit)); ReferendumInfoFor::::insert(index, info); Ok(()) } @@ -375,7 +384,7 @@ pub mod pallet { origin: OriginFor, index: ReferendumIndex, ) -> DispatchResult { - T::CancelOrigin::ensure_origin(origin)?; + T::KillOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; let track = Self::track(status.track).ok_or(Error::::BadTrack)?; if let Some(last_alarm) = status.alarm { diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 3cadb03698939..1473cbe5b7239 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -18,7 +18,9 @@ //! The crate's tests. use codec::Decode; -use frame_support::{assert_ok, assert_noop, traits::Contains, dispatch::RawOrigin}; +use assert_matches::assert_matches; +use frame_support::{assert_ok, assert_noop, traits::Contains}; +use frame_support::dispatch::{RawOrigin, DispatchError::BadOrigin}; use pallet_balances::Error as BalancesError; use super::*; use crate::mock::*; @@ -149,6 +151,107 @@ fn decision_deposit_errors_work() { }); } +#[test] +fn refund_deposit_works() { + new_test_ext().execute_with(|| { + let e = Error::::BadReferendum; + assert_noop!(Referenda::refund_decision_deposit(Origin::signed(1), 0), e); + + assert_ok!(Referenda::submit( + Origin::signed(1), + RawOrigin::Root.into(), + set_balance_proposal_hash(1), + AtOrAfter::At(10), + )); + let e = Error::::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::::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(|| { + assert_ok!(Referenda::submit( + Origin::signed(1), + RawOrigin::Root.into(), + set_balance_proposal_hash(1), + AtOrAfter::At(10), + )); + assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); + + run_to(10); + assert_ok!(Referenda::cancel(Origin::signed(4), 0)); + assert_ok!(Referenda::refund_decision_deposit(Origin::signed(3), 0)); + assert_matches!( + ReferendumInfoFor::::get(0).unwrap(), + ReferendumInfo::Cancelled(10, Deposit { who: 1, amount: 2 }, None) + ); + }); +} + +#[test] +fn cancel_errors_works() { + 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::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::::NotOngoing); + }); +} + +#[test] +fn kill_works() { + 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::place_decision_deposit(Origin::signed(2), 0)); + + run_to(10); + assert_ok!(Referenda::kill(Origin::root(), 0)); + let e = Error::::NoDeposit; + assert_noop!(Referenda::refund_decision_deposit(Origin::signed(3), 0), e); + assert_matches!( + ReferendumInfoFor::::get(0).unwrap(), + ReferendumInfo::Killed(10) + ); + }); +} + +#[test] +fn kill_errors_works() { + 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::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::::NotOngoing); + }); +} + #[test] fn set_balance_proposal_is_correctly_filtered_out() { for i in 0..10 { diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 2e5e7c55e109a..7395f34493e4e 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -276,12 +276,14 @@ impl< > ReferendumInfo { - pub fn take_decision_deposit(&mut self) -> Option> { + pub fn take_decision_deposit(&mut self) -> Result>, ()> { use ReferendumInfo::*; match self { - Approved(_, _, d) | Rejected(_, _, d) | TimedOut(_, _, d) => d.take(), + Ongoing(x) if x.decision_deposit.is_none() => Ok(None), // Cannot refund deposit if Ongoing as this breaks assumptions. - _ => None, + Ongoing(_) => Err(()), + Approved(_, _, d) | Rejected(_, _, d) | TimedOut(_, _, d) | Cancelled(_, _, d) => Ok(d.take()), + Killed(_) => Ok(None), } } } From d77afbfe242bde66dc5f5f4328679a77080eb37f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 12 Dec 2021 18:32:03 +0100 Subject: [PATCH 15/66] Fixes --- Cargo.lock | 1 + frame/conviction-voting/Cargo.toml | 2 +- frame/conviction-voting/src/lib.rs | 6 +++--- frame/conviction-voting/src/tests.rs | 7 +++++-- frame/referenda/Cargo.toml | 3 ++- frame/referenda/src/mock.rs | 19 +++++++++++++++++-- frame/support/src/dispatch.rs | 22 +++++++--------------- frame/support/src/traits/dispatch.rs | 2 +- frame/support/test/tests/system.rs | 18 +----------------- frame/system/src/lib.rs | 15 --------------- test-utils/runtime/src/lib.rs | 2 +- 11 files changed, 39 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d77f173dd2d20..f634c03a3233d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6003,6 +6003,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", + "pallet-preimage", "pallet-scheduler", "parity-scale-codec", "scale-info", diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index 3d55069203aff..e32f524381ecb 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -26,7 +26,7 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-core = { version = "4.1.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 55a34dbefd4d6..b55060b50cd6b 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -27,7 +27,7 @@ use frame_support::{ensure, traits::{ Currency, Get, LockIdentifier, LockableCurrency, ReservableCurrency, WithdrawReasons, - PollStatus, Referenda, + PollStatus, Polls, }}; use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, Perbill, traits::{ Saturating, Zero, AtLeast32BitUnsigned @@ -61,7 +61,7 @@ type VotingOf = Voting< ReferendumIndexOf, >; type TallyOf = Tally, ::MaxTurnout>; -type ReferendumIndexOf = <::Referenda as Referenda>>::Index; +type ReferendumIndexOf = <::Referenda as Polls>>::Index; #[frame_support::pallet] pub mod pallet { @@ -85,7 +85,7 @@ pub mod pallet { + LockableCurrency; /// The implementation of the logic which conducts referenda. - type Referenda: Referenda< + type Referenda: Polls< TallyOf, Votes = BalanceOf, Moment = Self::BlockNumber, diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index c2d1cff35377b..58f89e31de648 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -21,7 +21,7 @@ use super::*; use crate as pallet_conviction_voting; use frame_support::{ ord_parameter_types, parameter_types, - traits::{Contains, EqualPrivilegeOnly, OnInitialize, SortedMembers}, + traits::{Contains, EqualPrivilegeOnly, OnInitialize, SortedMembers, ConstU32}, weights::Weight, }; use frame_system::EnsureRoot; @@ -92,6 +92,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; @@ -106,6 +107,8 @@ impl pallet_scheduler::Config for Test { type MaxScheduledPerBlock = (); type WeightInfo = (); type OriginPrivilegeCmp = EqualPrivilegeOnly; + type PreimageProvider = (); + type NoPreimagePostponement = (); } parameter_types! { pub const ExistentialDeposit: u64 = 1; @@ -160,7 +163,7 @@ impl, A> Get for TotalIssuanceOf { } pub struct TestReferenda; -impl Referenda> for TestReferenda { +impl Polls> for TestReferenda { type Index = u8; type Votes = u64; type Moment = u64; diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index 71d3bc7ba90fd..61099f515d6f6 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -27,9 +27,10 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys [dev-dependencies] assert_matches = "1.5" -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-core = { version = "4.1.0-dev", 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" } [features] default = ["std"] diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 7f660f5708fbb..33bc76b2a620a 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -22,7 +22,10 @@ use crate as pallet_referenda; use codec::{Encode, Decode}; use frame_support::{ assert_ok, ord_parameter_types, parameter_types, - traits::{Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers, OriginTrait}, + traits::{ + Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers, OriginTrait, + ConstU32, ConstU64, + }, weights::Weight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; @@ -46,6 +49,7 @@ frame_support::construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Preimage: pallet_preimage, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, Referenda: pallet_referenda::{Pallet, Call, Storage, Event}, } @@ -88,6 +92,16 @@ impl frame_system::Config for Test { 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; + type MaxSize = ConstU32<4096>; + type BaseDeposit = (); + type ByteDeposit = (); } parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; @@ -102,6 +116,8 @@ impl pallet_scheduler::Config for Test { type MaxScheduledPerBlock = (); type WeightInfo = (); type OriginPrivilegeCmp = EqualPrivilegeOnly; + type PreimageProvider = Preimage; + type NoPreimagePostponement = ConstU64<10>; } parameter_types! { pub const ExistentialDeposit: u64 = 1; @@ -195,7 +211,6 @@ impl TracksInfo for TestTracksInfo { &DATA[..] } fn track_for(id: &Self::Origin) -> Result { - use sp_std::convert::TryFrom; if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { match system_origin { frame_system::RawOrigin::Root => Ok(0), diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index fbfd9476ab9f3..e3e335d8498ee 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -2605,21 +2605,7 @@ mod tests { type DbWeight: Get; } - #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, scale_info::TypeInfo)] - pub enum RawOrigin { - Root, - Signed(AccountId), - None, - } - - impl From> for RawOrigin { - fn from(s: Option) -> RawOrigin { - match s { - Some(who) => RawOrigin::Signed(who), - None => RawOrigin::None, - } - } - } + pub use super::super::RawOrigin; pub type Origin = RawOrigin<::AccountId>; } @@ -2705,6 +2691,12 @@ mod tests { #[derive(TypeInfo, crate::RuntimeDebug, Eq, PartialEq, Clone, Encode, Decode)] pub struct OuterOrigin; + impl From::AccountId>> for OuterOrigin { + fn from(_: RawOrigin<::AccountId>) -> Self { + unimplemented!("Not required in tests!") + } + } + impl crate::traits::OriginTrait for OuterOrigin { type Call = ::Call; type PalletsOrigin = OuterOrigin; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index d3e746e35e21c..e7f48ae12eb9a 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -18,7 +18,7 @@ //! Traits for dealing with dispatching calls and the origin from which they are dispatched. use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; -use sp_runtime::traits::{BadOrigin, Member, Either}; +use sp_runtime::{traits::{BadOrigin, Member}, Either}; /// Some sort of check on the origin is performed by this object. pub trait EnsureOrigin { diff --git a/frame/support/test/tests/system.rs b/frame/support/test/tests/system.rs index 9def12131dd19..efcdce58f532a 100644 --- a/frame/support/test/tests/system.rs +++ b/frame/support/test/tests/system.rs @@ -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 { - Root, - Signed(AccountId), - None, -} - -impl From> for RawOrigin { - fn from(s: Option) -> RawOrigin { - match s { - Some(who) => RawOrigin::Signed(who), - None => RawOrigin::None, - } - } -} - +pub use frame_support::dispatch::RawOrigin; pub type Origin = RawOrigin<::AccountId>; #[allow(dead_code)] diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index df3c87fab4d7f..315e17d47251e 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -889,21 +889,6 @@ where } } -/// Ensure that the origin `o` represents a signed extrinsic (i.e. transaction). -/// Returns `Ok` with the account that signed the extrinsic or an `Err` otherwise. -pub fn ensure_signed_or_root(o: OuterOrigin) - -> Result, BadOrigin> -where - OuterOrigin: Into, OuterOrigin>>, -{ - match o.into() { - Ok(RawOrigin::Signed(t)) => Ok(Some(t)), - Ok(RawOrigin::Root) => Ok(None), - _ => Err(BadOrigin), - } -} - - /// A type of block initialization to perform. pub enum InitKind { /// Leave inspectable storage entries in state. diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 7b78880c2c3bf..3c71686c47af9 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -428,7 +428,7 @@ impl GetRuntimeBlockType for Runtime { type RuntimeBlock = Block; } -#[derive(Clone, RuntimeDebug)] +#[derive(Clone, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo)] pub struct Origin; impl From> for Origin { From 6b98d2d41b6081b1508e5a0aeb711e387e562433 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 13 Dec 2021 11:43:44 +0100 Subject: [PATCH 16/66] Fixes --- frame/referenda/src/lib.rs | 14 +++++------ frame/referenda/src/mock.rs | 13 +++++++--- frame/referenda/src/tests.rs | 49 ++++++++++++++++++++++++------------ 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index b806da7e95f48..e004634ecf616 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -28,7 +28,7 @@ use codec::{Encode, Codec}; use frame_support::{ ensure, BoundedVec, traits::{ - schedule::{DispatchTime, Anon as ScheduleAnon, Named as ScheduleNamed}, + schedule::{DispatchTime, v2::Anon as ScheduleAnon, v2::Named as ScheduleNamed, MaybeHashed}, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, Polls, VoteTally, PollStatus, OriginTrait, }, @@ -80,8 +80,8 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// The Scheduler. - type Scheduler: ScheduleAnon, PalletsOriginOf> - + ScheduleNamed, PalletsOriginOf>; + type Scheduler: ScheduleAnon, PalletsOriginOf, Hash=Self::Hash> + + ScheduleNamed, PalletsOriginOf, Hash=Self::Hash>; /// Currency type for this pallet. type Currency: ReservableCurrency + LockableCurrency; @@ -497,7 +497,7 @@ impl Pallet { None, 63, origin, - Call::stub { call_hash }.into(), + MaybeHashed::Hash(call_hash), ).is_ok(); debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed"); } @@ -511,7 +511,7 @@ impl Pallet { None, 128u8, frame_system::RawOrigin::Root.into(), - call.into(), + MaybeHashed::Value(call.into()), ).ok(); debug_assert!(maybe_address.is_some(), "Unable to schedule a new referendum at #{} (now: #{})?!", when, frame_system::Pallet::::block_number()); maybe_address @@ -694,7 +694,7 @@ impl Pallet { Self::deposit_event(Event::::Confirmed { index, tally: status.tally }); return (ReferendumInfo::Approved(now, status.submission_deposit, status.decision_deposit), true) } - dirty = deciding.confirming.is_none(); + dirty = dirty || deciding.confirming.is_none(); let confirmation = deciding.confirming .unwrap_or_else(|| now.saturating_add(track.confirm_period)); deciding.confirming = Some(confirmation); @@ -707,7 +707,7 @@ impl Pallet { return (ReferendumInfo::Rejected(now, status.submission_deposit, status.decision_deposit), true) } // Cannot be confirming - dirty = deciding.confirming.is_some(); + dirty = dirty || deciding.confirming.is_some(); deciding.confirming = None; let decision_time = Self::decision_time(&deciding, &status.tally, track); alarm = decision_time; diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 33bc76b2a620a..e939023995fce 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -24,7 +24,7 @@ use frame_support::{ assert_ok, ord_parameter_types, parameter_types, traits::{ Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers, OriginTrait, - ConstU32, ConstU64, + ConstU32, ConstU64, PreimageRecipient, }, weights::Weight, }; @@ -32,7 +32,7 @@ use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, IdentityLookup, Hash,}, Perbill, }; @@ -289,8 +289,13 @@ pub fn set_balance_proposal(value: u64) -> Vec { } pub fn set_balance_proposal_hash(value: u64) -> H256 { - use sp_core::Hasher; - BlakeTwo256::hash(&set_balance_proposal(value)[..]) + let c = Call::Balances(pallet_balances::Call::set_balance { + who: 42, + new_free: value, + new_reserved: 0, + }); + >::note_preimage(c.encode().try_into().unwrap()); + BlakeTwo256::hash_of(&c) } #[allow(dead_code)] diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 1473cbe5b7239..5e76a29db4196 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -37,6 +37,7 @@ fn params_should_work() { #[test] fn basic_happy_path_works() { new_test_ext().execute_with(|| { + // #1: submit assert_ok!(Referenda::submit( Origin::signed(1), RawOrigin::Root.into(), @@ -46,14 +47,23 @@ fn basic_happy_path_works() { assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(ReferendumCount::::get(), 1); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); - run_to(6); - // Vote should now be deciding. + run_to(4); + assert_eq!(DecidingCount::::get(0), 0); + run_to(5); + // #5: 4 blocks after submit - vote should now be deciding. assert_eq!(DecidingCount::::get(0), 1); + run_to(6); + // #6: Lots of ayes. Should now be confirming. set_tally(0, 100, 0); - // Vote should now be confirming. run_to(8); - // Vote should now have ended. + // #8: Should be confirmed & ended. assert_ok!(Referenda::refund_decision_deposit(Origin::signed(2), 0)); + run_to(11); + // #9: Should not yet be enacted. + assert_eq!(Balances::free_balance(&42), 0); + run_to(12); + // #10: Proposal should be executed. + assert_eq!(Balances::free_balance(&42), 1); }); } @@ -112,11 +122,12 @@ fn tracks_are_distinguished() { #[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(), - set_balance_proposal_hash(1), + h, AtOrAfter::At(10), ), Error::::NoTrack); @@ -124,7 +135,7 @@ fn submit_errors_work() { assert_noop!(Referenda::submit( Origin::signed(10), RawOrigin::Root.into(), - set_balance_proposal_hash(1), + h, AtOrAfter::At(10), ), BalancesError::::InsufficientBalance); }); @@ -136,10 +147,11 @@ fn decision_deposit_errors_work() { let e = Error::::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(), - set_balance_proposal_hash(1), + h, AtOrAfter::At(10), )); let e = BalancesError::::InsufficientBalance; @@ -157,10 +169,11 @@ fn refund_deposit_works() { let e = Error::::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(), - set_balance_proposal_hash(1), + h, AtOrAfter::At(10), )); let e = Error::::NoDeposit; @@ -178,20 +191,21 @@ fn refund_deposit_works() { #[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(), - set_balance_proposal_hash(1), + h, AtOrAfter::At(10), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); - run_to(10); + run_to(8); assert_ok!(Referenda::cancel(Origin::signed(4), 0)); assert_ok!(Referenda::refund_decision_deposit(Origin::signed(3), 0)); assert_matches!( ReferendumInfoFor::::get(0).unwrap(), - ReferendumInfo::Cancelled(10, Deposit { who: 1, amount: 2 }, None) + ReferendumInfo::Cancelled(8, Deposit { who: 1, amount: 2 }, None) ); }); } @@ -199,10 +213,11 @@ fn cancel_works() { #[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(), - set_balance_proposal_hash(1), + h, AtOrAfter::At(10), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); @@ -216,21 +231,22 @@ fn cancel_errors_works() { #[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(), - set_balance_proposal_hash(1), + h, AtOrAfter::At(10), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); - run_to(10); + run_to(8); assert_ok!(Referenda::kill(Origin::root(), 0)); let e = Error::::NoDeposit; assert_noop!(Referenda::refund_decision_deposit(Origin::signed(3), 0), e); assert_matches!( ReferendumInfoFor::::get(0).unwrap(), - ReferendumInfo::Killed(10) + ReferendumInfo::Killed(8) ); }); } @@ -238,10 +254,11 @@ fn kill_works() { #[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(), - set_balance_proposal_hash(1), + h, AtOrAfter::At(10), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); From 505c0755330c5c84415d734c98c1583783937df1 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 13 Dec 2021 11:51:46 +0100 Subject: [PATCH 17/66] Formatting --- frame/conviction-voting/src/lib.rs | 58 +++++---- frame/conviction-voting/src/tests.rs | 8 +- frame/conviction-voting/src/types.rs | 9 +- frame/conviction-voting/src/vote.rs | 8 +- frame/referenda/src/lib.rs | 140 ++++++++++++---------- frame/referenda/src/mock.rs | 98 ++++++++-------- frame/referenda/src/tests.rs | 103 ++++++++-------- frame/referenda/src/types.rs | 142 ++++++++++++++--------- frame/support/src/dispatch.rs | 2 +- frame/support/src/storage/bounded_vec.rs | 1 - frame/support/src/traits.rs | 2 +- frame/support/src/traits/dispatch.rs | 5 +- frame/support/src/traits/voting.rs | 7 +- frame/system/src/lib.rs | 2 +- 14 files changed, 336 insertions(+), 249 deletions(-) diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index b55060b50cd6b..960963fb99106 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -25,13 +25,17 @@ #![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ensure, traits::{ - Currency, Get, LockIdentifier, LockableCurrency, ReservableCurrency, WithdrawReasons, - PollStatus, Polls, -}}; -use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, Perbill, traits::{ - Saturating, Zero, AtLeast32BitUnsigned -}}; +use frame_support::{ + ensure, + traits::{ + Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polls, ReservableCurrency, + WithdrawReasons, + }, +}; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, Saturating, Zero}, + ArithmeticError, DispatchError, DispatchResult, Perbill, +}; use sp_std::prelude::*; mod conviction; @@ -85,11 +89,7 @@ pub mod pallet { + LockableCurrency; /// The implementation of the logic which conducts referenda. - type Referenda: Polls< - TallyOf, - Votes = BalanceOf, - Moment = Self::BlockNumber, - >; + type Referenda: Polls, Votes = BalanceOf, 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 @@ -116,13 +116,8 @@ pub mod pallet { /// /// TWOX-NOTE: SAFE as `AccountId`s are crypto hashes anyway. #[pallet::storage] - pub type VotingFor = StorageMap< - _, - Twox64Concat, - T::AccountId, - VotingOf, - ValueQuery, - >; + pub type VotingFor = + StorageMap<_, Twox64Concat, T::AccountId, VotingOf, 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. @@ -374,9 +369,14 @@ impl Pallet { } else { return Err(Error::::AlreadyDelegating.into()) } - // Extend the lock to `balance` (rather than setting it) since we don't know what other - // votes are in place. - T::Currency::extend_lock(CONVICTION_VOTING_ID, who, vote.balance(), WithdrawReasons::TRANSFER); + // Extend the lock to `balance` (rather than setting it) since we don't know what + // other votes are in place. + T::Currency::extend_lock( + CONVICTION_VOTING_ID, + who, + vote.balance(), + WithdrawReasons::TRANSFER, + ); Ok(()) }) }) @@ -515,7 +515,12 @@ impl Pallet { let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance)); // Extend the lock to `balance` (rather than setting it) since we don't know what other // votes are in place. - T::Currency::extend_lock(CONVICTION_VOTING_ID, &who, balance, WithdrawReasons::TRANSFER); + T::Currency::extend_lock( + CONVICTION_VOTING_ID, + &who, + balance, + WithdrawReasons::TRANSFER, + ); Ok(votes) })?; Self::deposit_event(Event::::Delegated(who, target)); @@ -558,7 +563,12 @@ impl Pallet { 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); + T::Currency::set_lock( + CONVICTION_VOTING_ID, + who, + lock_needed, + WithdrawReasons::TRANSFER, + ); } } } diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 58f89e31de648..c0b725a18e3fc 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -21,7 +21,7 @@ use super::*; use crate as pallet_conviction_voting; use frame_support::{ ord_parameter_types, parameter_types, - traits::{Contains, EqualPrivilegeOnly, OnInitialize, SortedMembers, ConstU32}, + traits::{ConstU32, Contains, EqualPrivilegeOnly, OnInitialize, SortedMembers}, weights::Weight, }; use frame_system::EnsureRoot; @@ -167,7 +167,9 @@ impl Polls> for TestReferenda { type Index = u8; type Votes = u64; type Moment = u64; - fn is_active(_index: u8) -> bool { false } + fn is_active(_index: u8) -> bool { + false + } fn access_poll( _index: Self::Index, f: impl FnOnce(PollStatus<&mut TallyOf, u64>) -> R, @@ -262,4 +264,4 @@ fn basic_stuff() { let _ = big_nay(0); fast_forward_to(1); }); -} \ No newline at end of file +} diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index e8ffd950cb6da..4cd57b9764a5a 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -24,7 +24,10 @@ use crate::{AccountVote, Conviction, Vote}; use codec::{Decode, Encode}; use frame_support::traits::VoteTally; use scale_info::TypeInfo; -use sp_runtime::{traits::{Saturating, Zero}, RuntimeDebug}; +use sp_runtime::{ + traits::{Saturating, Zero}, + RuntimeDebug, +}; /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] @@ -42,7 +45,9 @@ pub struct Tally { impl> VoteTally for Tally { - fn ayes(&self) -> Votes { self.ayes } + fn ayes(&self) -> Votes { + self.ayes + } fn turnout(&self) -> Perbill { Perbill::from_rational(self.turnout, Total::get()) diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index 2c09d93cf3c0b..15d97289dd33d 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -167,8 +167,12 @@ impl Default } } -impl - Voting +impl< + Balance: Saturating + Ord + Zero + Copy, + BlockNumber: Ord + Copy + Zero, + AccountId, + ReferendumIndex, + > Voting { pub fn rejig(&mut self, now: BlockNumber) { match self { diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index e004634ecf616..4b2dd17d20899 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -25,29 +25,34 @@ #![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Encode, Codec}; +use codec::{Codec, Encode}; use frame_support::{ - ensure, BoundedVec, traits::{ - schedule::{DispatchTime, v2::Anon as ScheduleAnon, v2::Named as ScheduleNamed, MaybeHashed}, - Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, - Polls, VoteTally, PollStatus, OriginTrait, + ensure, + traits::{ + schedule::{ + v2::{Anon as ScheduleAnon, Named as ScheduleNamed}, + DispatchTime, MaybeHashed, + }, + Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, OriginTrait, PollStatus, + Polls, ReservableCurrency, VoteTally, }, + BoundedVec, }; use scale_info::TypeInfo; use sp_runtime::{ - traits::{Dispatchable, Saturating, One, AtLeast32BitUnsigned}, DispatchError, DispatchResult, - Perbill, + traits::{AtLeast32BitUnsigned, Dispatchable, One, Saturating}, + DispatchError, DispatchResult, Perbill, }; -use sp_std::{prelude::*, fmt::Debug}; +use sp_std::{fmt::Debug, prelude::*}; mod types; pub mod weights; pub use pallet::*; pub use types::{ - ReferendumInfo, ReferendumStatus, TrackInfo, TracksInfo, Curve, DecidingStatus, Deposit, - AtOrAfter, BalanceOf, NegativeImbalanceOf, CallOf, VotesOf, TallyOf, ReferendumInfoOf, - ReferendumStatusOf, DecidingStatusOf, TrackInfoOf, TrackIdOf, InsertSorted, ReferendumIndex, - PalletsOriginOf, ScheduleAddressOf, + AtOrAfter, BalanceOf, CallOf, Curve, DecidingStatus, DecidingStatusOf, Deposit, InsertSorted, + NegativeImbalanceOf, PalletsOriginOf, ReferendumIndex, ReferendumInfo, ReferendumInfoOf, + ReferendumStatus, ReferendumStatusOf, ScheduleAddressOf, TallyOf, TrackIdOf, TrackInfo, + TrackInfoOf, TracksInfo, VotesOf, }; pub use weights::WeightInfo; @@ -64,7 +69,7 @@ const ASSEMBLY_ID: LockIdentifier = *b"assembly"; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{Parameter, pallet_prelude::*, traits::EnsureOrigin, weights::Pays}; + use frame_support::{pallet_prelude::*, traits::EnsureOrigin, weights::Pays, Parameter}; use frame_system::pallet_prelude::*; use sp_runtime::DispatchResult; @@ -80,8 +85,8 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// The Scheduler. - type Scheduler: ScheduleAnon, PalletsOriginOf, Hash=Self::Hash> - + ScheduleNamed, PalletsOriginOf, Hash=Self::Hash>; + type Scheduler: ScheduleAnon, PalletsOriginOf, Hash = Self::Hash> + + ScheduleNamed, PalletsOriginOf, Hash = Self::Hash>; /// Currency type for this pallet. type Currency: ReservableCurrency + LockableCurrency; @@ -146,24 +151,14 @@ pub mod pallet { /// The number of referenda being decided currently. #[pallet::storage] - pub type DecidingCount = StorageMap< - _, - Twox64Concat, - TrackIdOf, - u32, - ValueQuery, - >; + pub type DecidingCount = StorageMap<_, Twox64Concat, TrackIdOf, u32, ValueQuery>; /// Information concerning any given referendum. /// /// TWOX-NOTE: SAFE as indexes are not under an attacker’s control. #[pallet::storage] - pub type ReferendumInfoFor = StorageMap< - _, - Twox64Concat, - ReferendumIndex, - ReferendumInfoOf, - >; + pub type ReferendumInfoFor = + StorageMap<_, Twox64Concat, ReferendumIndex, ReferendumInfoOf>; /// 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. @@ -306,7 +301,11 @@ pub mod pallet { let track = T::Tracks::track_for(&proposal_origin).map_err(|_| Error::::NoTrack)?; let submission_deposit = Self::take_deposit(who, T::SubmissionDeposit::get())?; - let index = ReferendumCount::::mutate(|x| { let r = *x; *x += 1; r }); + let index = ReferendumCount::::mutate(|x| { + let r = *x; + *x += 1; + r + }); let status = ReferendumStatus { track, origin: proposal_origin, @@ -349,7 +348,8 @@ pub mod pallet { ) -> DispatchResult { ensure_signed_or_root(origin)?; let mut info = ReferendumInfoFor::::get(index).ok_or(Error::::BadReferendum)?; - let deposit = info.take_decision_deposit() + let deposit = info + .take_decision_deposit() .map_err(|_| Error::::Unfinished)? .ok_or(Error::::NoDeposit)?; Self::refund_deposit(Some(deposit)); @@ -358,10 +358,7 @@ pub mod pallet { } #[pallet::weight(0)] - pub fn cancel( - origin: OriginFor, - index: ReferendumIndex, - ) -> DispatchResult { + pub fn cancel(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::CancelOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; let track = Self::track(status.track).ok_or(Error::::BadTrack)?; @@ -380,10 +377,7 @@ pub mod pallet { } #[pallet::weight(0)] - pub fn kill( - origin: OriginFor, - index: ReferendumIndex, - ) -> DispatchResult { + pub fn kill(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::KillOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; let track = Self::track(status.track).ok_or(Error::::BadTrack)?; @@ -420,7 +414,9 @@ pub mod pallet { /// Just a stub - not meant to do anything. #[pallet::weight(0)] - pub fn stub(_origin: OriginFor, _call_hash: T::Hash) -> DispatchResult { Ok(()) } + pub fn stub(_origin: OriginFor, _call_hash: T::Hash) -> DispatchResult { + Ok(()) + } } } @@ -436,7 +432,7 @@ impl Polls for Pallet { Some(ReferendumInfo::Ongoing(mut status)) => { let result = f(PollStatus::Ongoing(&mut status.tally)); let now = frame_system::Pallet::::block_number(); - let (info, _) = Self::service_referendum(now, index, status); + let (info, _) = Self::service_referendum(now, index, status); ReferendumInfoFor::::insert(index, info); result }, @@ -453,7 +449,7 @@ impl Polls for Pallet { Some(ReferendumInfo::Ongoing(mut status)) => { let result = f(PollStatus::Ongoing(&mut status.tally))?; let now = frame_system::Pallet::::block_number(); - let (info, _) = Self::service_referendum(now, index, status); + let (info, _) = Self::service_referendum(now, index, status); ReferendumInfoFor::::insert(index, info); Ok(result) }, @@ -471,9 +467,7 @@ impl Polls for Pallet { } impl Pallet { - pub fn ensure_ongoing(index: ReferendumIndex) - -> Result, DispatchError> - { + pub fn ensure_ongoing(index: ReferendumIndex) -> Result, DispatchError> { match ReferendumInfoFor::::get(index) { Some(ReferendumInfo::Ongoing(status)) => Ok(status), _ => Err(Error::::NotOngoing.into()), @@ -498,7 +492,8 @@ impl Pallet { 63, origin, MaybeHashed::Hash(call_hash), - ).is_ok(); + ) + .is_ok(); debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed"); } @@ -512,8 +507,14 @@ impl Pallet { 128u8, frame_system::RawOrigin::Root.into(), MaybeHashed::Value(call.into()), - ).ok(); - debug_assert!(maybe_address.is_some(), "Unable to schedule a new referendum at #{} (now: #{})?!", when, frame_system::Pallet::::block_number()); + ) + .ok(); + debug_assert!( + maybe_address.is_some(), + "Unable to schedule a new referendum at #{} (now: #{})?!", + when, + frame_system::Pallet::::block_number() + ); maybe_address } @@ -692,11 +693,18 @@ impl Pallet { let (desired, call_hash) = (status.enactment, status.proposal_hash); Self::schedule_enactment(index, track, desired, status.origin, call_hash); Self::deposit_event(Event::::Confirmed { index, tally: status.tally }); - return (ReferendumInfo::Approved(now, status.submission_deposit, status.decision_deposit), true) + return ( + ReferendumInfo::Approved( + now, + status.submission_deposit, + status.decision_deposit, + ), + true, + ) } dirty = dirty || deciding.confirming.is_none(); - let confirmation = deciding.confirming - .unwrap_or_else(|| now.saturating_add(track.confirm_period)); + let confirmation = + deciding.confirming.unwrap_or_else(|| now.saturating_add(track.confirm_period)); deciding.confirming = Some(confirmation); alarm = confirmation; } else { @@ -704,7 +712,14 @@ impl Pallet { // Failed! Self::note_one_fewer_deciding(status.track, track); Self::deposit_event(Event::::Rejected { index, tally: status.tally }); - return (ReferendumInfo::Rejected(now, status.submission_deposit, status.decision_deposit), true) + return ( + ReferendumInfo::Rejected( + now, + status.submission_deposit, + status.decision_deposit, + ), + true, + ) } // Cannot be confirming dirty = dirty || deciding.confirming.is_some(); @@ -715,7 +730,10 @@ impl Pallet { } else if now >= timeout { // Too long without being decided - end it. Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); - return (ReferendumInfo::TimedOut(now, status.submission_deposit, status.decision_deposit), true) + return ( + ReferendumInfo::TimedOut(now, status.submission_deposit, status.decision_deposit), + true, + ) } status.alarm = Self::set_alarm(Call::nudge_referendum { index }, alarm); (ReferendumInfo::Ongoing(status), dirty) @@ -732,13 +750,17 @@ impl Pallet { let until_approval = track.min_approval.delay(approval); let until_turnout = track.min_turnout.delay(turnout); let offset = until_turnout.max(until_approval); - deciding.ending.saturating_sub(deciding.period).saturating_add(offset * deciding.period) + deciding + .ending + .saturating_sub(deciding.period) + .saturating_add(offset * deciding.period) } /// Reserve a deposit and return the `Deposit` instance. - fn take_deposit(who: T::AccountId, amount: BalanceOf) - -> Result>, DispatchError> - { + fn take_deposit( + who: T::AccountId, + amount: BalanceOf, + ) -> Result>, DispatchError> { T::Currency::reserve(&who, amount)?; Ok(Deposit { who, amount }) } @@ -759,8 +781,7 @@ impl Pallet { fn track(id: TrackIdOf) -> Option<&'static TrackInfoOf> { let tracks = T::Tracks::tracks(); - let index = tracks.binary_search_by_key(&id, |x| x.0) - .unwrap_or_else(|x| x); + let index = tracks.binary_search_by_key(&id, |x| x.0).unwrap_or_else(|x| x); Some(&tracks[index].1) } @@ -772,7 +793,6 @@ impl Pallet { approval_needed: &Curve, ) -> bool { let x = Perbill::from_rational(now.min(period), period); - turnout_needed.passing(x, tally.turnout()) - && approval_needed.passing(x, tally.approval()) + turnout_needed.passing(x, tally.turnout()) && approval_needed.passing(x, tally.approval()) } } diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index e939023995fce..dc73a5b289782 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -19,12 +19,12 @@ use super::*; use crate as pallet_referenda; -use codec::{Encode, Decode}; +use codec::{Decode, Encode}; use frame_support::{ assert_ok, ord_parameter_types, parameter_types, traits::{ - Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, SortedMembers, OriginTrait, - ConstU32, ConstU64, PreimageRecipient, + ConstU32, ConstU64, Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, OriginTrait, + PreimageRecipient, SortedMembers, }, weights::Weight, }; @@ -32,7 +32,7 @@ use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, IdentityLookup, Hash,}, + traits::{BlakeTwo256, Hash, IdentityLookup}, Perbill, }; @@ -173,40 +173,46 @@ impl TracksInfo for TestTracksInfo { type Origin = ::PalletsOrigin; fn tracks() -> &'static [(Self::Id, TrackInfo)] { static DATA: [(u8, TrackInfo); 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), + ( + 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), + }, }, - 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), + }, }, - }), - (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[..] } @@ -270,17 +276,17 @@ pub struct Tally { } impl VoteTally for Tally { - fn ayes(&self) -> u32 { - self.ayes - } + fn ayes(&self) -> u32 { + self.ayes + } - fn turnout(&self) -> Perbill { - Perbill::from_percent(self.ayes + self.nays) - } + fn turnout(&self) -> Perbill { + Perbill::from_percent(self.ayes + self.nays) + } - fn approval(&self) -> Perbill { - Perbill::from_rational(self.ayes, self.ayes + self.nays) - } + fn approval(&self) -> Perbill { + Perbill::from_rational(self.ayes, self.ayes + self.nays) + } } pub fn set_balance_proposal(value: u64) -> Vec { @@ -338,4 +344,4 @@ pub fn set_tally(index: ReferendumIndex, ayes: u32, nays: u32) { tally.ayes = ayes; tally.nays = nays; }); -} \ No newline at end of file +} diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 5e76a29db4196..a0d1530fc1ac5 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -17,13 +17,16 @@ //! The crate's tests. -use codec::Decode; -use assert_matches::assert_matches; -use frame_support::{assert_ok, assert_noop, traits::Contains}; -use frame_support::dispatch::{RawOrigin, DispatchError::BadOrigin}; -use pallet_balances::Error as BalancesError; use super::*; use crate::mock::*; +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; #[test] fn params_should_work() { @@ -88,34 +91,43 @@ fn tracks_are_distinguished() { let mut i = ReferendumInfoFor::::iter().collect::>(); 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 }, - ayes_in_queue: None, - alarm: Some((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 }, - ayes_in_queue: None, - alarm: Some((3, 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 }, + ayes_in_queue: None, + alarm: Some((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 }, + ayes_in_queue: None, + alarm: Some((3, 0)), + }) + ), + ] + ); }); } @@ -124,20 +136,16 @@ 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::::NoTrack); + assert_noop!( + Referenda::submit(Origin::signed(1), RawOrigin::Signed(2).into(), h, AtOrAfter::At(10),), + Error::::NoTrack + ); // No funds for deposit - assert_noop!(Referenda::submit( - Origin::signed(10), - RawOrigin::Root.into(), - h, - AtOrAfter::At(10), - ), BalancesError::::InsufficientBalance); + assert_noop!( + Referenda::submit(Origin::signed(10), RawOrigin::Root.into(), h, AtOrAfter::At(10),), + BalancesError::::InsufficientBalance + ); }); } @@ -244,10 +252,7 @@ fn kill_works() { assert_ok!(Referenda::kill(Origin::root(), 0)); let e = Error::::NoDeposit; assert_noop!(Referenda::refund_decision_deposit(Origin::signed(3), 0), e); - assert_matches!( - ReferendumInfoFor::::get(0).unwrap(), - ReferendumInfo::Killed(8) - ); + assert_matches!(ReferendumInfoFor::::get(0).unwrap(), ReferendumInfo::Killed(8)); }); } diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 7395f34493e4e..d716dd955d2d8 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -19,7 +19,7 @@ use super::*; use codec::{Decode, Encode, EncodeLike}; -use frame_support::{Parameter, traits::schedule::Anon}; +use frame_support::{traits::schedule::Anon, Parameter}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; @@ -54,26 +54,17 @@ pub type ReferendumStatusOf = ReferendumStatus< ::AccountId, ScheduleAddressOf, >; -pub type DecidingStatusOf = DecidingStatus< - ::BlockNumber, ->; -pub type TrackInfoOf = TrackInfo< +pub type DecidingStatusOf = DecidingStatus<::BlockNumber>; +pub type TrackInfoOf = TrackInfo, ::BlockNumber>; +pub type TrackIdOf = <::Tracks as TracksInfo< BalanceOf, ::BlockNumber, ->; -pub type TrackIdOf = < - ::Tracks as TracksInfo< - BalanceOf, - ::BlockNumber, - > ->::Id; -pub type ScheduleAddressOf = < - ::Scheduler as Anon< - ::BlockNumber, - CallOf, - PalletsOriginOf, - > ->::Address; +>>::Id; +pub type ScheduleAddressOf = <::Scheduler as Anon< + ::BlockNumber, + CallOf, + PalletsOriginOf, +>>::Address; /// A referendum index. pub type ReferendumIndex = u32; @@ -83,16 +74,18 @@ pub trait InsertSorted { /// /// 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 + Ord, - >(&mut self, t: T, f: F,) -> bool; + fn insert_sorted_by_key K, K: PartialOrd + Ord>( + &mut self, + t: T, + f: F, + ) -> bool; } impl> InsertSorted for BoundedVec { - fn insert_sorted_by_key< - F: FnMut(&T) -> K, - K: PartialOrd + Ord, - >(&mut self, t: T, mut f: F,) -> bool { + fn insert_sorted_by_key K, K: PartialOrd + Ord>( + &mut self, + t: T, + mut f: F, + ) -> bool { let index = self.binary_search_by_key::(&f(&t), f).unwrap_or_else(|x| x); if index >= S::get() as usize { return false @@ -158,8 +151,8 @@ pub enum AtOrAfter { /// 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. + /// 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), } @@ -177,7 +170,15 @@ impl AtOrAfter { pub struct ReferendumStatus< TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Moment: Parameter + Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Moment: Parameter + + Eq + + PartialEq + + sp_std::fmt::Debug + + Encode + + Decode + + TypeInfo + + Clone + + EncodeLike, Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, @@ -214,16 +215,26 @@ pub struct ReferendumStatus< } impl< - TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Moment: Parameter + Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + AtLeast32BitUnsigned + Copy + EncodeLike, - Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, -> + TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Moment: Parameter + + Eq + + PartialEq + + sp_std::fmt::Debug + + Encode + + Decode + + TypeInfo + + Clone + + AtLeast32BitUnsigned + + Copy + + EncodeLike, + Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + > ReferendumStatus { pub fn begin_deciding(&mut self, now: Moment, decision_period: Moment) { @@ -250,7 +261,19 @@ pub enum ReferendumInfo< ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, > { /// Referendum has been submitted and is being voted on. - Ongoing(ReferendumStatus), + Ongoing( + ReferendumStatus< + TrackId, + Origin, + Moment, + Hash, + Balance, + Votes, + Tally, + AccountId, + ScheduleAddress, + >, + ), /// Referendum finished with approval. Submission deposit is held. Approved(Moment, Deposit, Option>), /// Referendum finished with rejection. Submission deposit is held. @@ -264,17 +287,24 @@ pub enum ReferendumInfo< } impl< - TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Moment: Parameter + Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, - Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, -> - ReferendumInfo + TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Moment: Parameter + + Eq + + PartialEq + + sp_std::fmt::Debug + + Encode + + Decode + + TypeInfo + + Clone + + EncodeLike, + Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + > ReferendumInfo { pub fn take_decision_deposit(&mut self) -> Result>, ()> { use ReferendumInfo::*; @@ -282,7 +312,8 @@ impl< 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()), + Approved(_, _, d) | Rejected(_, _, d) | TimedOut(_, _, d) | Cancelled(_, _, d) => + Ok(d.take()), Killed(_) => Ok(None), } } @@ -302,9 +333,8 @@ impl Curve { } pub fn delay(&self, y: Perbill) -> Perbill { match self { - Self::LinearDecreasing { begin, delta } => { - (*begin - y.min(*begin)).min(*delta) / *delta - }, + Self::LinearDecreasing { begin, delta } => + (*begin - y.min(*begin)).min(*delta) / *delta, } } pub fn passing(&self, x: Perbill, y: Perbill) -> bool { diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index e3e335d8498ee..3f4663b37944c 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -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}, @@ -32,7 +33,6 @@ pub use crate::{ ClassifyDispatch, DispatchInfo, GetDispatchInfo, PaysFee, PostDispatchInfo, TransactionPriority, WeighData, Weight, WithPostDispatchInfo, }, - scale_info::TypeInfo, }; pub use sp_runtime::{traits::Dispatchable, DispatchError, RuntimeDebug}; diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 23cdf76725f15..bedd8bd5d945e 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -147,7 +147,6 @@ impl BoundedVec { pub fn pop(&mut self) -> Option { self.0.pop() } - } impl> From> for Vec { diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 46da73e8535e3..b27e7fc1dd5ac 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -89,5 +89,5 @@ pub use dispatch::{EnsureOneOf, EnsureOrigin, OriginTrait, UnfilteredDispatchabl mod voting; pub use voting::{ - CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, Polls, PollStatus, + CurrencyToVote, PollStatus, Polls, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, }; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index e7f48ae12eb9a..16d086b6a4134 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -18,7 +18,10 @@ //! Traits for dealing with dispatching calls and the origin from which they are dispatched. use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; -use sp_runtime::{traits::{BadOrigin, Member}, Either}; +use sp_runtime::{ + traits::{BadOrigin, Member}, + Either, +}; /// Some sort of check on the origin is performed by this object. pub trait EnsureOrigin { diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index d4b966000521c..8027e568c15e2 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -18,10 +18,13 @@ //! Traits and associated data structures concerned with voting, and moving between tokens and //! votes. +use crate::dispatch::{DispatchError, Parameter}; use codec::HasCompact; -use sp_arithmetic::{Perbill, traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}}; +use sp_arithmetic::{ + traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}, + Perbill, +}; use sp_runtime::traits::Member; -use crate::dispatch::{Parameter, DispatchError}; /// 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 diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 315e17d47251e..c5d3c712a1e59 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -124,8 +124,8 @@ pub use extensions::{ }; // Backward compatible re-export. pub use extensions::check_mortality::CheckMortality as CheckEra; -pub use weights::WeightInfo; pub use frame_support::dispatch::RawOrigin; +pub use weights::WeightInfo; /// Compute the trie root of a list of extrinsics. pub fn extrinsics_root(extrinsics: &[E]) -> H::Output { From bfbce8ff13f43a17b59eb06be529964bc325a719 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 13 Dec 2021 14:16:00 +0100 Subject: [PATCH 18/66] Fixes --- frame/referenda/src/benchmarking.rs | 715 +--------------------------- frame/referenda/src/lib.rs | 53 +-- frame/referenda/src/tests.rs | 25 + frame/support/src/traits/voting.rs | 9 +- 4 files changed, 60 insertions(+), 742 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index 34bcb0da301e6..cd14c78c10df7 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -33,9 +33,6 @@ use sp_runtime::traits::{BadOrigin, Bounded, One}; use crate::Pallet as Democracy; const SEED: u32 = 0; -const MAX_REFERENDUMS: u32 = 99; -const MAX_SECONDERS: u32 = 100; -const MAX_BYTES: u32 = 16_384; fn assert_last_event(generic_event: ::Event) { frame_system::Pallet::::assert_last_event(generic_event.into()); @@ -46,13 +43,13 @@ fn funded_account(name: &'static str, index: u32) -> T::AccountId { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); caller } - -fn add_proposal(n: u32) -> Result { +/* +fn add_proposal(track: TrackIfOf, n: u32) -> Result { let other = funded_account::("proposer", n); - let value = T::MinimumDeposit::get(); + let value = T::SubmissionDeposit::get(); let proposal_hash: T::Hash = T::Hashing::hash_of(&n); - Democracy::::propose(RawOrigin::Signed(other).into(), proposal_hash, value.into())?; + Referenda::::submit(RawOrigin::Signed(other).into(), proposal_hash, AtOrAfter::After(0))?; Ok(proposal_hash) } @@ -85,9 +82,9 @@ fn account_vote(b: BalanceOf) -> AccountVote> { AccountVote::Standard { vote: v, balance: b } } - +*/ benchmarks! { - propose { + submit { let p = T::MaxProposals::get(); for i in 0 .. (p - 1) { @@ -103,706 +100,6 @@ benchmarks! { assert_eq!(Democracy::::public_props().len(), p as usize, "Proposals not created."); } - second { - let s in 0 .. MAX_SECONDERS; - - let caller = funded_account::("caller", 0); - let proposal_hash = add_proposal::(s)?; - - // Create s existing "seconds" - for i in 0 .. s { - let seconder = funded_account::("seconder", i); - Democracy::::second(RawOrigin::Signed(seconder).into(), 0, u32::MAX)?; - } - - let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; - assert_eq!(deposits.0.len(), (s + 1) as usize, "Seconds not recorded"); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), 0, u32::MAX) - verify { - let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; - assert_eq!(deposits.0.len(), (s + 2) as usize, "`second` benchmark did not work"); - } - - vote_new { - let r in 1 .. MAX_REFERENDUMS; - - let caller = funded_account::("caller", 0); - let account_vote = account_vote::(100u32.into()); - - // We need to create existing direct votes - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; - } - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Votes were not recorded."); - - let referendum_index = add_referendum::(r)?; - whitelist_account!(caller); - }: vote(RawOrigin::Signed(caller.clone()), referendum_index, account_vote) - verify { - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r + 1) as usize, "Vote was not recorded."); - } - - vote_existing { - let r in 1 .. MAX_REFERENDUMS; - - let caller = funded_account::("caller", 0); - let account_vote = account_vote::(100u32.into()); - - // We need to create existing direct votes - for i in 0 ..=r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; - } - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded."); - - // Change vote from aye to nay - let nay = Vote { aye: false, conviction: Conviction::Locked1x }; - let new_vote = AccountVote::Standard { vote: nay, balance: 1000u32.into() }; - let referendum_index = Democracy::::referendum_count() - 1; - - // This tests when a user changes a vote - whitelist_account!(caller); - }: vote(RawOrigin::Signed(caller.clone()), referendum_index, new_vote) - verify { - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r + 1) as usize, "Vote was incorrectly added"); - let referendum_info = Democracy::::referendum_info(referendum_index) - .ok_or("referendum doesn't exist")?; - let tally = match referendum_info { - ReferendumInfo::Ongoing(r) => r.tally, - _ => return Err("referendum not ongoing".into()), - }; - assert_eq!(tally.nays, 1000u32.into(), "changed vote was not recorded"); - } - - emergency_cancel { - let origin = T::CancellationOrigin::successful_origin(); - let referendum_index = add_referendum::(0)?; - assert_ok!(Democracy::::referendum_status(referendum_index)); - }: _(origin, referendum_index) - verify { - // Referendum has been canceled - assert_noop!( - Democracy::::referendum_status(referendum_index), - Error::::ReferendumInvalid, - ); - } - - blacklist { - let p in 1 .. T::MaxProposals::get(); - - // Place our proposal at the end to make sure it's worst case. - for i in 0 .. p - 1 { - add_proposal::(i)?; - } - // We should really add a lot of seconds here, but we're not doing it elsewhere. - - // Place our proposal in the external queue, too. - let hash = T::Hashing::hash_of(&0); - assert_ok!( - Democracy::::external_propose(T::ExternalOrigin::successful_origin(), hash.clone()) - ); - let origin = T::BlacklistOrigin::successful_origin(); - // Add a referendum of our proposal. - let referendum_index = add_referendum::(0)?; - assert_ok!(Democracy::::referendum_status(referendum_index)); - }: _(origin, hash, Some(referendum_index)) - verify { - // Referendum has been canceled - assert_noop!( - Democracy::::referendum_status(referendum_index), - Error::::ReferendumInvalid - ); - } - - // Worst case scenario, we external propose a previously blacklisted proposal - external_propose { - let v in 1 .. MAX_VETOERS as u32; - - let origin = T::ExternalOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&0); - // Add proposal to blacklist with block number 0 - Blacklist::::insert( - proposal_hash, - (T::BlockNumber::zero(), vec![T::AccountId::default(); v as usize]) - ); - }: _(origin, proposal_hash) - verify { - // External proposal created - ensure!(>::exists(), "External proposal didn't work"); - } - - external_propose_majority { - let origin = T::ExternalMajorityOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&0); - }: _(origin, proposal_hash) - verify { - // External proposal created - ensure!(>::exists(), "External proposal didn't work"); - } - - external_propose_default { - let origin = T::ExternalDefaultOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&0); - }: _(origin, proposal_hash) - verify { - // External proposal created - ensure!(>::exists(), "External proposal didn't work"); - } - - fast_track { - let origin_propose = T::ExternalDefaultOrigin::successful_origin(); - let proposal_hash: T::Hash = T::Hashing::hash_of(&0); - Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; - - // NOTE: Instant origin may invoke a little bit more logic, but may not always succeed. - let origin_fast_track = T::FastTrackOrigin::successful_origin(); - let voting_period = T::FastTrackVotingPeriod::get(); - let delay = 0u32; - }: _(origin_fast_track, proposal_hash, voting_period.into(), delay.into()) - verify { - assert_eq!(Democracy::::referendum_count(), 1, "referendum not created") - } - - veto_external { - // Existing veto-ers - let v in 0 .. MAX_VETOERS as u32; - - let proposal_hash: T::Hash = T::Hashing::hash_of(&v); - - let origin_propose = T::ExternalDefaultOrigin::successful_origin(); - Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; - - let mut vetoers: Vec = Vec::new(); - for i in 0 .. v { - vetoers.push(account("vetoer", i, SEED)); - } - vetoers.sort(); - Blacklist::::insert(proposal_hash, (T::BlockNumber::zero(), vetoers)); - - let origin = T::VetoOrigin::successful_origin(); - ensure!(NextExternal::::get().is_some(), "no external proposal"); - }: _(origin, proposal_hash) - verify { - assert!(NextExternal::::get().is_none()); - let (_, new_vetoers) = >::get(&proposal_hash).ok_or("no blacklist")?; - assert_eq!(new_vetoers.len(), (v + 1) as usize, "vetoers not added"); - } - - cancel_proposal { - let p in 1 .. T::MaxProposals::get(); - - // Place our proposal at the end to make sure it's worst case. - for i in 0 .. p { - add_proposal::(i)?; - } - }: _(RawOrigin::Root, 0) - - cancel_referendum { - let referendum_index = add_referendum::(0)?; - }: _(RawOrigin::Root, referendum_index) - - cancel_queued { - let r in 1 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; // This add one element in the scheduler - } - - let referendum_index = add_referendum::(r)?; - }: _(RawOrigin::Root, referendum_index) - - // This measures the path of `launch_next` external. Not currently used as we simply - // assume the weight is `MaxBlockWeight` when executing. - #[extra] - on_initialize_external { - let r in 0 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; - } - - assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); - - // Launch external - LastTabledWasExternal::::put(false); - - let origin = T::ExternalMajorityOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&r); - let call = Call::::external_propose_majority { proposal_hash }; - call.dispatch_bypass_filter(origin)?; - // External proposal created - ensure!(>::exists(), "External proposal didn't work"); - - let block_number = T::LaunchPeriod::get(); - - }: { Democracy::::on_initialize(block_number) } - verify { - // One extra because of next external - assert_eq!(Democracy::::referendum_count(), r + 1, "referenda not created"); - ensure!(!>::exists(), "External wasn't taken"); - - // All but the new next external should be finished - for i in 0 .. r { - if let Some(value) = ReferendumInfoOf::::get(i) { - match value { - ReferendumInfo::Finished { .. } => (), - ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()), - } - } - } - } - - // This measures the path of `launch_next` public. Not currently used as we simply - // assume the weight is `MaxBlockWeight` when executing. - #[extra] - on_initialize_public { - let r in 1 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; - } - - assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); - - // Launch public - assert!(add_proposal::(r).is_ok(), "proposal not created"); - LastTabledWasExternal::::put(true); - - let block_number = T::LaunchPeriod::get(); - - }: { Democracy::::on_initialize(block_number) } - verify { - // One extra because of next public - assert_eq!(Democracy::::referendum_count(), r + 1, "proposal not accepted"); - - // All should be finished - for i in 0 .. r { - if let Some(value) = ReferendumInfoOf::::get(i) { - match value { - ReferendumInfo::Finished { .. } => (), - ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()), - } - } - } - } - - // No launch no maturing referenda. - on_initialize_base { - let r in 1 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; - } - - for (key, mut info) in ReferendumInfoOf::::iter() { - if let ReferendumInfo::Ongoing(ref mut status) = info { - status.end += 100u32.into(); - } - ReferendumInfoOf::::insert(key, info); - } - - assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); - assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); - - }: { Democracy::::on_initialize(1u32.into()) } - verify { - // All should be on going - for i in 0 .. r { - if let Some(value) = ReferendumInfoOf::::get(i) { - match value { - ReferendumInfo::Finished { .. } => return Err("Referendum has been finished".into()), - ReferendumInfo::Ongoing(_) => (), - } - } - } - } - - on_initialize_base_with_launch_period { - let r in 1 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; - } - - for (key, mut info) in ReferendumInfoOf::::iter() { - if let ReferendumInfo::Ongoing(ref mut status) = info { - status.end += 100u32.into(); - } - ReferendumInfoOf::::insert(key, info); - } - - assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); - assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); - - let block_number = T::LaunchPeriod::get(); - - }: { Democracy::::on_initialize(block_number) } - verify { - // All should be on going - for i in 0 .. r { - if let Some(value) = ReferendumInfoOf::::get(i) { - match value { - ReferendumInfo::Finished { .. } => return Err("Referendum has been finished".into()), - ReferendumInfo::Ongoing(_) => (), - } - } - } - } - - delegate { - let r in 1 .. MAX_REFERENDUMS; - - let initial_balance: BalanceOf = 100u32.into(); - let delegated_balance: BalanceOf = 1000u32.into(); - - let caller = funded_account::("caller", 0); - // Caller will initially delegate to `old_delegate` - let old_delegate: T::AccountId = funded_account::("old_delegate", r); - Democracy::::delegate( - RawOrigin::Signed(caller.clone()).into(), - old_delegate.clone(), - Conviction::Locked1x, - delegated_balance, - )?; - let (target, balance) = match VotingOf::::get(&caller) { - Voting::Delegating { target, balance, .. } => (target, balance), - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(target, old_delegate, "delegation target didn't work"); - assert_eq!(balance, delegated_balance, "delegation balance didn't work"); - // Caller will now switch to `new_delegate` - let new_delegate: T::AccountId = funded_account::("new_delegate", r); - let account_vote = account_vote::(initial_balance); - // We need to create existing direct votes for the `new_delegate` - for i in 0..r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote.clone())?; - } - let votes = match VotingOf::::get(&new_delegate) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Votes were not recorded."); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone()), new_delegate.clone(), Conviction::Locked1x, delegated_balance) - verify { - let (target, balance) = match VotingOf::::get(&caller) { - Voting::Delegating { target, balance, .. } => (target, balance), - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(target, new_delegate, "delegation target didn't work"); - assert_eq!(balance, delegated_balance, "delegation balance didn't work"); - let delegations = match VotingOf::::get(&new_delegate) { - Voting::Direct { delegations, .. } => delegations, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(delegations.capital, delegated_balance, "delegation was not recorded."); - } - - undelegate { - let r in 1 .. MAX_REFERENDUMS; - - let initial_balance: BalanceOf = 100u32.into(); - let delegated_balance: BalanceOf = 1000u32.into(); - - let caller = funded_account::("caller", 0); - // Caller will delegate - let the_delegate: T::AccountId = funded_account::("delegate", r); - Democracy::::delegate( - RawOrigin::Signed(caller.clone()).into(), - the_delegate.clone(), - Conviction::Locked1x, - delegated_balance, - )?; - let (target, balance) = match VotingOf::::get(&caller) { - Voting::Delegating { target, balance, .. } => (target, balance), - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(target, the_delegate, "delegation target didn't work"); - assert_eq!(balance, delegated_balance, "delegation balance didn't work"); - // We need to create votes direct votes for the `delegate` - let account_vote = account_vote::(initial_balance); - for i in 0..r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote( - RawOrigin::Signed(the_delegate.clone()).into(), - ref_idx, - account_vote.clone() - )?; - } - let votes = match VotingOf::::get(&the_delegate) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Votes were not recorded."); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone())) - verify { - // Voting should now be direct - match VotingOf::::get(&caller) { - Voting::Direct { .. } => (), - _ => return Err("undelegation failed".into()), - } - } - - clear_public_proposals { - add_proposal::(0)?; - - }: _(RawOrigin::Root) - - note_preimage { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let caller = funded_account::("caller", 0); - let encoded_proposal = vec![1; b as usize]; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) - verify { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - } - - note_imminent_preimage { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - // d + 1 to include the one we are testing - let encoded_proposal = vec![1; b as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - let block_number = T::BlockNumber::one(); - Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number)); - - let caller = funded_account::("caller", 0); - let encoded_proposal = vec![1; b as usize]; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) - verify { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - } - - reap_preimage { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let encoded_proposal = vec![1; b as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - - let submitter = funded_account::("submitter", b); - Democracy::::note_preimage(RawOrigin::Signed(submitter.clone()).into(), encoded_proposal.clone())?; - - // We need to set this otherwise we get `Early` error. - let block_number = T::VotingPeriod::get() + T::EnactmentPeriod::get() + T::BlockNumber::one(); - System::::set_block_number(block_number.into()); - - assert!(Preimages::::contains_key(proposal_hash)); - - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), proposal_hash.clone(), u32::MAX) - verify { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - assert!(!Preimages::::contains_key(proposal_hash)); - } - - // Test when unlock will remove locks - unlock_remove { - let r in 1 .. MAX_REFERENDUMS; - - let locker = funded_account::("locker", 0); - // Populate votes so things are locked - let base_balance: BalanceOf = 100u32.into(); - let small_vote = account_vote::(base_balance); - // Vote and immediately unvote - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; - Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_idx)?; - } - - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - }: unlock(RawOrigin::Signed(caller), locker.clone()) - verify { - // Note that we may want to add a `get_lock` api to actually verify - let voting = VotingOf::::get(&locker); - assert_eq!(voting.locked_balance(), BalanceOf::::zero()); - } - - // Test when unlock will set a new value - unlock_set { - let r in 1 .. MAX_REFERENDUMS; - - let locker = funded_account::("locker", 0); - // Populate votes so things are locked - let base_balance: BalanceOf = 100u32.into(); - let small_vote = account_vote::(base_balance); - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; - } - - // Create a big vote so lock increases - let big_vote = account_vote::(base_balance * 10u32.into()); - let referendum_index = add_referendum::(r)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), referendum_index, big_vote)?; - - let votes = match VotingOf::::get(&locker) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded."); - - let voting = VotingOf::::get(&locker); - assert_eq!(voting.locked_balance(), base_balance * 10u32.into()); - - Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), referendum_index)?; - - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - }: unlock(RawOrigin::Signed(caller), locker.clone()) - verify { - let votes = match VotingOf::::get(&locker) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Vote was not removed"); - - let voting = VotingOf::::get(&locker); - // Note that we may want to add a `get_lock` api to actually verify - assert_eq!(voting.locked_balance(), base_balance); - } - - remove_vote { - let r in 1 .. MAX_REFERENDUMS; - - let caller = funded_account::("caller", 0); - let account_vote = account_vote::(100u32.into()); - - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; - } - - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Votes not created"); - - let referendum_index = r - 1; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone()), referendum_index) - verify { - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); - } - - // Worst case is when target == caller and referendum is ongoing - remove_other_vote { - let r in 1 .. MAX_REFERENDUMS; - - let caller = funded_account::("caller", r); - let account_vote = account_vote::(100u32.into()); - - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; - } - - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Votes not created"); - - let referendum_index = r - 1; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone()), caller.clone(), referendum_index) - verify { - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); - } - - #[extra] - enact_proposal_execute { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let proposer = funded_account::("proposer", 0); - let raw_call = Call::note_preimage { encoded_proposal: vec![1; b as usize] }; - let generic_call: T::Proposal = raw_call.into(); - let encoded_proposal = generic_call.encode(); - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; - - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - }: enact_proposal(RawOrigin::Root, proposal_hash, 0) - verify { - // Fails due to mismatched origin - assert_last_event::(Event::::Executed(0, Err(BadOrigin.into())).into()); - } - - #[extra] - enact_proposal_slash { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let proposer = funded_account::("proposer", 0); - // Random invalid bytes - let encoded_proposal = vec![200; b as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; - - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - let origin = RawOrigin::Root.into(); - let call = Call::::enact_proposal { proposal_hash, index: 0 }.encode(); - }: { - assert_eq!( - as Decode>::decode(&mut &*call) - .expect("call is encoded above, encoding must be correct") - .dispatch_bypass_filter(origin), - Err(Error::::PreimageInvalid.into()) - ); - } - impl_benchmark_test_suite!( Democracy, crate::tests::new_test_ext(), diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 4b2dd17d20899..8970848959741 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -136,8 +136,15 @@ pub mod pallet { #[pallet::storage] pub type ReferendumCount = StorageValue<_, ReferendumIndex, ValueQuery>; - /// The sorted list of referenda - /// ready to be decided but not yet being decided, ordered by conviction-weighted approvals. + /// Information concerning any given referendum. + /// + /// TWOX-NOTE: SAFE as indexes are not under an attacker’s control. + #[pallet::storage] + pub type ReferendumInfoFor = + StorageMap<_, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf>; + + /// The sorted list of referenda ready to be decided but not yet being decided, ordered by + /// conviction-weighted approvals. /// /// This should be empty if `DecidingCount` is less than `TrackInfo::max_deciding`. #[pallet::storage] @@ -153,21 +160,6 @@ pub mod pallet { #[pallet::storage] pub type DecidingCount = StorageMap<_, Twox64Concat, TrackIdOf, u32, ValueQuery>; - /// Information concerning any given referendum. - /// - /// TWOX-NOTE: SAFE as indexes are not under an attacker’s control. - #[pallet::storage] - pub type ReferendumInfoFor = - StorageMap<_, Twox64Concat, ReferendumIndex, ReferendumInfoOf>; - - /// 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 = StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber>; - #[pallet::genesis_config] pub struct GenesisConfig { _phantom: sp_std::marker::PhantomData, @@ -306,18 +298,20 @@ pub mod pallet { *x += 1; r }); + let now = frame_system::Pallet::::block_number(); + let nudge_call = Call::nudge_referendum { index }; let status = ReferendumStatus { track, origin: proposal_origin, proposal_hash: proposal_hash.clone(), enactment: enactment_moment, - submitted: frame_system::Pallet::::block_number(), + submitted: now, submission_deposit, decision_deposit: None, deciding: None, tally: Default::default(), ayes_in_queue: None, - alarm: None, + alarm: Self::set_alarm(nudge_call, now + T::UndecidingTimeout::get()), }; ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); @@ -393,8 +387,8 @@ pub mod pallet { Ok(()) } - /// Advance a referendum onto its next logical state. This will happen eventually anyway, - /// but you can nudge it + /// Advance a referendum onto its next logical state. This should happen automaically, + /// but this exists in order to allow it to happen manaully in case it is needed. #[pallet::weight(0)] pub fn nudge_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { ensure_signed_or_root(origin)?; @@ -402,6 +396,8 @@ pub mod pallet { Ok(()) } + /// Advance a track onto its next logical state. This should happen automaically, + /// but this exists in order to allow it to happen manaully in case it is needed. #[pallet::weight(0)] pub fn nudge_track( origin: OriginFor, @@ -411,12 +407,6 @@ pub mod pallet { Self::advance_track(track)?; Ok(Pays::No.into()) } - - /// Just a stub - not meant to do anything. - #[pallet::weight(0)] - pub fn stub(_origin: OriginFor, _call_hash: T::Hash) -> DispatchResult { - Ok(()) - } } } @@ -424,6 +414,8 @@ impl Polls for Pallet { type Index = ReferendumIndex; type Votes = VotesOf; type Moment = T::BlockNumber; + type Class = TrackIdOf; + fn access_poll( index: Self::Index, f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber>) -> R, @@ -441,6 +433,7 @@ impl Polls for Pallet { _ => f(PollStatus::None), } } + fn try_access_poll( index: Self::Index, f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber>) -> Result, @@ -458,11 +451,13 @@ impl Polls for Pallet { _ => f(PollStatus::None), } } + fn tally(index: Self::Index) -> Option { Some(Self::ensure_ongoing(index).ok()?.tally) } - fn is_active(index: Self::Index) -> bool { - Self::ensure_ongoing(index).is_ok() + + fn is_active(index: Self::Index) -> Option> { + Self::ensure_ongoing(index).ok().map(|e| e.track) } } diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index a0d1530fc1ac5..21eab7c39d336 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -70,6 +70,31 @@ fn basic_happy_path_works() { }); } +#[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(10), + )); + run_to(10); + assert_matches!( + ReferendumInfoFor::::get(0), + Some(ReferendumInfo::Ongoing(..)) + ); + run_to(11); + // #11: Timed out - ended. + assert_matches!( + ReferendumInfoFor::::get(0), + Some(ReferendumInfo::TimedOut(11, _, None)) + ); + + }); +} + #[test] fn tracks_are_distinguished() { new_test_ext().execute_with(|| { diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 8027e568c15e2..aac78e9ac260d 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -118,13 +118,14 @@ impl PollStatus { pub trait Polls { type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; + type Class: Parameter + Member; type Moment; - /// `true` if the referendum `index` can be voted on. Once this is `false`, existing votes may - /// be cancelled permissionlessly. - fn is_active(index: Self::Index) -> bool; + /// `Some` if the referendum `index` can be voted on, along with the class of referendum. Once + /// this is `None`, existing votes may be cancelled permissionlessly. + fn is_active(index: Self::Index) -> Option; - /// Don't use this if you might mutate - use `try_mutate_tally` instead. + /// Don't use this if you might mutate - use `try_access_poll` instead. fn tally(index: Self::Index) -> Option; fn access_poll( From c04ca085f6ca58b0aa7f0c9c103f96f32ae3f55d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 13 Dec 2021 15:04:14 +0100 Subject: [PATCH 19/66] Tests --- frame/referenda/src/tests.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 21eab7c39d336..20cb417c86e26 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -70,6 +70,40 @@ fn basic_happy_path_works() { }); } +#[test] +fn confirming_then_fail_works() { + new_test_ext().execute_with(|| { + }); +} + +#[test] +fn confirming_then_reconfirming_works() { + new_test_ext().execute_with(|| { + }); +} + +#[test] +fn queueing_works() { + new_test_ext().execute_with(|| { + // Submit 4 proposals with a queue len of 1. + // One should be being decided. + // Vote on the others to set order. + // Cancel the first. + // The other with the most approvals should be being decided. + // Vote on the remaining two to change order. + // Vote enough for it to insta-win. + // There should be a third being decided, the one with the most approvals. + // Let it end unsuccessfully. + // The final should be being decided. + }); +} + +#[test] +fn kill_when_confirming_works() { + new_test_ext().execute_with(|| { + }); +} + #[test] fn auto_timeout_should_happen_with_nothing_but_submit() { new_test_ext().execute_with(|| { From 7d99c7dccfb58e10ff49cdb4f2fbff3008955af9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 14 Dec 2021 09:19:11 +0100 Subject: [PATCH 20/66] Fixes --- frame/referenda/src/lib.rs | 45 +++++++++++++++++++++-------- frame/referenda/src/mock.rs | 4 +-- frame/referenda/src/tests.rs | 55 +++++++++++++++++++++++++++++++++--- frame/referenda/src/types.rs | 47 +++++++++++++++++++++++++++--- 4 files changed, 130 insertions(+), 21 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 8970848959741..67aa4ae05ab76 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -356,7 +356,7 @@ pub mod pallet { T::CancelOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; let track = Self::track(status.track).ok_or(Error::::BadTrack)?; - if let Some(last_alarm) = status.alarm { + if let Some((_, last_alarm)) = status.alarm { let _ = T::Scheduler::cancel(last_alarm); } Self::note_one_fewer_deciding(status.track, track); @@ -375,7 +375,7 @@ pub mod pallet { T::KillOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; let track = Self::track(status.track).ok_or(Error::::BadTrack)?; - if let Some(last_alarm) = status.alarm { + if let Some((_, last_alarm)) = status.alarm { let _ = T::Scheduler::cancel(last_alarm); } Self::note_one_fewer_deciding(status.track, track); @@ -493,24 +493,25 @@ impl Pallet { } /// Set an alarm. - fn set_alarm(call: impl Into>, when: T::BlockNumber) -> Option> { + fn set_alarm(call: impl Into>, when: T::BlockNumber) -> Option<(T::BlockNumber, ScheduleAddressOf)> { let alarm_interval = T::AlarmInterval::get().max(One::one()); let when = (when + alarm_interval - One::one()) / alarm_interval * alarm_interval; - let maybe_address = T::Scheduler::schedule( + let maybe_result = T::Scheduler::schedule( DispatchTime::At(when), None, 128u8, frame_system::RawOrigin::Root.into(), MaybeHashed::Value(call.into()), ) - .ok(); + .ok() + .map(|x| (when, x)); debug_assert!( - maybe_address.is_some(), + maybe_result.is_some(), "Unable to schedule a new referendum at #{} (now: #{})?!", when, frame_system::Pallet::::block_number() ); - maybe_address + maybe_result } fn ready_for_deciding( @@ -519,13 +520,16 @@ impl Pallet { index: ReferendumIndex, status: &mut ReferendumStatusOf, ) { + println!("ready_for_deciding ref #{:?}", index); let deciding_count = DecidingCount::::get(status.track); if deciding_count < track.max_deciding { // Begin deciding. + println!("Beginning deciding..."); status.begin_deciding(now, track.decision_period); DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); } else { // Add to queue. + println!("Queuing for decision."); let item = (index, status.tally.ayes()); status.ayes_in_queue = Some(status.tally.ayes()); TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)); @@ -627,10 +631,8 @@ impl Pallet { index: ReferendumIndex, mut status: ReferendumStatusOf, ) -> (ReferendumInfoOf, bool) { + println!("#{:?}: Servicing #{:?}", now, index); let mut dirty = false; - if let Some(last_alarm) = status.alarm.take() { - let _ = T::Scheduler::cancel(last_alarm); - } // Should it begin being decided? let track = match Self::track(status.track) { Some(x) => x, @@ -642,6 +644,7 @@ impl Pallet { if status.deciding.is_none() { // Are we already queued for deciding? if let Some(_) = status.ayes_in_queue.as_ref() { + println!("Already queued..."); // Does our position in the queue need updating? let ayes = status.tally.ayes(); let mut queue = TrackQueue::::get(status.track); @@ -668,6 +671,7 @@ impl Pallet { Self::ready_for_deciding(now, &track, index, &mut status); dirty = true; } else { + println!("Not deciding, have DD, within PP: Setting alarm to end of PP"); alarm = alarm.min(prepare_end); } } @@ -684,6 +688,7 @@ impl Pallet { if is_passing { if deciding.confirming.map_or(false, |c| now >= c) { // Passed! + Self::kill_alarm(&mut status); Self::note_one_fewer_deciding(status.track, track); let (desired, call_hash) = (status.enactment, status.proposal_hash); Self::schedule_enactment(index, track, desired, status.origin, call_hash); @@ -705,6 +710,7 @@ impl Pallet { } else { if now >= deciding.ending { // Failed! + Self::kill_alarm(&mut status); Self::note_one_fewer_deciding(status.track, track); Self::deposit_event(Event::::Rejected { index, tally: status.tally }); return ( @@ -724,13 +730,23 @@ impl Pallet { } } else if now >= timeout { // Too long without being decided - end it. + Self::kill_alarm(&mut status); Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); return ( ReferendumInfo::TimedOut(now, status.submission_deposit, status.decision_deposit), true, ) } - status.alarm = Self::set_alarm(Call::nudge_referendum { index }, alarm); + + if !status.alarm.as_ref().map_or(false, |&(when, _)| when == alarm) { + println!("Setting alarm at block #{:?} for ref {:?}", alarm, index); + // Either no alarm or one that was different + Self::kill_alarm(&mut status); + status.alarm = Self::set_alarm(Call::nudge_referendum { index }, alarm); + dirty = true; + } else { + println!("Keeping alarm at block #{:?} for ref {:?}", alarm, index); + } (ReferendumInfo::Ongoing(status), dirty) } @@ -751,6 +767,13 @@ impl Pallet { .saturating_add(offset * deciding.period) } + fn kill_alarm(status: &mut ReferendumStatusOf) { + if let Some((_, last_alarm)) = status.alarm.take() { + // Incorrect alarm - cancel it. + let _ = T::Scheduler::cancel(last_alarm); + } + } + /// Reserve a deposit and return the `Deposit` instance. fn take_deposit( who: T::AccountId, diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index dc73a5b289782..149b7ba22522d 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -104,7 +104,7 @@ impl pallet_preimage::Config for Test { type ByteDeposit = (); } parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; + pub MaximumSchedulerWeight: Weight = 2_000_000_000_000; } impl pallet_scheduler::Config for Test { type Event = Event; @@ -113,7 +113,7 @@ impl pallet_scheduler::Config for Test { type Call = Call; type MaximumWeight = MaximumSchedulerWeight; type ScheduleOrigin = EnsureRoot; - type MaxScheduledPerBlock = (); + type MaxScheduledPerBlock = ConstU32<100>; type WeightInfo = (); type OriginPrivilegeCmp = EqualPrivilegeOnly; type PreimageProvider = Preimage; diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 20cb417c86e26..35fa3d134f0fd 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -28,6 +28,8 @@ use frame_support::{ }; 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(|| { @@ -85,11 +87,56 @@ fn confirming_then_reconfirming_works() { #[test] fn queueing_works() { new_test_ext().execute_with(|| { - // Submit 4 proposals with a queue len of 1. + // 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..=3 { + 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(6), i as u32)); + // TODO: decision deposit after some initial votes with a non-highest voted coming first. + } + assert_eq!(ReferendumCount::::get(), 4); + + run_to(5); // One should be being decided. - // Vote on the others to set order. + assert_eq!(DecidingCount::::get(0), 1); + assert_matches!( + ReferendumInfoFor::::get(0), + Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. })) + ); + + // Vote to set order. + set_tally(1, 1, 10); + set_tally(2, 2, 20); + set_tally(3, 3, 30); + println!("Agenda #6: {:?}", pallet_scheduler::Agenda::::get(6)); + run_to(6); + println!("{:?}", Vec::<_>::from(TrackQueue::::get(0))); + // Cancel the first. + assert_ok!(Referenda::cancel(Origin::signed(4), 0)); + // The other with the most approvals should be being decided. + assert_eq!(DecidingCount::::get(0), 1); + assert_matches!( + ReferendumInfoFor::::get(3), + Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. })) + ); + // Vote on the remaining two to change order. // Vote enough for it to insta-win. // There should be a third being decided, the one with the most approvals. @@ -166,7 +213,7 @@ fn tracks_are_distinguished() { deciding: None, tally: Tally { ayes: 0, nays: 0 }, ayes_in_queue: None, - alarm: Some((5, 0)), + alarm: Some((5, (5, 0))), }) ), ( @@ -182,7 +229,7 @@ fn tracks_are_distinguished() { deciding: None, tally: Tally { ayes: 0, nays: 0 }, ayes_in_queue: None, - alarm: Some((3, 0)), + alarm: Some((3, (3, 0))), }) ), ] diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index d716dd955d2d8..e32ca662ff6dc 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -87,14 +87,53 @@ impl> InsertSorted for BoundedVec { mut f: F, ) -> bool { let index = self.binary_search_by_key::(&f(&t), f).unwrap_or_else(|x| x); - if index >= S::get() as usize { - return false + if self.len() == S::get() as usize { + if index == 0 { + return false + } + self[0..index].rotate_left(1); + self[index - 1] = t; + } else { + self.force_insert(index, t); } - self.force_insert(index, t); true } } +#[cfg(test)] +mod tests { + use super::*; + use frame_support::traits::ConstU32; + + #[test] + fn insert_sorted_works() { + let mut b: BoundedVec> = 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)] pub struct DecidingStatus { /// When this referendum will end. If confirming, then the @@ -211,7 +250,7 @@ pub struct ReferendumStatus< /// Automatic advancement is scheduled when ayes_in_queue is Some value greater than the /// ayes in `tally`. pub(crate) ayes_in_queue: Option, - pub(crate) alarm: Option, + pub(crate) alarm: Option<(Moment, ScheduleAddress)>, } impl< From c17ee4b411c59816c586e71eee6a4b5098368d31 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 14 Dec 2021 15:49:07 +0100 Subject: [PATCH 21/66] Fixes --- frame/referenda/src/lib.rs | 147 +++++++++++++++-------- frame/referenda/src/mock.rs | 6 +- frame/referenda/src/tests.rs | 144 +++++++++++++++++++--- frame/referenda/src/types.rs | 67 +++-------- frame/support/src/storage/bounded_vec.rs | 130 +++++++++++++++++++- 5 files changed, 372 insertions(+), 122 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 67aa4ae05ab76..e6d78a53f51fa 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -40,7 +40,7 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Dispatchable, One, Saturating}, + traits::{AtLeast32BitUnsigned, Dispatchable, Zero, One, Saturating}, DispatchError, DispatchResult, Perbill, }; use sp_std::{fmt::Debug, prelude::*}; @@ -507,13 +507,54 @@ impl Pallet { .map(|x| (when, x)); debug_assert!( maybe_result.is_some(), - "Unable to schedule a new referendum at #{} (now: #{})?!", + "Unable to schedule a new alarm at #{} (now: #{})?!", when, frame_system::Pallet::::block_number() ); maybe_result } + /// Mutate a referendum's `status` into the correct deciding state. + /// + /// - `now` is the current block number. + /// - `track` is the track info for the referendum. + /// + /// This will properly set up the `confirming` item. + fn begin_deciding( + status: &mut ReferendumStatusOf, + index: ReferendumIndex, + now: T::BlockNumber, + track: &TrackInfoOf, + ) { + println!("Begining deciding for ref #{:?} at block #{:?}", index, now); + let is_passing = Self::is_passing( + &status.tally, + Zero::zero(), + track.decision_period, + &track.min_turnout, + &track.min_approval, + ); + dbg!( + is_passing, + &status.tally, + now, + track.decision_period, + &track.min_turnout, + &track.min_approval, + ); + status.ayes_in_queue = None; + let confirming = if is_passing { + Some(now.saturating_add(track.confirm_period)) + } else { + None + }; + let deciding_status = DecidingStatus { since: now, confirming }; + let alarm = Self::decision_time(&deciding_status, &status.tally, track); + dbg!(alarm); + Self::ensure_alarm_at(status, index, alarm); + status.deciding = Some(deciding_status); + } + fn ready_for_deciding( now: T::BlockNumber, track: &TrackInfoOf, @@ -525,7 +566,7 @@ impl Pallet { if deciding_count < track.max_deciding { // Begin deciding. println!("Beginning deciding..."); - status.begin_deciding(now, track.decision_period); + Self::begin_deciding(status, index, now, track); DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); } else { // Add to queue. @@ -563,7 +604,7 @@ impl Pallet { while deciding_count < track_info.max_deciding { if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { let now = frame_system::Pallet::::block_number(); - status.begin_deciding(now, track_info.decision_period); + Self::begin_deciding(&mut status, index, now, &track_info); ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); deciding_count.saturating_inc(); count.saturating_inc(); @@ -594,10 +635,12 @@ impl Pallet { /// - begin deciding another referendum (and leave `DecidingCount` alone); or /// - decrement `DecidingCount`. fn note_one_fewer_deciding(track: TrackIdOf, track_info: &TrackInfoOf) { + println!("One fewer deciding on {:?}", track); let mut track_queue = TrackQueue::::get(track); if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { + println!("Ref #{:?} ready for deciding", index); let now = frame_system::Pallet::::block_number(); - status.begin_deciding(now, track_info.decision_period); + Self::begin_deciding(&mut status, index, now, track_info); ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); TrackQueue::::insert(track, track_queue); } else { @@ -605,6 +648,28 @@ impl Pallet { } } + /// Ensure that an `service_referendum` alarm happens for the referendum `index` at `alarm`. + /// + /// This will do nothing if the alarm is already set. + /// + /// Returns `false` if nothing changed. + fn ensure_alarm_at( + status: &mut ReferendumStatusOf, + index: ReferendumIndex, + alarm: T::BlockNumber, + ) -> bool { + if !status.alarm.as_ref().map_or(false, |&(when, _)| when == alarm) { + println!("Setting alarm at block #{:?} for ref {:?}", alarm, index); + // Either no alarm or one that was different + Self::kill_alarm(status); + status.alarm = Self::set_alarm(Call::nudge_referendum { index }, alarm); + true + } else { + println!("Keeping alarm at block #{:?} for ref {:?}", alarm, index); + false + } + } + /// Advance the state of a referendum, which comes down to: /// - If it's ready to be decided, start deciding; /// - If it's not ready to be decided and non-deciding timeout has passed, fail; @@ -650,17 +715,13 @@ impl Pallet { let mut queue = TrackQueue::::get(status.track); let maybe_old_pos = queue.iter().position(|(x, _)| *x == index); let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x); - if maybe_old_pos.is_none() && new_pos < queue.len() { + if maybe_old_pos.is_none() && new_pos > 0 { // Just insert. - queue.force_insert(new_pos, (index, ayes)); + queue.force_insert_keep_right(new_pos, (index, ayes)); } else if let Some(old_pos) = maybe_old_pos { // We were in the queue - just update and sort. queue[old_pos].1 = ayes; - if new_pos < old_pos { - queue[new_pos..=old_pos].rotate_right(1); - } else if old_pos < new_pos { - queue[old_pos..=new_pos].rotate_left(1); - } + queue.slide(old_pos, new_pos); } TrackQueue::::insert(status.track, queue); } else { @@ -680,8 +741,8 @@ impl Pallet { if let Some(deciding) = &mut status.deciding { let is_passing = Self::is_passing( &status.tally, - now, - deciding.period, + now.saturating_sub(deciding.since), + track.decision_period, &track.min_turnout, &track.min_approval, ); @@ -702,13 +763,13 @@ impl Pallet { true, ) } - dirty = dirty || deciding.confirming.is_none(); - let confirmation = - deciding.confirming.unwrap_or_else(|| now.saturating_add(track.confirm_period)); - deciding.confirming = Some(confirmation); - alarm = confirmation; + if deciding.confirming.is_none() { + // Start confirming + dirty = true; + deciding.confirming = Some(now.saturating_add(track.confirm_period)); + } } else { - if now >= deciding.ending { + if now >= deciding.since.saturating_add(track.decision_period) { // Failed! Self::kill_alarm(&mut status); Self::note_one_fewer_deciding(status.track, track); @@ -722,12 +783,13 @@ impl Pallet { true, ) } - // Cannot be confirming - dirty = dirty || deciding.confirming.is_some(); - deciding.confirming = None; - let decision_time = Self::decision_time(&deciding, &status.tally, track); - alarm = decision_time; + if deciding.confirming.is_some() { + // Stop confirming + dirty = true; + deciding.confirming = None; + } } + alarm = Self::decision_time(&deciding, &status.tally, track); } else if now >= timeout { // Too long without being decided - end it. Self::kill_alarm(&mut status); @@ -738,16 +800,8 @@ impl Pallet { ) } - if !status.alarm.as_ref().map_or(false, |&(when, _)| when == alarm) { - println!("Setting alarm at block #{:?} for ref {:?}", alarm, index); - // Either no alarm or one that was different - Self::kill_alarm(&mut status); - status.alarm = Self::set_alarm(Call::nudge_referendum { index }, alarm); - dirty = true; - } else { - println!("Keeping alarm at block #{:?} for ref {:?}", alarm, index); - } - (ReferendumInfo::Ongoing(status), dirty) + let dirty_alarm = Self::ensure_alarm_at(&mut status, index, alarm); + (ReferendumInfo::Ongoing(status), dirty_alarm || dirty) } fn decision_time( @@ -755,16 +809,15 @@ impl Pallet { tally: &T::Tally, track: &TrackInfoOf, ) -> T::BlockNumber { - // Set alarm to the point where the current voting would make it pass. - let approval = tally.approval(); - let turnout = tally.turnout(); - let until_approval = track.min_approval.delay(approval); - let until_turnout = track.min_turnout.delay(turnout); - let offset = until_turnout.max(until_approval); - deciding - .ending - .saturating_sub(deciding.period) - .saturating_add(offset * deciding.period) + deciding.confirming.unwrap_or_else(|| { + // Set alarm to the point where the current voting would make it pass. + let approval = tally.approval(); + let turnout = tally.turnout(); + let until_approval = track.min_approval.delay(approval); + let until_turnout = track.min_turnout.delay(turnout); + let offset = until_turnout.max(until_approval); + deciding.since.saturating_add(offset * track.decision_period) + }) } fn kill_alarm(status: &mut ReferendumStatusOf) { @@ -805,12 +858,12 @@ impl Pallet { fn is_passing( tally: &T::Tally, - now: T::BlockNumber, + elapsed: T::BlockNumber, period: T::BlockNumber, turnout_needed: &Curve, approval_needed: &Curve, ) -> bool { - let x = Perbill::from_rational(now.min(period), period); + let x = Perbill::from_rational(elapsed.min(period), period); turnout_needed.passing(x, tally.turnout()) && approval_needed.passing(x, tally.approval()) } } diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 149b7ba22522d..903b2bbc4ab97 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -147,8 +147,8 @@ parameter_types! { pub static InstantAllowed: bool = false; pub static AlarmInterval: u64 = 1; pub const SubmissionDeposit: u64 = 2; - pub const MaxQueued: u32 = 2; - pub const UndecidingTimeout: u64 = 10; + pub const MaxQueued: u32 = 3; + pub const UndecidingTimeout: u64 = 20; } ord_parameter_types! { pub const One: u64 = 1; @@ -250,7 +250,7 @@ impl Config for Test { pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)], } .assimilate_storage(&mut t) .unwrap(); diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 35fa3d134f0fd..05f1c762c9d7b 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -35,7 +35,7 @@ fn params_should_work() { new_test_ext().execute_with(|| { assert_eq!(ReferendumCount::::get(), 0); assert_eq!(Balances::free_balance(42), 0); - assert_eq!(Balances::total_issuance(), 210); + assert_eq!(Balances::total_issuance(), 600); }); } @@ -84,6 +84,68 @@ fn confirming_then_reconfirming_works() { }); } +fn is_waiting(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: None, .. })) + ) +} + +fn is_deciding(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. })) + ) +} + +fn is_deciding_and_failing(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Ongoing(ReferendumStatus { + deciding: Some(DecidingStatus { confirming: None, .. }), + .. + })) + ) +} + +fn is_confirming(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Ongoing(ReferendumStatus { + deciding: Some(DecidingStatus { confirming: Some(_), .. }), + .. + })) + ) +} + +fn is_approved(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Approved(..)) + ) +} + +fn is_rejected(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Rejected(..)) + ) +} + +fn is_cancelled(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Cancelled(..)) + ) +} + +fn is_killed(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Killed(..)) + ) +} + #[test] fn queueing_works() { new_test_ext().execute_with(|| { @@ -99,52 +161,94 @@ fn queueing_works() { run_to(2); // Submit 3 more proposals into the same queue. - for i in 1..=3 { + 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(6), i as u32)); + 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::::get(), 4); + assert_eq!(ReferendumCount::::get(), 5); run_to(5); // One should be being decided. assert_eq!(DecidingCount::::get(0), 1); - assert_matches!( - ReferendumInfoFor::::get(0), - Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. })) - ); + assert!(is_deciding_and_failing(0)); // 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::::get(6)); run_to(6); println!("{:?}", Vec::<_>::from(TrackQueue::::get(0))); // Cancel the first. assert_ok!(Referenda::cancel(Origin::signed(4), 0)); + assert!(is_cancelled(0)); - // The other with the most approvals should be being decided. + // The other with the most approvals (#4) should be being decided. assert_eq!(DecidingCount::::get(0), 1); - assert_matches!( - ReferendumInfoFor::::get(3), - Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. })) - ); + assert!(is_deciding(4)); + assert!(is_confirming(4)); // Vote on the remaining two to change order. - // Vote enough for it to insta-win. - // There should be a third being decided, the one with the most approvals. + println!("Set tally #1"); + set_tally(1, 30, 31); + println!("{:?}", Vec::<_>::from(TrackQueue::::get(0))); + println!("Set tally #2"); + set_tally(2, 20, 20); + println!("{:?}", Vec::<_>::from(TrackQueue::::get(0))); + + // Let confirmation period end. + run_to(8); + + // #4 should have been confirmed. + assert!(is_approved(4)); + // #1 (the one with the most approvals) should now be being decided. + assert!(is_deciding(1)); + // Let it end unsuccessfully. - // The final should be being decided. + run_to(12); + assert!(is_rejected(1)); + + // #2 should now be being decided. It will (barely) pass. + assert!(is_deciding_and_failing(2)); + + // #2 moves into confirming at the last moment with a 50% approval. + run_to(16); + assert!(is_confirming(2)); + + // #2 gets approved. + run_to(18); + assert!(is_approved(2)); + assert!(is_deciding(3)); +/* + // Vote enough on #2 for it to go into confirming. + set_tally(2, 100, 0); + assert!(is_confirming(2)); + + run_to(14); + set_tally(2, 100, 100); + assert!(is_deciding_and_failing(2)); + + run_to(15); + set_tally(2, 1000, 100); + assert!(is_confirming(2)); + + run_to(17); +*/ }); } +// TODO: Confirm -> Unconfirm -> Reconfirm -> Pass +// TODO: (End) Confirm -> Unconfirm (overtime) -> Pass +// TODO: (End) Confirm -> Unconfirm (overtime) -> Fail + #[test] fn kill_when_confirming_works() { new_test_ext().execute_with(|| { @@ -159,18 +263,18 @@ fn auto_timeout_should_happen_with_nothing_but_submit() { Origin::signed(1), RawOrigin::Root.into(), set_balance_proposal_hash(1), - AtOrAfter::At(10), + AtOrAfter::At(20), )); - run_to(10); + run_to(20); assert_matches!( ReferendumInfoFor::::get(0), Some(ReferendumInfo::Ongoing(..)) ); - run_to(11); + run_to(21); // #11: Timed out - ended. assert_matches!( ReferendumInfoFor::::get(0), - Some(ReferendumInfo::TimedOut(11, _, None)) + Some(ReferendumInfo::TimedOut(21, _, None)) ); }); diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index e32ca662ff6dc..93451e87fb70f 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -87,16 +87,7 @@ impl> InsertSorted for BoundedVec { mut f: F, ) -> bool { let index = self.binary_search_by_key::(&f(&t), f).unwrap_or_else(|x| x); - if self.len() == S::get() as usize { - if index == 0 { - return false - } - self[0..index].rotate_left(1); - self[index - 1] = t; - } else { - self.force_insert(index, t); - } - true + self.force_insert_keep_right(index, t) } } @@ -136,11 +127,9 @@ mod tests { #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct DecidingStatus { - /// When this referendum will end. If confirming, then the + /// When this referendum began being "decided". If confirming, then the /// end will actually be delayed until the end of the confirmation period. - pub(crate) ending: BlockNumber, - /// How long we will be deciding on this referendum for. - pub(crate) period: BlockNumber, + 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, @@ -253,39 +242,6 @@ pub struct ReferendumStatus< pub(crate) alarm: Option<(Moment, ScheduleAddress)>, } -impl< - TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Moment: Parameter - + Eq - + PartialEq - + sp_std::fmt::Debug - + Encode - + Decode - + TypeInfo - + Clone - + AtLeast32BitUnsigned - + Copy - + EncodeLike, - Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - > - ReferendumStatus -{ - pub fn begin_deciding(&mut self, now: Moment, decision_period: Moment) { - self.ayes_in_queue = None; - self.deciding = Some(DecidingStatus { - ending: now.saturating_add(decision_period), - period: decision_period, - confirming: None, - }); - } -} - /// Info regarding a referendum, present or past. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum ReferendumInfo< @@ -359,6 +315,7 @@ impl< } #[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[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 }, @@ -380,3 +337,19 @@ impl Curve { y >= self.threshold(x) } } + +#[cfg(feature = "std")] +impl sp_std::fmt::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, + ) + }, + } + } +} + diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index bedd8bd5d945e..33f97ea7f193a 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -161,13 +161,70 @@ impl> BoundedVec { S::get() as usize } - /// Forces the insertion of `s` into `self` truncating first if necessary. + /// Forces the insertion of `s` into `self` retaining all items with index at least `index`. /// - /// Infallible, but if the limit is zero, then it's a no-op. - pub fn force_insert(&mut self, index: usize, element: T) { - if Self::bound() > 0 { - self.0.truncate(Self::bound() as usize - 1); + /// If `index == 0` and `self.len() == Self::bound()` then this is a no-op. + /// + /// Returns `true` if the item was inserted. + pub fn force_insert_keep_right(&mut self, index: usize, element: T) -> bool { + if self.len() < Self::bound() { self.0.insert(index, element); + } else { + if index == 0 { + return false + } + self[0] = element; + self[0..index].rotate_left(1); + } + true + } + + /// Forces the insertion of `s` into `self` retaining all items with index at most `index`. + /// + /// If `index == Self::bound()` and `self.len() == Self::bound()` then this is a no-op. + /// + /// Returns `true` if the item was inserted. + pub fn force_insert_keep_left(&mut self, index: usize, element: T) -> bool { + if self.len() >= Self::bound() && index >= Self::bound() { + return false + } + self.0.insert(index, element); + self.0.truncate(Self::bound()); + true + } + + /// Move the position of an item from one location to another in the slice. + /// + /// Except for the item being moved, the order of the slice remains the same. + /// + /// - `index` is the location of the item to be moved. + /// - `insert_position` is the index of the item in the slice which should *immediately follow* + /// the item which is being moved. + pub fn slide(&mut self, index: usize, insert_position: usize) { + if insert_position < index { + // --- --- --- === === === === @@@ --- --- --- + // ^-- N ^O^ + // ... + // /-----<<<-----\ + // --- --- --- === === === === @@@ --- --- --- + // >>> >>> >>> >>> + // ... + // --- --- --- @@@ === === === === --- --- --- + // ^N^ + self[insert_position..=index].rotate_right(1); + } else if index + 1 < insert_position { + // Note that the apparent asymmetry of these two branches is due to the + // fact that the "new" position is the position to be inserted *before*. + // --- --- --- @@@ === === === === --- --- --- + // ^O^ ^-- N + // ... + // /----->>>-----\ + // --- --- --- @@@ === === === === --- --- --- + // <<< <<< <<< <<< + // ... + // --- --- --- === === === === @@@ --- --- --- + // ^N^ + self[index..=(insert_position - 1)].rotate_left(1); } } @@ -379,6 +436,7 @@ pub mod test { use super::*; use crate::Twox128; use sp_io::TestExternalities; + use crate::traits::ConstU32; crate::parameter_types! { pub const Seven: u32 = 7; @@ -392,6 +450,68 @@ pub mod test { FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedVec> } + #[test] + fn slide_works() { + let mut b: BoundedVec> = vec![0, 1, 2, 3, 4, 5].try_into().unwrap(); + b.slide(1, 5); + assert_eq!(*b, vec![0, 2, 3, 4, 1, 5]); + b.slide(4, 0); + assert_eq!(*b, vec![1, 0, 2, 3, 4, 5]); + b.slide(0, 2); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + b.slide(1, 6); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + b.slide(0, 6); + assert_eq!(*b, vec![2, 3, 4, 5, 1, 0]); + } + + #[test] + fn slide_noops_work() { + let mut b: BoundedVec> = vec![0, 1, 2, 3, 4, 5].try_into().unwrap(); + b.slide(3, 3); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + b.slide(3, 4); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + } + + #[test] + fn force_insert_keep_left_works() { + let mut b: BoundedVec> = vec![].try_into().unwrap(); + assert!(b.force_insert_keep_left(0, 30)); + assert!(b.force_insert_keep_left(0, 10)); + assert!(b.force_insert_keep_left(1, 20)); + assert!(b.force_insert_keep_left(3, 40)); + assert_eq!(*b, vec![10, 20, 30, 40]); + // at capacity. + assert!(!b.force_insert_keep_left(4, 41)); + assert_eq!(*b, vec![10, 20, 30, 40]); + assert!(b.force_insert_keep_left(3, 31)); + assert_eq!(*b, vec![10, 20, 30, 31]); + assert!(b.force_insert_keep_left(1, 11)); + assert_eq!(*b, vec![10, 11, 20, 30]); + assert!(b.force_insert_keep_left(0, 1)); + assert_eq!(*b, vec![1, 10, 11, 20]); + } + + #[test] + fn force_insert_keep_right_works() { + let mut b: BoundedVec> = vec![].try_into().unwrap(); + assert!(b.force_insert_keep_right(0, 30)); + assert!(b.force_insert_keep_right(0, 10)); + assert!(b.force_insert_keep_right(1, 20)); + assert!(b.force_insert_keep_right(3, 40)); + assert_eq!(*b, vec![10, 20, 30, 40]); + // at capacity. + assert!(!b.force_insert_keep_right(0, 0)); + assert_eq!(*b, vec![10, 20, 30, 40]); + assert!(b.force_insert_keep_right(1, 11)); + assert_eq!(*b, vec![11, 20, 30, 40]); + assert!(b.force_insert_keep_right(3, 31)); + assert_eq!(*b, vec![20, 30, 31, 40]); + assert!(b.force_insert_keep_right(4, 41)); + assert_eq!(*b, vec![30, 31, 40, 41]); + } + #[test] fn try_append_is_correct() { assert_eq!(BoundedVec::::bound(), 7); From fd575649c5059bdf3ae415263514a12df901ad2a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 14 Dec 2021 19:27:09 +0100 Subject: [PATCH 22/66] More tests --- frame/referenda/src/mock.rs | 134 ++++++++++++++++++++ frame/referenda/src/tests.rs | 231 +++++++++++++++++++++++------------ 2 files changed, 284 insertions(+), 81 deletions(-) diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 903b2bbc4ab97..a3417a6a53869 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -345,3 +345,137 @@ pub fn set_tally(index: ReferendumIndex, ayes: u32, nays: u32) { tally.nays = nays; }); } + +pub fn is_waiting(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: None, .. })) + ) +} + +pub fn is_deciding(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. })) + ) +} + +pub fn is_deciding_and_failing(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Ongoing(ReferendumStatus { + deciding: Some(DecidingStatus { confirming: None, .. }), + .. + })) + ) +} + +pub fn is_confirming(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Ongoing(ReferendumStatus { + deciding: Some(DecidingStatus { confirming: Some(_), .. }), + .. + })) + ) +} + +pub fn is_approved(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Approved(..)) + ) +} + +pub fn is_rejected(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Rejected(..)) + ) +} + +pub fn is_cancelled(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Cancelled(..)) + ) +} + +pub fn is_killed(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Killed(..)) + ) +} + +pub fn waiting_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Ongoing(ReferendumStatus { submitted, deciding: None, .. }) => submitted, + _ => panic!("Not waiting"), + } +} + +pub fn deciding_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::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::::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::::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::::get(i).unwrap() { + ReferendumInfo::Approved(since, ..) => since, + _ => panic!("Not approved"), + } +} + +pub fn rejected_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Rejected(since, ..) => since, + _ => panic!("Not rejected"), + } +} + +pub fn cancelled_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Cancelled(since, ..) => since, + _ => panic!("Not cancelled"), + } +} + +pub fn killed_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Killed(since, ..) => since, + _ => panic!("Not killed"), + } +} diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 05f1c762c9d7b..5c9ea94fd91d7 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -72,78 +72,172 @@ fn basic_happy_path_works() { }); } +// TODO: Confirm -> Kill -> Fail +// TODO: Confirm -> Unconfirm -> Reconfirm -> Pass +// TODO: (End) Confirm -> Unconfirm (overtime) -> Pass +// TODO: (End) Confirm -> Unconfirm (overtime) -> Fail + #[test] -fn confirming_then_fail_works() { +fn insta_confirm_then_kill_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_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); + // Insta-confirm + set_tally(0, 100, 0); + run_to(6); + assert!(pallet_scheduler::Agenda::::get(7).iter().any(|x| x.is_some())); + assert_ok!(Referenda::kill(Origin::root(), 0)); + assert!(!pallet_scheduler::Agenda::::get(7).iter().any(|x| x.is_some())); + assert_eq!(killed_since(0), 6); }); } -#[test] -fn confirming_then_reconfirming_works() { - new_test_ext().execute_with(|| { - }); +fn make_instaconfirm_and_run_until_deciding() -> ReferendumIndex { + assert_ok!(Referenda::submit( + Origin::signed(1), + RawOrigin::Root.into(), + set_balance_proposal_hash(1), + AtOrAfter::At(10), + )); + assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); + set_tally(0, 100, 0); + let index = ReferendumCount::::get() - 1; + while !is_deciding(index) { + run_to(System::block_number() + 1); + } + assert_eq!(confirming_until(index), System::block_number() + 2); + index } -fn is_waiting(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: None, .. })) - ) +fn make_confirming_and_run_until_deciding() -> ReferendumIndex { + assert_ok!(Referenda::submit( + Origin::signed(1), + RawOrigin::Root.into(), + set_balance_proposal_hash(1), + AtOrAfter::At(10), + )); + assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); + let index = ReferendumCount::::get() - 1; + while !is_deciding(index) { + run_to(System::block_number() + 1); + } + set_tally(0, 100, 0); + index } -fn is_deciding(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. })) - ) +fn make_passing_and_run_until_deciding() -> ReferendumIndex { + assert_ok!(Referenda::submit( + Origin::signed(1), + RawOrigin::Root.into(), + set_balance_proposal_hash(1), + AtOrAfter::At(10), + )); + assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); + let index = ReferendumCount::::get() - 1; + while !is_deciding(index) { + run_to(System::block_number() + 1); + } + set_tally(0, 100, 99); + index } -fn is_deciding_and_failing(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Ongoing(ReferendumStatus { - deciding: Some(DecidingStatus { confirming: None, .. }), - .. - })) - ) +#[test] +fn confirm_then_reconfirm_with_elapsed_trigger_works() { + new_test_ext().execute_with(|| { + let r = make_confirming_and_run_until_deciding(); + assert_eq!(confirming_until(r), 7); + run_to(6); + set_tally(r, 100, 99); + assert_eq!(deciding_and_failing_since(r), 5); + run_to(11); + assert_eq!(approved_since(r), 11); + }); } -fn is_confirming(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Ongoing(ReferendumStatus { - deciding: Some(DecidingStatus { confirming: Some(_), .. }), - .. - })) - ) +#[test] +fn instaconfirm_then_reconfirm_with_elapsed_trigger_works() { + new_test_ext().execute_with(|| { + let r = make_instaconfirm_and_run_until_deciding(); + run_to(6); + assert_eq!(confirming_until(r), 7); + set_tally(r, 100, 99); + assert_eq!(deciding_and_failing_since(r), 5); + run_to(11); + assert_eq!(approved_since(r), 11); + }); } -fn is_approved(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Approved(..)) - ) +#[test] +fn instaconfirm_then_reconfirm_with_voting_trigger_works() { + new_test_ext().execute_with(|| { + let r = make_instaconfirm_and_run_until_deciding(); + run_to(6); + assert_eq!(confirming_until(r), 7); + set_tally(r, 100, 99); + assert_eq!(deciding_and_failing_since(r), 5); + run_to(8); + set_tally(r, 100, 0); + assert_eq!(confirming_until(r), 10); + run_to(10); + assert_eq!(approved_since(r), 10); + }); } -fn is_rejected(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Rejected(..)) - ) +#[test] +fn voting_should_extend_for_late_confirmation() { + new_test_ext().execute_with(|| { + let r = make_confirming_and_run_until_deciding(); + run_to(6); + assert_eq!(confirming_until(r), 7); + set_tally(r, 100, 99); + assert_eq!(deciding_and_failing_since(r), 5); + run_to(8); + set_tally(r, 100, 0); + assert_eq!(confirming_until(r), 10); + run_to(10); + assert_eq!(approved_since(r), 10); + }); } -fn is_cancelled(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Cancelled(..)) - ) +#[test] +fn should_instafail_during_extension_confirmation() { + new_test_ext().execute_with(|| { + let r = make_passing_and_run_until_deciding(); + 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); + assert_eq!(rejected_since(r), 10); + }); } -fn is_killed(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Killed(..)) - ) +#[test] +fn confirming_then_fail_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_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); + // Normally ends at 5 + 4 (voting period) = 9. + run_to(6); + assert_eq!(deciding_and_failing_since(0), 5); + set_tally(0, 100, 0); + assert_eq!(confirming_until(0), 8); + run_to(7); + set_tally(0, 100, 101); + run_to(9); + assert_eq!(rejected_since(0), 9); + }); } #[test] @@ -177,6 +271,9 @@ fn queueing_works() { // One should be being decided. assert_eq!(DecidingCount::::get(0), 1); assert!(is_deciding_and_failing(0)); + for i in 1..=4 { + assert!(is_waiting(i)); + } // Vote to set order. set_tally(1, 1, 10); @@ -227,31 +324,6 @@ fn queueing_works() { run_to(18); assert!(is_approved(2)); assert!(is_deciding(3)); -/* - // Vote enough on #2 for it to go into confirming. - set_tally(2, 100, 0); - assert!(is_confirming(2)); - - run_to(14); - set_tally(2, 100, 100); - assert!(is_deciding_and_failing(2)); - - run_to(15); - set_tally(2, 1000, 100); - assert!(is_confirming(2)); - - run_to(17); -*/ - }); -} - -// TODO: Confirm -> Unconfirm -> Reconfirm -> Pass -// TODO: (End) Confirm -> Unconfirm (overtime) -> Pass -// TODO: (End) Confirm -> Unconfirm (overtime) -> Fail - -#[test] -fn kill_when_confirming_works() { - new_test_ext().execute_with(|| { }); } @@ -421,10 +493,7 @@ fn cancel_works() { run_to(8); assert_ok!(Referenda::cancel(Origin::signed(4), 0)); assert_ok!(Referenda::refund_decision_deposit(Origin::signed(3), 0)); - assert_matches!( - ReferendumInfoFor::::get(0).unwrap(), - ReferendumInfo::Cancelled(8, Deposit { who: 1, amount: 2 }, None) - ); + assert!(is_cancelled(0)); }); } @@ -462,7 +531,7 @@ fn kill_works() { assert_ok!(Referenda::kill(Origin::root(), 0)); let e = Error::::NoDeposit; assert_noop!(Referenda::refund_decision_deposit(Origin::signed(3), 0), e); - assert_matches!(ReferendumInfoFor::::get(0).unwrap(), ReferendumInfo::Killed(8)); + assert!(is_killed(0)); }); } From 24b438e6f28e8cb698ff7543eec1febdbfbbb88d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 14 Dec 2021 21:25:07 +0100 Subject: [PATCH 23/66] Formatting --- frame/referenda/src/lib.rs | 18 +-- frame/referenda/src/mock.rs | 128 +++++++++------------ frame/referenda/src/tests.rs | 137 ++++++----------------- frame/referenda/src/types.rs | 4 +- frame/support/src/storage/bounded_vec.rs | 3 +- 5 files changed, 95 insertions(+), 195 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index e6d78a53f51fa..7350c69737f1a 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -40,7 +40,7 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Dispatchable, Zero, One, Saturating}, + traits::{AtLeast32BitUnsigned, Dispatchable, One, Saturating, Zero}, DispatchError, DispatchResult, Perbill, }; use sp_std::{fmt::Debug, prelude::*}; @@ -493,7 +493,10 @@ impl Pallet { } /// Set an alarm. - fn set_alarm(call: impl Into>, when: T::BlockNumber) -> Option<(T::BlockNumber, ScheduleAddressOf)> { + fn set_alarm( + call: impl Into>, + when: T::BlockNumber, + ) -> Option<(T::BlockNumber, ScheduleAddressOf)> { let alarm_interval = T::AlarmInterval::get().max(One::one()); let when = (when + alarm_interval - One::one()) / alarm_interval * alarm_interval; let maybe_result = T::Scheduler::schedule( @@ -503,8 +506,8 @@ impl Pallet { frame_system::RawOrigin::Root.into(), MaybeHashed::Value(call.into()), ) - .ok() - .map(|x| (when, x)); + .ok() + .map(|x| (when, x)); debug_assert!( maybe_result.is_some(), "Unable to schedule a new alarm at #{} (now: #{})?!", @@ -543,11 +546,8 @@ impl Pallet { &track.min_approval, ); status.ayes_in_queue = None; - let confirming = if is_passing { - Some(now.saturating_add(track.confirm_period)) - } else { - None - }; + let confirming = + if is_passing { Some(now.saturating_add(track.confirm_period)) } else { None }; let deciding_status = DecidingStatus { since: now, confirming }; let alarm = Self::decision_time(&deciding_status, &status.tally, track); dbg!(alarm); diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index a3417a6a53869..fa56a1b525323 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -346,68 +346,6 @@ pub fn set_tally(index: ReferendumIndex, ayes: u32, nays: u32) { }); } -pub fn is_waiting(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: None, .. })) - ) -} - -pub fn is_deciding(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. })) - ) -} - -pub fn is_deciding_and_failing(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Ongoing(ReferendumStatus { - deciding: Some(DecidingStatus { confirming: None, .. }), - .. - })) - ) -} - -pub fn is_confirming(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Ongoing(ReferendumStatus { - deciding: Some(DecidingStatus { confirming: Some(_), .. }), - .. - })) - ) -} - -pub fn is_approved(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Approved(..)) - ) -} - -pub fn is_rejected(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Rejected(..)) - ) -} - -pub fn is_cancelled(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Cancelled(..)) - ) -} - -pub fn is_killed(i: ReferendumIndex) -> bool { - matches!( - ReferendumInfoFor::::get(i), - Some(ReferendumInfo::Killed(..)) - ) -} - pub fn waiting_since(i: ReferendumIndex) -> u64 { match ReferendumInfoFor::::get(i).unwrap() { ReferendumInfo::Ongoing(ReferendumStatus { submitted, deciding: None, .. }) => submitted, @@ -417,8 +355,10 @@ pub fn waiting_since(i: ReferendumIndex) -> u64 { pub fn deciding_since(i: ReferendumIndex) -> u64 { match ReferendumInfoFor::::get(i).unwrap() { - ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(DecidingStatus { since, .. }), .. }) - => since, + ReferendumInfo::Ongoing(ReferendumStatus { + deciding: Some(DecidingStatus { since, .. }), + .. + }) => since, _ => panic!("Not deciding"), } } @@ -426,14 +366,9 @@ pub fn deciding_since(i: ReferendumIndex) -> u64 { pub fn deciding_and_failing_since(i: ReferendumIndex) -> u64 { match ReferendumInfoFor::::get(i).unwrap() { ReferendumInfo::Ongoing(ReferendumStatus { - deciding: Some(DecidingStatus { - since, - confirming: None, - .. - }), + deciding: Some(DecidingStatus { since, confirming: None, .. }), .. - }) - => since, + }) => since, _ => panic!("Not deciding"), } } @@ -441,13 +376,9 @@ pub fn deciding_and_failing_since(i: ReferendumIndex) -> u64 { pub fn confirming_until(i: ReferendumIndex) -> u64 { match ReferendumInfoFor::::get(i).unwrap() { ReferendumInfo::Ongoing(ReferendumStatus { - deciding: Some(DecidingStatus { - confirming: Some(until), - .. - }), + deciding: Some(DecidingStatus { confirming: Some(until), .. }), .. - }) - => until, + }) => until, _ => panic!("Not confirming"), } } @@ -479,3 +410,46 @@ pub fn killed_since(i: ReferendumIndex) -> u64 { _ => panic!("Not killed"), } } + +fn is_deciding(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::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::::get() - 1; + while !is_deciding(index) { + run_to(System::block_number() + 1); + } + if matches!(self, RefState::Confirming { immediate: false }) { + set_tally(0, 100, 0); + } + if matches!(self, RefState::Confirming { .. }) { + assert_eq!(confirming_until(index), System::block_number() + 2); + } + if matches!(self, RefState::Passing) { + set_tally(0, 100, 99); + } + index + } +} diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 5c9ea94fd91d7..85487d7641a1d 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -18,7 +18,7 @@ //! The crate's tests. use super::*; -use crate::mock::*; +use crate::mock::{RefState::*, *}; use assert_matches::assert_matches; use codec::Decode; use frame_support::{ @@ -72,85 +72,22 @@ fn basic_happy_path_works() { }); } -// TODO: Confirm -> Kill -> Fail -// TODO: Confirm -> Unconfirm -> Reconfirm -> Pass -// TODO: (End) Confirm -> Unconfirm (overtime) -> Pass -// TODO: (End) Confirm -> Unconfirm (overtime) -> Fail - #[test] fn insta_confirm_then_kill_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_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); - // Insta-confirm - set_tally(0, 100, 0); + let r = Confirming { immediate: true }.create(); run_to(6); assert!(pallet_scheduler::Agenda::::get(7).iter().any(|x| x.is_some())); - assert_ok!(Referenda::kill(Origin::root(), 0)); + assert_ok!(Referenda::kill(Origin::root(), r)); assert!(!pallet_scheduler::Agenda::::get(7).iter().any(|x| x.is_some())); - assert_eq!(killed_since(0), 6); + assert_eq!(killed_since(r), 6); }); } -fn make_instaconfirm_and_run_until_deciding() -> ReferendumIndex { - assert_ok!(Referenda::submit( - Origin::signed(1), - RawOrigin::Root.into(), - set_balance_proposal_hash(1), - AtOrAfter::At(10), - )); - assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); - set_tally(0, 100, 0); - let index = ReferendumCount::::get() - 1; - while !is_deciding(index) { - run_to(System::block_number() + 1); - } - assert_eq!(confirming_until(index), System::block_number() + 2); - index -} - -fn make_confirming_and_run_until_deciding() -> ReferendumIndex { - assert_ok!(Referenda::submit( - Origin::signed(1), - RawOrigin::Root.into(), - set_balance_proposal_hash(1), - AtOrAfter::At(10), - )); - assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); - let index = ReferendumCount::::get() - 1; - while !is_deciding(index) { - run_to(System::block_number() + 1); - } - set_tally(0, 100, 0); - index -} - -fn make_passing_and_run_until_deciding() -> ReferendumIndex { - assert_ok!(Referenda::submit( - Origin::signed(1), - RawOrigin::Root.into(), - set_balance_proposal_hash(1), - AtOrAfter::At(10), - )); - assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); - let index = ReferendumCount::::get() - 1; - while !is_deciding(index) { - run_to(System::block_number() + 1); - } - set_tally(0, 100, 99); - index -} - #[test] fn confirm_then_reconfirm_with_elapsed_trigger_works() { new_test_ext().execute_with(|| { - let r = make_confirming_and_run_until_deciding(); + let r = Confirming { immediate: false }.create(); assert_eq!(confirming_until(r), 7); run_to(6); set_tally(r, 100, 99); @@ -163,7 +100,7 @@ fn confirm_then_reconfirm_with_elapsed_trigger_works() { #[test] fn instaconfirm_then_reconfirm_with_elapsed_trigger_works() { new_test_ext().execute_with(|| { - let r = make_instaconfirm_and_run_until_deciding(); + let r = Confirming { immediate: true }.create(); run_to(6); assert_eq!(confirming_until(r), 7); set_tally(r, 100, 99); @@ -176,7 +113,7 @@ fn instaconfirm_then_reconfirm_with_elapsed_trigger_works() { #[test] fn instaconfirm_then_reconfirm_with_voting_trigger_works() { new_test_ext().execute_with(|| { - let r = make_instaconfirm_and_run_until_deciding(); + let r = Confirming { immediate: true }.create(); run_to(6); assert_eq!(confirming_until(r), 7); set_tally(r, 100, 99); @@ -192,7 +129,7 @@ fn instaconfirm_then_reconfirm_with_voting_trigger_works() { #[test] fn voting_should_extend_for_late_confirmation() { new_test_ext().execute_with(|| { - let r = make_confirming_and_run_until_deciding(); + let r = Confirming { immediate: false }.create(); run_to(6); assert_eq!(confirming_until(r), 7); set_tally(r, 100, 99); @@ -208,7 +145,7 @@ fn voting_should_extend_for_late_confirmation() { #[test] fn should_instafail_during_extension_confirmation() { new_test_ext().execute_with(|| { - let r = make_passing_and_run_until_deciding(); + 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. @@ -220,23 +157,16 @@ fn should_instafail_during_extension_confirmation() { #[test] fn confirming_then_fail_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_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); + let r = Failing.create(); // Normally ends at 5 + 4 (voting period) = 9. run_to(6); - assert_eq!(deciding_and_failing_since(0), 5); - set_tally(0, 100, 0); - assert_eq!(confirming_until(0), 8); + assert_eq!(deciding_and_failing_since(r), 5); + set_tally(r, 100, 0); + assert_eq!(confirming_until(r), 8); run_to(7); - set_tally(0, 100, 101); + set_tally(r, 100, 101); run_to(9); - assert_eq!(rejected_since(0), 9); + assert_eq!(rejected_since(r), 9); }); } @@ -263,16 +193,17 @@ fn queueing_works() { 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. + // TODO: decision deposit after some initial votes with a non-highest voted coming + // first. } assert_eq!(ReferendumCount::::get(), 5); run_to(5); // One should be being decided. assert_eq!(DecidingCount::::get(0), 1); - assert!(is_deciding_and_failing(0)); + assert_eq!(deciding_and_failing_since(0), 5); for i in 1..=4 { - assert!(is_waiting(i)); + assert_eq!(waiting_since(i), 2); } // Vote to set order. @@ -286,12 +217,12 @@ fn queueing_works() { // Cancel the first. assert_ok!(Referenda::cancel(Origin::signed(4), 0)); - assert!(is_cancelled(0)); + assert_eq!(cancelled_since(0), 6); // The other with the most approvals (#4) should be being decided. assert_eq!(DecidingCount::::get(0), 1); - assert!(is_deciding(4)); - assert!(is_confirming(4)); + assert_eq!(deciding_since(4), 6); + assert_eq!(confirming_until(4), 8); // Vote on the remaining two to change order. println!("Set tally #1"); @@ -305,25 +236,25 @@ fn queueing_works() { run_to(8); // #4 should have been confirmed. - assert!(is_approved(4)); + assert_eq!(approved_since(4), 8); // #1 (the one with the most approvals) should now be being decided. - assert!(is_deciding(1)); + assert_eq!(deciding_since(1), 8); // Let it end unsuccessfully. run_to(12); - assert!(is_rejected(1)); + assert_eq!(rejected_since(1), 12); // #2 should now be being decided. It will (barely) pass. - assert!(is_deciding_and_failing(2)); + assert_eq!(deciding_and_failing_since(2), 12); // #2 moves into confirming at the last moment with a 50% approval. run_to(16); - assert!(is_confirming(2)); + assert_eq!(confirming_until(2), 18); // #2 gets approved. run_to(18); - assert!(is_approved(2)); - assert!(is_deciding(3)); + assert_eq!(approved_since(2), 18); + assert_eq!(deciding_since(3), 18); }); } @@ -338,17 +269,13 @@ fn auto_timeout_should_happen_with_nothing_but_submit() { AtOrAfter::At(20), )); run_to(20); - assert_matches!( - ReferendumInfoFor::::get(0), - Some(ReferendumInfo::Ongoing(..)) - ); + assert_matches!(ReferendumInfoFor::::get(0), Some(ReferendumInfo::Ongoing(..))); run_to(21); // #11: Timed out - ended. assert_matches!( ReferendumInfoFor::::get(0), Some(ReferendumInfo::TimedOut(21, _, None)) ); - }); } @@ -493,7 +420,7 @@ fn cancel_works() { run_to(8); assert_ok!(Referenda::cancel(Origin::signed(4), 0)); assert_ok!(Referenda::refund_decision_deposit(Origin::signed(3), 0)); - assert!(is_cancelled(0)); + assert_eq!(cancelled_since(0), 8); }); } @@ -531,7 +458,7 @@ fn kill_works() { assert_ok!(Referenda::kill(Origin::root(), 0)); let e = Error::::NoDeposit; assert_noop!(Referenda::refund_decision_deposit(Origin::signed(3), 0), e); - assert!(is_killed(0)); + assert_eq!(killed_since(0), 8); }); } diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 93451e87fb70f..04e6514374715 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -343,7 +343,8 @@ impl sp_std::fmt::Debug for Curve { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { match self { Self::LinearDecreasing { begin, delta } => { - write!(f, + write!( + f, "Linear[(0%, {}%) -> (100%, {}%)]", *begin * 100u32, (*begin - *delta) * 100u32, @@ -352,4 +353,3 @@ impl sp_std::fmt::Debug for Curve { } } } - diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 33f97ea7f193a..094bc33edeb41 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -434,9 +434,8 @@ where #[cfg(test)] pub mod test { use super::*; - use crate::Twox128; + use crate::{traits::ConstU32, Twox128}; use sp_io::TestExternalities; - use crate::traits::ConstU32; crate::parameter_types! { pub const Seven: u32 = 7; From e9de7f538d6a5df1de94eab3afc62d12b01fc46e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 15 Dec 2021 01:26:14 +0100 Subject: [PATCH 24/66] First few benchmarks --- frame/referenda/Cargo.toml | 4 +- frame/referenda/src/benchmarking.rs | 159 +++++++++++++++++----------- frame/referenda/src/lib.rs | 10 +- 3 files changed, 106 insertions(+), 67 deletions(-) diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index 61099f515d6f6..224438435a6a3 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -24,13 +24,14 @@ sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../pr 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] -assert_matches = "1.5" sp-core = { version = "4.1.0-dev", 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"] @@ -50,5 +51,6 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "frame-support/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "assert_matches", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index cd14c78c10df7..b672841302653 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -18,22 +18,16 @@ //! Democracy pallet benchmarking. use super::*; - use frame_benchmarking::{account, benchmarks, whitelist_account}; -use frame_support::{ - assert_noop, assert_ok, - codec::Decode, - traits::{ - schedule::DispatchTime, Currency, EnsureOrigin, Get, OnInitialize, UnfilteredDispatchable, - }, -}; -use frame_system::{Pallet as System, RawOrigin}; -use sp_runtime::traits::{BadOrigin, Bounded, One}; - -use crate::Pallet as Democracy; +use frame_support::{assert_ok, traits::{Currency, EnsureOrigin}}; +use frame_system::RawOrigin; +use sp_runtime::traits::{Bounded, Hash}; +use assert_matches::assert_matches; +use crate::Pallet as Referenda; const SEED: u32 = 0; +#[allow(dead_code)] fn assert_last_event(generic_event: ::Event) { frame_system::Pallet::::assert_last_event(generic_event.into()); } @@ -43,66 +37,107 @@ fn funded_account(name: &'static str, index: u32) -> T::AccountId { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); caller } -/* -fn add_proposal(track: TrackIfOf, n: u32) -> Result { - let other = funded_account::("proposer", n); - let value = T::SubmissionDeposit::get(); - let proposal_hash: T::Hash = T::Hashing::hash_of(&n); - - Referenda::::submit(RawOrigin::Signed(other).into(), proposal_hash, AtOrAfter::After(0))?; - - Ok(proposal_hash) -} - -fn add_referendum(n: u32) -> Result { - let proposal_hash: T::Hash = T::Hashing::hash_of(&n); - let vote_threshold = VoteThreshold::SimpleMajority; - - Democracy::::inject_referendum( - T::LaunchPeriod::get(), - proposal_hash, - vote_threshold, - 0u32.into(), - ); - let referendum_index: ReferendumIndex = ReferendumCount::::get() - 1; - T::Scheduler::schedule_named( - (DEMOCRACY_ID, referendum_index).encode(), - DispatchTime::At(2u32.into()), - None, - 63, - frame_system::RawOrigin::Root.into(), - Call::enact_proposal { proposal_hash, index: referendum_index }.into(), - ) - .map_err(|_| "failed to schedule named")?; - Ok(referendum_index) -} - -fn account_vote(b: BalanceOf) -> AccountVote> { - let v = Vote { aye: true, conviction: Conviction::Locked1x }; - AccountVote::Standard { vote: v, balance: b } -} -*/ benchmarks! { submit { - let p = T::MaxProposals::get(); + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + }: _( + RawOrigin::Signed(caller), + RawOrigin::Root.into(), + T::Hashing::hash_of(&0), + AtOrAfter::After(0u32.into()) + ) verify { + let index = ReferendumCount::::get().checked_sub(1).unwrap(); + let status = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(status, ReferendumInfo::Ongoing(_)); + } - for i in 0 .. (p - 1) { - add_proposal::(i)?; - } + place_decision_deposit { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let origin = move || RawOrigin::Signed(caller.clone()); + assert_ok!(Referenda::::submit( + origin().into(), + RawOrigin::Root.into(), + T::Hashing::hash_of(&0), + AtOrAfter::After(0u32.into()) + )); + let index = ReferendumCount::::get().checked_sub(1).unwrap(); + }: _(origin(), index) + verify { + let status = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(status, ReferendumInfo::Ongoing(ReferendumStatus { + decision_deposit: Some(..), + .. + })); + } + refund_decision_deposit { let caller = funded_account::("caller", 0); - let proposal_hash: T::Hash = T::Hashing::hash_of(&0); - let value = T::MinimumDeposit::get(); whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), proposal_hash, value.into()) + let origin = move || RawOrigin::Signed(caller.clone()); + assert_ok!(Referenda::::submit( + origin().into(), + RawOrigin::Root.into(), + T::Hashing::hash_of(&0), + AtOrAfter::After(0u32.into()) + )); + let index = ReferendumCount::::get().checked_sub(1).unwrap(); + assert_ok!(Referenda::::place_decision_deposit(origin().into(), index)); + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); + }: _(origin(), index) verify { - assert_eq!(Democracy::::public_props().len(), p as usize, "Proposals not created."); + let status = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(status, ReferendumInfo::Cancelled(_, _, None)); } + cancel { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let origin = move || RawOrigin::Signed(caller.clone()); + assert_ok!(Referenda::::submit( + origin().into(), + RawOrigin::Root.into(), + T::Hashing::hash_of(&0), + AtOrAfter::After(0u32.into()) + )); + let index = ReferendumCount::::get().checked_sub(1).unwrap(); + assert_ok!(Referenda::::place_decision_deposit(origin().into(), index)); + }: _(T::CancelOrigin::successful_origin(), index) + verify { + let status = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(status, ReferendumInfo::Cancelled(..)); + } + + kill { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let origin = move || RawOrigin::Signed(caller.clone()); + assert_ok!(Referenda::::submit( + origin().into(), + RawOrigin::Root.into(), + T::Hashing::hash_of(&0), + AtOrAfter::After(0u32.into()) + )); + let index = ReferendumCount::::get().checked_sub(1).unwrap(); + assert_ok!(Referenda::::place_decision_deposit(origin().into(), index)); + }: _(T::KillOrigin::successful_origin(), index) + verify { + let status = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(status, ReferendumInfo::Killed(..)); + } +/* + nudge_referendum { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + }: _(RawOrigin::Signed(caller), 0) + verify { + } +*/ impl_benchmark_test_suite!( - Democracy, - crate::tests::new_test_ext(), - crate::tests::Test + Referenda, + crate::mock::new_test_ext(), + crate::mock::Test ); } diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 7350c69737f1a..a267e7822fda3 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -61,15 +61,15 @@ mod mock; #[cfg(test)] mod tests; -//#[cfg(feature = "runtime-benchmarks")] -//pub mod benchmarking; +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; const ASSEMBLY_ID: LockIdentifier = *b"assembly"; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, traits::EnsureOrigin, weights::Pays, Parameter}; + use frame_support::{pallet_prelude::*, traits::EnsureOrigin, Parameter}; use frame_system::pallet_prelude::*; use sp_runtime::DispatchResult; @@ -395,7 +395,7 @@ pub mod pallet { Self::advance_referendum(index)?; Ok(()) } - +/* /// Advance a track onto its next logical state. This should happen automaically, /// but this exists in order to allow it to happen manaully in case it is needed. #[pallet::weight(0)] @@ -407,6 +407,7 @@ pub mod pallet { Self::advance_track(track)?; Ok(Pays::No.into()) } +*/ } } @@ -596,6 +597,7 @@ impl Pallet { /// `max_deciding`. /// /// This should never be needed, since referenda should automatically begin when others end. + #[allow(dead_code)] fn advance_track(track: TrackIdOf) -> Result { let track_info = Self::track(track).ok_or(Error::::BadTrack)?; let mut deciding_count = DecidingCount::::get(track); From 92d19890cd92ee0662a08355ec04909b1caa0db4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 15 Dec 2021 11:25:13 +0100 Subject: [PATCH 25/66] First few benchmarks --- frame/referenda/src/benchmarking.rs | 70 +++++++++++++++++++++++++---- frame/referenda/src/lib.rs | 55 ++++++++++++----------- frame/referenda/src/types.rs | 8 +++- 3 files changed, 97 insertions(+), 36 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index b672841302653..aa82d305c6973 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -38,6 +38,25 @@ fn funded_account(name: &'static str, index: u32) -> T::AccountId { caller } +fn create_referendum() -> (T::AccountId, ReferendumIndex) { + funded_account::("caller", 0); + whitelist_account!(caller); + assert_ok!(Referenda::::submit( + RawOrigin::Signed(caller.clone()).into(), + RawOrigin::Root.into(), + T::Hashing::hash_of(&0), + AtOrAfter::After(0u32.into()) + )); + (caller, ReferendumCount::::get() - 1) +} + +fn decision_period() -> T::BlockNumber { + let id = T::Tracks::track_for(&RawOrigin::Root.into()) + .expect("Root should always be a gpvernance origin"); + let info = T::Tracks::info_for(id).expect("Id value returned from T::Tracks"); + info.decision_period +} + benchmarks! { submit { let caller = funded_account::("caller", 0); @@ -53,6 +72,9 @@ benchmarks! { assert_matches!(status, ReferendumInfo::Ongoing(_)); } + // TODO: Track at capacity. + // TODO: Track not at capacity and vote failing. + // TODO: Track at capacity and vote passing. place_decision_deposit { let caller = funded_account::("caller", 0); whitelist_account!(caller); @@ -92,6 +114,8 @@ benchmarks! { assert_matches!(status, ReferendumInfo::Cancelled(_, _, None)); } + // TODO: When track empty. + // TODO: When track not empty. cancel { let caller = funded_account::("caller", 0); whitelist_account!(caller); @@ -104,12 +128,14 @@ benchmarks! { )); let index = ReferendumCount::::get().checked_sub(1).unwrap(); assert_ok!(Referenda::::place_decision_deposit(origin().into(), index)); - }: _(T::CancelOrigin::successful_origin(), index) + }: _(T::CancelOrigin::successful_origin(), index) verify { let status = ReferendumInfoFor::::get(index).unwrap(); assert_matches!(status, ReferendumInfo::Cancelled(..)); } + // TODO: When track empty. + // TODO: When track not empty. kill { let caller = funded_account::("caller", 0); whitelist_account!(caller); @@ -122,19 +148,47 @@ benchmarks! { )); let index = ReferendumCount::::get().checked_sub(1).unwrap(); assert_ok!(Referenda::::place_decision_deposit(origin().into(), index)); - }: _(T::KillOrigin::successful_origin(), index) + }: _(T::KillOrigin::successful_origin(), index) verify { let status = ReferendumInfoFor::::get(index).unwrap(); assert_matches!(status, ReferendumInfo::Killed(..)); } -/* - nudge_referendum { - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), 0) + + // Not deciding -> not deciding + // TODO: not deciding, not queued, no DD paid, PP done + // TODO: not deciding, not queued, DD paid, PP not done + + // Not deciding -> deciding + // TODO: not deciding, not queued, DD paid, PP (just) done, track empty, passing + // TODO: not deciding, not queued, DD paid, PP (just) done, track empty, failing + + // Not deciding -> not deciding (queued) + // TODO: not deciding, not queued, DD paid, PP (just) done, track full + + // Not deciding (queued) -> not deciding (queued) + // TODO: not deciding, queued, since removed + // TODO: not deciding, queued, still in but slide needed + + // Deciding -> deciding + // TODO: deciding, passing, not confirming + // TODO: deciding, passing, confirming, confirmation period not over + // TODO: deciding, failing, confirming, decision period not over + // TODO: deciding, failing, not confirming, decision period not over + + // Deciding -> end + // TODO: deciding, passing, confirming, confirmation period over (accepted) + // TODO: deciding, failing, decision period over (rejected) + + // Not deciding -> end + // TODO: not deciding, timeout + + nudge_referendum_no_dd_pp_over { + let (_caller, index) = create_referendum(); + System::set_block_number(System::block_number() + decision_period::()); + }: _(RawOrigin::Root, index) verify { } -*/ + impl_benchmark_test_suite!( Referenda, crate::mock::new_test_ext(), diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index a267e7822fda3..3b6c085ac3c59 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -392,7 +392,12 @@ pub mod pallet { #[pallet::weight(0)] pub fn nudge_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { ensure_signed_or_root(origin)?; - Self::advance_referendum(index)?; + let now = frame_system::Pallet::::block_number(); + let status = Self::ensure_ongoing(index)?; + let (info, dirty) = Self::service_referendum(now, index, status); + if dirty { + ReferendumInfoFor::::insert(index, info); + } Ok(()) } /* @@ -425,8 +430,8 @@ impl Polls for Pallet { Some(ReferendumInfo::Ongoing(mut status)) => { let result = f(PollStatus::Ongoing(&mut status.tally)); let now = frame_system::Pallet::::block_number(); - let (info, _) = Self::service_referendum(now, index, status); - ReferendumInfoFor::::insert(index, info); + Self::ensure_alarm_at(&mut status, index, now + One::one()); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); result }, Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), @@ -443,8 +448,8 @@ impl Polls for Pallet { Some(ReferendumInfo::Ongoing(mut status)) => { let result = f(PollStatus::Ongoing(&mut status.tally))?; let now = frame_system::Pallet::::block_number(); - let (info, _) = Self::service_referendum(now, index, status); - ReferendumInfoFor::::insert(index, info); + Self::ensure_alarm_at(&mut status, index, now + One::one()); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); Ok(result) }, Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), @@ -529,7 +534,7 @@ impl Pallet { index: ReferendumIndex, now: T::BlockNumber, track: &TrackInfoOf, - ) { + ) -> Option { println!("Begining deciding for ref #{:?} at block #{:?}", index, now); let is_passing = Self::is_passing( &status.tally, @@ -551,9 +556,8 @@ impl Pallet { if is_passing { Some(now.saturating_add(track.confirm_period)) } else { None }; let deciding_status = DecidingStatus { since: now, confirming }; let alarm = Self::decision_time(&deciding_status, &status.tally, track); - dbg!(alarm); - Self::ensure_alarm_at(status, index, alarm); status.deciding = Some(deciding_status); + Some(alarm) } fn ready_for_deciding( @@ -561,20 +565,21 @@ impl Pallet { track: &TrackInfoOf, index: ReferendumIndex, status: &mut ReferendumStatusOf, - ) { + ) -> Option { println!("ready_for_deciding ref #{:?}", index); let deciding_count = DecidingCount::::get(status.track); if deciding_count < track.max_deciding { // Begin deciding. println!("Beginning deciding..."); - Self::begin_deciding(status, index, now, track); DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); + Self::begin_deciding(status, index, now, track) } else { // Add to queue. println!("Queuing for decision."); let item = (index, status.tally.ayes()); status.ayes_in_queue = Some(status.tally.ayes()); TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)); + None } } @@ -606,7 +611,10 @@ impl Pallet { while deciding_count < track_info.max_deciding { if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { let now = frame_system::Pallet::::block_number(); - Self::begin_deciding(&mut status, index, now, &track_info); + let maybe_set_alarm = Self::begin_deciding(&mut status, index, now, &track_info); + if let Some(set_alarm) = maybe_set_alarm { + Self::ensure_alarm_at(&mut status, index, set_alarm); + } ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); deciding_count.saturating_inc(); count.saturating_inc(); @@ -621,17 +629,6 @@ impl Pallet { Ok(count) } - /// Attempts to advance the referendum. Returns `Ok` if something useful happened. - fn advance_referendum(index: ReferendumIndex) -> DispatchResult { - let now = frame_system::Pallet::::block_number(); - let status = Self::ensure_ongoing(index)?; - let (info, dirty) = Self::service_referendum(now, index, status); - if dirty { - ReferendumInfoFor::::insert(index, info); - } - Ok(()) - } - /// Action item for when there is now one fewer referendum in the deciding phase and the /// `DecidingCount` is not yet updated. This means that we should either: /// - begin deciding another referendum (and leave `DecidingCount` alone); or @@ -642,7 +639,10 @@ impl Pallet { if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { println!("Ref #{:?} ready for deciding", index); let now = frame_system::Pallet::::block_number(); - Self::begin_deciding(&mut status, index, now, track_info); + let maybe_set_alarm = Self::begin_deciding(&mut status, index, now, track_info); + if let Some(set_alarm) = maybe_set_alarm { + Self::ensure_alarm_at(&mut status, index, set_alarm); + } ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); TrackQueue::::insert(track, track_queue); } else { @@ -721,7 +721,7 @@ impl Pallet { // Just insert. queue.force_insert_keep_right(new_pos, (index, ayes)); } else if let Some(old_pos) = maybe_old_pos { - // We were in the queue - just update and sort. + // We were in the queue - slide into the correct position. queue[old_pos].1 = ayes; queue.slide(old_pos, new_pos); } @@ -731,7 +731,9 @@ impl Pallet { if status.decision_deposit.is_some() { let prepare_end = status.submitted.saturating_add(track.prepare_period); if now >= prepare_end { - Self::ready_for_deciding(now, &track, index, &mut status); + if let Some(set_alarm) = Self::ready_for_deciding(now, &track, index, &mut status) { + alarm = alarm.min(set_alarm); + } dirty = true; } else { println!("Not deciding, have DD, within PP: Setting alarm to end of PP"); @@ -739,8 +741,7 @@ impl Pallet { } } } - } - if let Some(deciding) = &mut status.deciding { + } else if let Some(deciding) = &mut status.deciding { let is_passing = Self::is_passing( &status.tally, now.saturating_sub(deciding.since), diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 04e6514374715..15bb7878ac829 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -170,7 +170,13 @@ pub trait TracksInfo { type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync; type Origin; fn tracks() -> &'static [(Self::Id, TrackInfo)]; - fn track_for(id: &Self::Origin) -> Result; + fn track_for(origin: &Self::Origin) -> Result; + fn info(id: Self::Id) -> Option<&'static TrackInfo> { + 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. From 52a3da50c30484b64403dac73bf1ebb329bb190a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 15 Dec 2021 12:35:48 +0100 Subject: [PATCH 26/66] Defered queue servicing --- frame/referenda/src/benchmarking.rs | 47 +++++++---- frame/referenda/src/lib.rs | 125 ++++++++++++++-------------- frame/referenda/src/mock.rs | 9 ++ frame/referenda/src/tests.rs | 79 ++++++++++-------- frame/referenda/src/types.rs | 2 +- 5 files changed, 148 insertions(+), 114 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index aa82d305c6973..c4d0244ad31a0 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -38,8 +38,8 @@ fn funded_account(name: &'static str, index: u32) -> T::AccountId { caller } -fn create_referendum() -> (T::AccountId, ReferendumIndex) { - funded_account::("caller", 0); +fn create_referendum(deposit: bool) -> (T::AccountId, ReferendumIndex) { + let caller = funded_account::("caller", 0); whitelist_account!(caller); assert_ok!(Referenda::::submit( RawOrigin::Signed(caller.clone()).into(), @@ -47,13 +47,20 @@ fn create_referendum() -> (T::AccountId, ReferendumIndex) { T::Hashing::hash_of(&0), AtOrAfter::After(0u32.into()) )); - (caller, ReferendumCount::::get() - 1) + let index = ReferendumCount::::get() - 1; + if deposit { + assert_ok!(Referenda::::place_decision_deposit( + RawOrigin::Signed(caller.clone()).into(), + index, + )); + } + (caller, index) } -fn decision_period() -> T::BlockNumber { +fn decision_period() -> T::BlockNumber { let id = T::Tracks::track_for(&RawOrigin::Root.into()) .expect("Root should always be a gpvernance origin"); - let info = T::Tracks::info_for(id).expect("Id value returned from T::Tracks"); + let info = T::Tracks::info(id).expect("Id value returned from T::Tracks"); info.decision_period } @@ -155,12 +162,8 @@ benchmarks! { } // Not deciding -> not deciding - // TODO: not deciding, not queued, no DD paid, PP done - // TODO: not deciding, not queued, DD paid, PP not done - - // Not deciding -> deciding - // TODO: not deciding, not queued, DD paid, PP (just) done, track empty, passing - // TODO: not deciding, not queued, DD paid, PP (just) done, track empty, failing + // DONE: not deciding, not queued, no DD paid, PP done + // DONE: not deciding, not queued, DD paid, PP not done // Not deciding -> not deciding (queued) // TODO: not deciding, not queued, DD paid, PP (just) done, track full @@ -169,6 +172,10 @@ benchmarks! { // TODO: not deciding, queued, since removed // TODO: not deciding, queued, still in but slide needed + // Not deciding -> deciding + // TODO: not deciding, not queued, DD paid, PP (just) done, track empty, passing + // TODO: not deciding, not queued, DD paid, PP (just) done, track empty, failing + // Deciding -> deciding // TODO: deciding, passing, not confirming // TODO: deciding, passing, confirming, confirmation period not over @@ -183,10 +190,22 @@ benchmarks! { // TODO: not deciding, timeout nudge_referendum_no_dd_pp_over { - let (_caller, index) = create_referendum(); - System::set_block_number(System::block_number() + decision_period::()); - }: _(RawOrigin::Root, index) + let (_caller, index) = create_referendum::(false); + let decision_period_over = frame_system::Pallet::::block_number() + decision_period::(); + frame_system::Pallet::::set_block_number(decision_period_over); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let status = Referenda::::ensure_ongoing(index).unwrap(); + assert_matches!(status, ReferendumStatus { deciding: None, .. }); + } + + nudge_referendum_dd_pp_still { + let (_caller, index) = create_referendum::(true); + let decision_period_over = frame_system::Pallet::::block_number() + decision_period::(); + }: nudge_referendum(RawOrigin::Root, index) verify { + let status = Referenda::::ensure_ongoing(index).unwrap(); + assert_matches!(status, ReferendumStatus { deciding: None, .. }); } impl_benchmark_test_suite!( diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 3b6c085ac3c59..9e3fa4d899141 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -387,11 +387,10 @@ pub mod pallet { Ok(()) } - /// Advance a referendum onto its next logical state. This should happen automaically, - /// but this exists in order to allow it to happen manaully in case it is needed. + /// Advance a referendum onto its next logical state. Only used internally. #[pallet::weight(0)] pub fn nudge_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { - ensure_signed_or_root(origin)?; + ensure_root(origin)?; let now = frame_system::Pallet::::block_number(); let status = Self::ensure_ongoing(index)?; let (info, dirty) = Self::service_referendum(now, index, status); @@ -400,19 +399,17 @@ pub mod pallet { } Ok(()) } -/* - /// Advance a track onto its next logical state. This should happen automaically, - /// but this exists in order to allow it to happen manaully in case it is needed. + + /// Advance a track onto its next logical state. Only used internally. #[pallet::weight(0)] - pub fn nudge_track( + pub fn defer_one_fewer_deciding( origin: OriginFor, track: TrackIdOf, - ) -> DispatchResultWithPostInfo { - ensure_signed_or_root(origin)?; - Self::advance_track(track)?; - Ok(Pays::No.into()) + ) -> DispatchResult { + ensure_root(origin)?; + Self::act_one_fewer_deciding(track); + Ok(()) } -*/ } } @@ -592,65 +589,63 @@ impl Pallet { let (index, _) = track_queue.pop()?; match Self::ensure_ongoing(index) { Ok(s) => return Some((index, s)), - Err(_) => debug_assert!(false, "Queued referendum not ongoing?!"), + Err(_) => {}, // referendum already timedout or was cancelled. } } } - /// Advance a track - this dequeues one or more referenda from the from the `TrackQueue` of - /// referenda which are ready to be decided until the `DecidingCount` is equal to the track's - /// `max_deciding`. + /// Schedule a call to `act_one_fewer_deciding` function via the dispatchable + /// `defer_one_fewer_deciding`. We could theoretically call it immediately (and it would be + /// overall more efficient), however the weights become rather less easy to measure. + fn note_one_fewer_deciding(track: TrackIdOf, _track_info: &TrackInfoOf) { + // Set an alarm call for the next block to nudge the track along. + let now = frame_system::Pallet::::block_number(); + let next_block = now + One::one(); + let alarm_interval = T::AlarmInterval::get().max(One::one()); + let when = (next_block + alarm_interval - One::one()) / alarm_interval * alarm_interval; + + let maybe_result = T::Scheduler::schedule( + DispatchTime::At(when), + None, + 128u8, + frame_system::RawOrigin::Root.into(), + MaybeHashed::Value(Call::defer_one_fewer_deciding { track }.into()), + ); + debug_assert!( + maybe_result.is_ok(), + "Unable to schedule a new alarm at #{} (now: #{})?!", + when, + now + ); + } + + /// Action item for when there is now one fewer referendum in the deciding phase and the + /// `DecidingCount` is not yet updated. This means that we should either: + /// - begin deciding another referendum (and leave `DecidingCount` alone); or + /// - decrement `DecidingCount`. /// - /// This should never be needed, since referenda should automatically begin when others end. - #[allow(dead_code)] - fn advance_track(track: TrackIdOf) -> Result { - let track_info = Self::track(track).ok_or(Error::::BadTrack)?; - let mut deciding_count = DecidingCount::::get(track); - let mut track_queue = TrackQueue::::get(track); - let mut count = 0; - while deciding_count < track_info.max_deciding { + /// We defer the actual action to `act_one_fewer_deciding` function via a call in order to + /// simplify weight considerations. + fn act_one_fewer_deciding(track: TrackIdOf) { + if let Some(track_info) = T::Tracks::info(track) { + println!("One fewer deciding on {:?}", track); + let mut track_queue = TrackQueue::::get(track); if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { + println!("Ref #{:?} ready for deciding", index); let now = frame_system::Pallet::::block_number(); - let maybe_set_alarm = Self::begin_deciding(&mut status, index, now, &track_info); + let maybe_set_alarm = Self::begin_deciding(&mut status, index, now, track_info); if let Some(set_alarm) = maybe_set_alarm { Self::ensure_alarm_at(&mut status, index, set_alarm); } ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); - deciding_count.saturating_inc(); - count.saturating_inc(); + TrackQueue::::insert(track, track_queue); } else { - break - } - } - ensure!(count > 0, Error::::NothingToDo); - - DecidingCount::::insert(track, deciding_count); - TrackQueue::::insert(track, track_queue); - Ok(count) - } - - /// Action item for when there is now one fewer referendum in the deciding phase and the - /// `DecidingCount` is not yet updated. This means that we should either: - /// - begin deciding another referendum (and leave `DecidingCount` alone); or - /// - decrement `DecidingCount`. - fn note_one_fewer_deciding(track: TrackIdOf, track_info: &TrackInfoOf) { - println!("One fewer deciding on {:?}", track); - let mut track_queue = TrackQueue::::get(track); - if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { - println!("Ref #{:?} ready for deciding", index); - let now = frame_system::Pallet::::block_number(); - let maybe_set_alarm = Self::begin_deciding(&mut status, index, now, track_info); - if let Some(set_alarm) = maybe_set_alarm { - Self::ensure_alarm_at(&mut status, index, set_alarm); + DecidingCount::::mutate(track, |x| x.saturating_dec()); } - ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); - TrackQueue::::insert(track, track_queue); - } else { - DecidingCount::::mutate(track, |x| x.saturating_dec()); } } - /// Ensure that an `service_referendum` alarm happens for the referendum `index` at `alarm`. + /// Ensure that a `service_referendum` alarm happens for the referendum `index` at `alarm`. /// /// This will do nothing if the alarm is already set. /// @@ -660,7 +655,7 @@ impl Pallet { index: ReferendumIndex, alarm: T::BlockNumber, ) -> bool { - if !status.alarm.as_ref().map_or(false, |&(when, _)| when == alarm) { + if status.alarm.as_ref().map_or(true, |&(when, _)| when != alarm) { println!("Setting alarm at block #{:?} for ref {:?}", alarm, index); // Either no alarm or one that was different Self::kill_alarm(status); @@ -741,6 +736,16 @@ impl Pallet { } } } + // If we didn't move into being decided, then check the timeout. + if status.deciding.is_none() && now >= timeout { + // Too long without being decided - end it. + Self::kill_alarm(&mut status); + Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); + return ( + ReferendumInfo::TimedOut(now, status.submission_deposit, status.decision_deposit), + true, + ) + } } else if let Some(deciding) = &mut status.deciding { let is_passing = Self::is_passing( &status.tally, @@ -793,14 +798,6 @@ impl Pallet { } } alarm = Self::decision_time(&deciding, &status.tally, track); - } else if now >= timeout { - // Too long without being decided - end it. - Self::kill_alarm(&mut status); - Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); - return ( - ReferendumInfo::TimedOut(now, status.submission_deposit, status.decision_deposit), - true, - ) } let dirty_alarm = Self::ensure_alarm_at(&mut status, index, alarm); diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index fa56a1b525323..f898fbb7ec4fa 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -411,6 +411,13 @@ pub fn killed_since(i: ReferendumIndex) -> u64 { } } +pub fn timed_out_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::TimedOut(since, ..) => since, + _ => panic!("Not timed out"), + } +} + fn is_deciding(i: ReferendumIndex) -> bool { matches!( ReferendumInfoFor::::get(i), @@ -443,12 +450,14 @@ impl RefState { } 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 } diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 85487d7641a1d..da4eed49fe21f 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -60,13 +60,16 @@ fn basic_happy_path_works() { run_to(6); // #6: Lots of ayes. Should now be confirming. set_tally(0, 100, 0); - run_to(8); + 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(11); + run_to(12); // #9: Should not yet be enacted. assert_eq!(Balances::free_balance(&42), 0); - run_to(12); + run_to(13); // #10: Proposal should be executed. assert_eq!(Balances::free_balance(&42), 1); }); @@ -77,9 +80,7 @@ fn insta_confirm_then_kill_works() { new_test_ext().execute_with(|| { let r = Confirming { immediate: true }.create(); run_to(6); - assert!(pallet_scheduler::Agenda::::get(7).iter().any(|x| x.is_some())); assert_ok!(Referenda::kill(Origin::root(), r)); - assert!(!pallet_scheduler::Agenda::::get(7).iter().any(|x| x.is_some())); assert_eq!(killed_since(r), 6); }); } @@ -88,9 +89,10 @@ fn insta_confirm_then_kill_works() { fn confirm_then_reconfirm_with_elapsed_trigger_works() { new_test_ext().execute_with(|| { let r = Confirming { immediate: false }.create(); - assert_eq!(confirming_until(r), 7); - run_to(6); + 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); @@ -104,6 +106,7 @@ fn instaconfirm_then_reconfirm_with_elapsed_trigger_works() { 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); @@ -117,28 +120,25 @@ fn instaconfirm_then_reconfirm_with_voting_trigger_works() { 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); - assert_eq!(confirming_until(r), 10); - run_to(10); - assert_eq!(approved_since(r), 10); + 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 = Confirming { immediate: false }.create(); - run_to(6); - assert_eq!(confirming_until(r), 7); - set_tally(r, 100, 99); - assert_eq!(deciding_and_failing_since(r), 5); - run_to(8); - set_tally(r, 100, 0); - assert_eq!(confirming_until(r), 10); + let r = Passing.create(); run_to(10); - assert_eq!(approved_since(r), 10); + assert_eq!(confirming_until(r), 11); + run_to(11); + assert_eq!(approved_since(r), 11); }); } @@ -150,7 +150,8 @@ fn should_instafail_during_extension_confirmation() { assert_eq!(confirming_until(r), 11); // Should insta-fail since it's now past the normal voting time. set_tally(r, 100, 101); - assert_eq!(rejected_since(r), 10); + run_to(11); + assert_eq!(rejected_since(r), 11); }); } @@ -159,11 +160,10 @@ fn confirming_then_fail_works() { new_test_ext().execute_with(|| { let r = Failing.create(); // Normally ends at 5 + 4 (voting period) = 9. - run_to(6); assert_eq!(deciding_and_failing_since(r), 5); set_tally(r, 100, 0); + run_to(6); assert_eq!(confirming_until(r), 8); - run_to(7); set_tally(r, 100, 101); run_to(9); assert_eq!(rejected_since(r), 9); @@ -220,9 +220,10 @@ fn queueing_works() { assert_eq!(cancelled_since(0), 6); // The other with the most approvals (#4) should be being decided. + run_to(7); assert_eq!(DecidingCount::::get(0), 1); - assert_eq!(deciding_since(4), 6); - assert_eq!(confirming_until(4), 8); + assert_eq!(deciding_since(4), 7); + assert_eq!(confirming_until(4), 9); // Vote on the remaining two to change order. println!("Set tally #1"); @@ -233,28 +234,36 @@ fn queueing_works() { println!("{:?}", Vec::<_>::from(TrackQueue::::get(0))); // Let confirmation period end. - run_to(8); + run_to(9); // #4 should have been confirmed. - assert_eq!(approved_since(4), 8); + 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), 8); + assert_eq!(deciding_since(1), 10); // Let it end unsuccessfully. - run_to(12); - assert_eq!(rejected_since(1), 12); + 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), 12); + assert_eq!(deciding_and_failing_since(2), 15); // #2 moves into confirming at the last moment with a 50% approval. - run_to(16); - assert_eq!(confirming_until(2), 18); + run_to(19); + assert_eq!(confirming_until(2), 21); // #2 gets approved. - run_to(18); - assert_eq!(approved_since(2), 18); - assert_eq!(deciding_since(3), 18); + 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); }); } diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 15bb7878ac829..3151829f9f77e 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -167,7 +167,7 @@ pub struct TrackInfo { } pub trait TracksInfo { - type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync; + type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static; type Origin; fn tracks() -> &'static [(Self::Id, TrackInfo)]; fn track_for(origin: &Self::Origin) -> Result; From 6149ed46b5eed4e369dc186ecb404d2cc72d5807 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 18 Dec 2021 13:39:01 +0000 Subject: [PATCH 27/66] More testing --- frame/conviction-voting/src/types.rs | 22 +++ frame/referenda/src/benchmarking.rs | 255 +++++++++++++++++++++++---- frame/referenda/src/lib.rs | 17 +- frame/referenda/src/mock.rs | 18 ++ frame/referenda/src/types.rs | 2 +- frame/support/src/traits/voting.rs | 4 + 6 files changed, 279 insertions(+), 39 deletions(-) diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 4cd57b9764a5a..35998ba0f0569 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -56,6 +56,28 @@ impl> VoteTally fn approval(&self) -> Perbill { Perbill::from_rational(self.ayes, self.ayes.saturating_add(self.nays)) } + + #[cfg(features = "runtime-benchmarks")] + fn unanimity() -> Self { + Self { + ayes: Total::get(), + nays: Zero::zero(), + turnout: Total::get(), + dummy: PhantomData, + } + } + + #[cfg(features = "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> Tally { diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index c4d0244ad31a0..3cef344e1e03f 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -38,7 +38,7 @@ fn funded_account(name: &'static str, index: u32) -> T::AccountId { caller } -fn create_referendum(deposit: bool) -> (T::AccountId, ReferendumIndex) { +fn create_referendum() -> (T::AccountId, ReferendumIndex) { let caller = funded_account::("caller", 0); whitelist_account!(caller); assert_ok!(Referenda::::submit( @@ -48,20 +48,100 @@ fn create_referendum(deposit: bool) -> (T::AccountId, ReferendumIndex AtOrAfter::After(0u32.into()) )); let index = ReferendumCount::::get() - 1; - if deposit { - assert_ok!(Referenda::::place_decision_deposit( - RawOrigin::Signed(caller.clone()).into(), - index, - )); - } (caller, index) } -fn decision_period() -> T::BlockNumber { - let id = T::Tracks::track_for(&RawOrigin::Root.into()) - .expect("Root should always be a gpvernance origin"); - let info = T::Tracks::info(id).expect("Id value returned from T::Tracks"); - info.decision_period +fn place_deposit(index: ReferendumIndex) { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + assert_ok!(Referenda::::place_decision_deposit( + RawOrigin::Signed(caller.clone()).into(), + index, + )); +} + +fn info(index: ReferendumIndex) -> &'static TrackInfoOf { + let status = Referenda::::ensure_ongoing(index).unwrap(); + T::Tracks::info(status.track).expect("Id value returned from T::Tracks") +} + +fn make_passing_after(index: ReferendumIndex, period_portion: Perbill) { + let turnout = info::(index).min_turnout.threshold(period_portion); + let approval = info::(index).min_approval.threshold(period_portion); + Referenda::::access_poll(index, |status| { + if let PollStatus::Ongoing(tally) = status { + *tally = T::Tally::from_requirements(turnout, approval); + dbg!(&*tally); + } + }); +} + +fn make_passing(index: ReferendumIndex) { + Referenda::::access_poll(index, |status| { + if let PollStatus::Ongoing(tally) = status { + *tally = T::Tally::unanimity(); + } + }); +} + +fn make_failing(index: ReferendumIndex) { + Referenda::::access_poll(index, |status| { + if let PollStatus::Ongoing(tally) = status { + *tally = T::Tally::default(); + } + }); +} + +fn skip_prepare_period(index: ReferendumIndex) { + let status = Referenda::::ensure_ongoing(index).unwrap(); + let prepare_period_over = status.submitted + info::(index).prepare_period; + frame_system::Pallet::::set_block_number(prepare_period_over); +} + +fn skip_decision_period(index: ReferendumIndex) { + let status = Referenda::::ensure_ongoing(index).unwrap(); + let decision_period_over = status.deciding.unwrap().since + info::(index).decision_period; + frame_system::Pallet::::set_block_number(decision_period_over); +} + +fn skip_confirm_period(index: ReferendumIndex) { + let status = Referenda::::ensure_ongoing(index).unwrap(); + let confirm_period_over = status.deciding.unwrap().confirming.unwrap(); + frame_system::Pallet::::set_block_number(confirm_period_over); +} + +fn skip_timeout_period(index: ReferendumIndex) { + let status = Referenda::::ensure_ongoing(index).unwrap(); + let timeout_period_over = status.submitted + T::UndecidingTimeout::get(); + frame_system::Pallet::::set_block_number(timeout_period_over); +} + +fn nudge(index: ReferendumIndex) { + assert_ok!(Referenda::::nudge_referendum( + RawOrigin::Root.into(), + index, + )); +} + +fn alarm_time(index: ReferendumIndex) -> T::BlockNumber { + let status = Referenda::::ensure_ongoing(index).unwrap(); + status.alarm.unwrap().0 +} + +fn is_confirming(index: ReferendumIndex) -> bool { + let status = Referenda::::ensure_ongoing(index).unwrap(); + matches!(status, ReferendumStatus { + deciding: Some(DecidingStatus { confirming: Some(_), .. }), + .. + }) +} + +fn is_not_confirming(index: ReferendumIndex) -> bool { + let status = Referenda::::ensure_ongoing(index).unwrap(); + matches!(status, ReferendumStatus { + deciding: Some(DecidingStatus { confirming: None, .. }), + .. + }) } benchmarks! { @@ -121,8 +201,6 @@ benchmarks! { assert_matches!(status, ReferendumInfo::Cancelled(_, _, None)); } - // TODO: When track empty. - // TODO: When track not empty. cancel { let caller = funded_account::("caller", 0); whitelist_account!(caller); @@ -141,8 +219,6 @@ benchmarks! { assert_matches!(status, ReferendumInfo::Cancelled(..)); } - // TODO: When track empty. - // TODO: When track not empty. kill { let caller = funded_account::("caller", 0); whitelist_account!(caller); @@ -173,41 +249,156 @@ benchmarks! { // TODO: not deciding, queued, still in but slide needed // Not deciding -> deciding - // TODO: not deciding, not queued, DD paid, PP (just) done, track empty, passing - // TODO: not deciding, not queued, DD paid, PP (just) done, track empty, failing + // DONE: not deciding, not queued, DD paid, PP (just) done, track empty, passing + // DONE: not deciding, not queued, DD paid, PP (just) done, track empty, failing // Deciding -> deciding - // TODO: deciding, passing, not confirming - // TODO: deciding, passing, confirming, confirmation period not over - // TODO: deciding, failing, confirming, decision period not over - // TODO: deciding, failing, not confirming, decision period not over + // DONE: deciding, passing, not confirming + // DONE: deciding, passing, confirming, confirmation period not over + // DONE: deciding, failing, confirming, decision period not over + // DONE: deciding, failing, not confirming, decision period not over, alarm reset // Deciding -> end - // TODO: deciding, passing, confirming, confirmation period over (accepted) - // TODO: deciding, failing, decision period over (rejected) + // DONE: deciding, passing, confirming, confirmation period over (accepted) + // DONE: deciding, failing, decision period over (rejected) // Not deciding -> end - // TODO: not deciding, timeout + // DONE: not deciding, timeout - nudge_referendum_no_dd_pp_over { - let (_caller, index) = create_referendum::(false); - let decision_period_over = frame_system::Pallet::::block_number() + decision_period::(); - frame_system::Pallet::::set_block_number(decision_period_over); + nudge_referendum_dd_pp_not_queued_track_full { + let (_caller, index) = create_referendum::(); + skip_prepare_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + // TODO: 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. + let status = Referenda::::ensure_ongoing(index).unwrap(); + assert_matches!(status, ReferendumStatus { deciding: None, .. }); + } + nudge_referendum_no_dd_pp { + let (_caller, index) = create_referendum::(); + skip_prepare_period::(index); }: nudge_referendum(RawOrigin::Root, index) verify { let status = Referenda::::ensure_ongoing(index).unwrap(); assert_matches!(status, ReferendumStatus { deciding: None, .. }); } - nudge_referendum_dd_pp_still { - let (_caller, index) = create_referendum::(true); - let decision_period_over = frame_system::Pallet::::block_number() + decision_period::(); + nudge_referendum_dd_no_pp { + let (_caller, index) = create_referendum::(); + place_deposit::(index); }: nudge_referendum(RawOrigin::Root, index) verify { let status = Referenda::::ensure_ongoing(index).unwrap(); assert_matches!(status, ReferendumStatus { deciding: None, .. }); } + nudge_referendum_timeout { + let (_caller, index) = create_referendum::(); + skip_timeout_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let info = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(info, ReferendumInfo::TimedOut(..)); + } + + nudge_referendum_dd_pp_failing { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(is_not_confirming::(index)); + } + + nudge_referendum_dd_pp_passing { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + make_passing::(index); + skip_prepare_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(is_confirming::(index)); + } + + nudge_referendum_deciding_passing_not_confirming { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + nudge::(index); + assert!(!is_confirming::(index)); + make_passing::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(is_confirming::(index)); + } + + nudge_referendum_deciding_failing_but_confirming { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + make_passing::(index); + nudge::(index); + assert!(is_confirming::(index)); + make_failing::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(!is_confirming::(index)); + } + + nudge_referendum_deciding_failing_not_confirming { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + nudge::(index); + assert!(!is_confirming::(index)); + let old_alarm = alarm_time::(index); + make_passing_after::(index, Perbill::from_percent(50)); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert_ne!(old_alarm, alarm_time::(index)); + assert!(!is_confirming::(index)); + } + + nudge_referendum_deciding_passing_and_confirming { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + make_passing::(index); + skip_prepare_period::(index); + nudge::(index); + assert!(is_confirming::(index)); + let old_alarm = alarm_time::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(is_confirming::(index)); + } + + nudge_referendum_approved { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + make_passing::(index); + nudge::(index); + skip_confirm_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let info = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(info, ReferendumInfo::Approved(..)); + } + + nudge_referendum_rejected { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + nudge::(index); + skip_decision_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let info = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(info, ReferendumInfo::Rejected(..)); + } + impl_benchmark_test_suite!( Referenda, crate::mock::new_test_ext(), diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 9e3fa4d899141..b65354b5fe61e 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -392,7 +392,9 @@ pub mod pallet { pub fn nudge_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { ensure_root(origin)?; let now = frame_system::Pallet::::block_number(); - let status = Self::ensure_ongoing(index)?; + let mut status = Self::ensure_ongoing(index)?; + // This is our wake-up, so we can disregard the alarm. + status.alarm = None; let (info, dirty) = Self::service_referendum(now, index, status); if dirty { ReferendumInfoFor::::insert(index, info); @@ -658,7 +660,7 @@ impl Pallet { if status.alarm.as_ref().map_or(true, |&(when, _)| when != alarm) { println!("Setting alarm at block #{:?} for ref {:?}", alarm, index); // Either no alarm or one that was different - Self::kill_alarm(status); + Self::ensure_no_alarm(status); status.alarm = Self::set_alarm(Call::nudge_referendum { index }, alarm); true } else { @@ -739,7 +741,7 @@ impl Pallet { // If we didn't move into being decided, then check the timeout. if status.deciding.is_none() && now >= timeout { // Too long without being decided - end it. - Self::kill_alarm(&mut status); + Self::ensure_no_alarm(&mut status); Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); return ( ReferendumInfo::TimedOut(now, status.submission_deposit, status.decision_deposit), @@ -757,7 +759,7 @@ impl Pallet { if is_passing { if deciding.confirming.map_or(false, |c| now >= c) { // Passed! - Self::kill_alarm(&mut status); + Self::ensure_no_alarm(&mut status); Self::note_one_fewer_deciding(status.track, track); let (desired, call_hash) = (status.enactment, status.proposal_hash); Self::schedule_enactment(index, track, desired, status.origin, call_hash); @@ -779,7 +781,7 @@ impl Pallet { } else { if now >= deciding.since.saturating_add(track.decision_period) { // Failed! - Self::kill_alarm(&mut status); + Self::ensure_no_alarm(&mut status); Self::note_one_fewer_deciding(status.track, track); Self::deposit_event(Event::::Rejected { index, tally: status.tally }); return ( @@ -797,7 +799,9 @@ impl Pallet { deciding.confirming = None; } } + dbg!(&deciding, &status.tally); alarm = Self::decision_time(&deciding, &status.tally, track); + println!("decision time for ref #{:?} is {:?}", index, alarm); } let dirty_alarm = Self::ensure_alarm_at(&mut status, index, alarm); @@ -816,11 +820,12 @@ impl Pallet { let until_approval = track.min_approval.delay(approval); let until_turnout = track.min_turnout.delay(turnout); let offset = until_turnout.max(until_approval); + dbg!(approval, turnout, until_approval, until_turnout, offset); deciding.since.saturating_add(offset * track.decision_period) }) } - fn kill_alarm(status: &mut ReferendumStatusOf) { + fn ensure_no_alarm(status: &mut ReferendumStatusOf) { if let Some((_, last_alarm)) = status.alarm.take() { // Incorrect alarm - cancel it. let _ = T::Scheduler::cancel(last_alarm); diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index f898fbb7ec4fa..bc05669091d05 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -287,6 +287,24 @@ impl VoteTally for Tally { 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 { diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 3151829f9f77e..da58d19374362 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -328,7 +328,7 @@ pub enum Curve { } impl Curve { - fn threshold(&self, x: Perbill) -> Perbill { + pub(crate) fn threshold(&self, x: Perbill) -> Perbill { match self { Self::LinearDecreasing { begin, delta } => *begin - *delta * x, } diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index aac78e9ac260d..f257f298f5512 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -98,6 +98,10 @@ pub trait VoteTally { 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 { From 46c5c41c3a0900e3793ab2ad73bec55e951d8841 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 18 Dec 2021 16:18:07 +0000 Subject: [PATCH 28/66] Benchmarks --- frame/referenda/src/benchmarking.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index 3cef344e1e03f..f7a1c06306a56 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -266,16 +266,29 @@ benchmarks! { // DONE: not deciding, timeout nudge_referendum_dd_pp_not_queued_track_full { + // 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 a referendum to know our track, We keep it out of the track by not placing + // the deposit. let (_caller, index) = create_referendum::(); + // Then, fill the track. + for _ in 0..(info::(index).max_deciding - 1) { + create_referendum::(); + place_deposit::(index); + } + + // Now the track is full, our referendum will be queued. + let (_caller, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + }: nudge_referendum(RawOrigin::Root, index) verify { - // TODO: 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. - let status = Referenda::::ensure_ongoing(index).unwrap(); - assert_matches!(status, ReferendumStatus { deciding: None, .. }); } + nudge_referendum_no_dd_pp { let (_caller, index) = create_referendum::(); skip_prepare_period::(index); From 2d4b6ffb764f62a662f54c572def4faa97dcddd2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 18 Dec 2021 16:43:30 +0000 Subject: [PATCH 29/66] Fiddly benchmark --- frame/referenda/src/benchmarking.rs | 40 ++++++++++++++++++++++------- frame/referenda/src/lib.rs | 3 +-- frame/referenda/src/mock.rs | 2 +- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index f7a1c06306a56..032fe8c45d220 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -242,7 +242,7 @@ benchmarks! { // DONE: not deciding, not queued, DD paid, PP not done // Not deciding -> not deciding (queued) - // TODO: not deciding, not queued, DD paid, PP (just) done, track full + // not deciding, not queued, DD paid, PP (just) done, track full // Not deciding (queued) -> not deciding (queued) // TODO: not deciding, queued, since removed @@ -270,23 +270,45 @@ benchmarks! { // free and this failing. It would result in `QUEUE_SIZE - 1` items being shifted for the // insertion at the beginning. - // First create a referendum to know our track, We keep it out of the track by not placing - // the deposit. + // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); - // Then, fill the track. - for _ in 0..(info::(index).max_deciding - 1) { - create_referendum::(); + place_deposit::(index); + + // Then, create enough other referendums to fill the track. + let mut others = vec![]; + for _ in 0..info::(index).max_deciding { + let (_caller, index) = create_referendum::(); place_deposit::(index); + others.push(index); } - // Now the track is full, our referendum will be queued. - let (_caller, index) = create_referendum::(); - place_deposit::(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 1..T::MaxQueued::get() { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + make_passing::(index); + others.push(index); + } + // Skip to when they can start being decided. skip_prepare_period::(index); + // Manually nudge the other referenda first to ensure that they begin. + for i in others.into_iter() { + Referenda::::nudge_referendum(RawOrigin::Root.into(), i); + } + + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); + assert!(TrackQueue::::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 { + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); } nudge_referendum_no_dd_pp { diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index b65354b5fe61e..1ac40a64230cf 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -40,8 +40,7 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Dispatchable, One, Saturating, Zero}, - DispatchError, DispatchResult, Perbill, + traits::{AtLeast32BitUnsigned, Dispatchable, One, Saturating, Zero}, DispatchError, Perbill, }; use sp_std::{fmt::Debug, prelude::*}; diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index bc05669091d05..7b1fbd1b56478 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -33,7 +33,7 @@ use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, Hash, IdentityLookup}, - Perbill, + Perbill, DispatchResult, }; const MAX_PROPOSALS: u32 = 100; From df78dced5aaf3aea65df212a3d5c473e8d2992ab Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 18 Dec 2021 17:48:13 +0000 Subject: [PATCH 30/66] Final nudge benchmarks --- frame/referenda/src/benchmarking.rs | 100 +++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index 032fe8c45d220..c81aa8b09b1fe 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -76,6 +76,10 @@ fn make_passing_after(index: ReferendumIndex, period_portion: Perbill }); } +fn make_just_passing(index: ReferendumIndex) { + make_passing_after::(index, Perbill::one()); +} + fn make_passing(index: ReferendumIndex) { Referenda::::access_poll(index, |status| { if let PollStatus::Ongoing(tally) = status { @@ -245,8 +249,8 @@ benchmarks! { // not deciding, not queued, DD paid, PP (just) done, track full // Not deciding (queued) -> not deciding (queued) - // TODO: not deciding, queued, since removed - // TODO: not deciding, queued, still in but slide needed + // TODO: not deciding, queued, since removed (insertion needed) + // TODO: not deciding, queued, still in (slide needed) // Not deciding -> deciding // DONE: not deciding, not queued, DD paid, PP (just) done, track empty, passing @@ -265,6 +269,97 @@ benchmarks! { // Not deciding -> end // DONE: not deciding, timeout + nudge_referendum_queued_insert { + // First create our referendum and place the deposit. It will be failing. + let (_caller, index) = create_referendum::(); + place_deposit::(index); + + // Then, create enough other referendums to fill the track. + let mut others = vec![]; + for _ in 0..info::(index).max_deciding { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + others.push(index); + } + + // We will also need enough referenda which are queued and passing, we want `MaxQueued` + // in order to ensure ours will not be queued and thus can force an insertion later. + for _ in 0..T::MaxQueued::get() { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + make_passing_after::(index, Perbill::from_percent(90)); + others.push(index); + } + + // Skip to when they can start being decided. + skip_prepare_period::(index); + + // Manually nudge the other referenda first to ensure that they begin. + for i in others.into_iter() { + Referenda::::nudge_referendum(RawOrigin::Root.into(), i); + } + + // Now nudge ours, with the track now full and the queue full of referenda with votes, + // ours will not be in the queue. + Referenda::::nudge_referendum(RawOrigin::Root.into(), index); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert!(TrackQueue::::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::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let t = TrackQueue::::get(&track); + assert_eq!(t.len() as u32, T::MaxQueued::get()); + assert_eq!(t[t.len() - 1].0, index); + } + + nudge_referendum_queued_slide { + // First create our referendum and place the deposit. It will be failing. + let (_caller, index) = create_referendum::(); + place_deposit::(index); + + // Then, create enough other referendums to fill the track. + let mut others = vec![]; + for _ in 0..info::(index).max_deciding { + let (_caller, index) = create_referendum::(); + place_deposit::(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 1..T::MaxQueued::get() { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + make_passing_after::(index, Perbill::from_percent(90)); + others.push(index); + } + + // Skip to when they can start being decided. + skip_prepare_period::(index); + + // Manually nudge the other referenda first to ensure that they begin. + for i in others.into_iter() { + Referenda::::nudge_referendum(RawOrigin::Root.into(), i); + } + + // Now nudge ours, with the track now full, ours will be queued, but with no votes, it + // will have the worst position. + Referenda::::nudge_referendum(RawOrigin::Root.into(), index); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); + + // Now alter the voting, so that ours leap-frogs all into the best position. + make_passing::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let t = TrackQueue::::get(&track); + assert_eq!(t.len() as u32, T::MaxQueued::get()); + assert_eq!(t[t.len() - 1].0, index); + } + nudge_referendum_dd_pp_not_queued_track_full { // 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 @@ -306,7 +401,6 @@ benchmarks! { // Then nudge ours, with the track now full, ours will be queued. }: nudge_referendum(RawOrigin::Root, index) verify { - let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); } From c2d9dc5b2cccdbf10c5b9633399235ac0c5fe1f3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 18 Dec 2021 18:00:15 +0000 Subject: [PATCH 31/66] Formatting --- frame/conviction-voting/src/types.rs | 14 ++----------- frame/referenda/src/benchmarking.rs | 30 ++++++++++++++-------------- frame/referenda/src/lib.rs | 13 +++++++++--- frame/referenda/src/mock.rs | 12 +++-------- frame/referenda/src/types.rs | 5 +---- 5 files changed, 31 insertions(+), 43 deletions(-) diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 35998ba0f0569..17565be794c04 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -59,24 +59,14 @@ impl> VoteTally #[cfg(features = "runtime-benchmarks")] fn unanimity() -> Self { - Self { - ayes: Total::get(), - nays: Zero::zero(), - turnout: Total::get(), - dummy: PhantomData, - } + Self { ayes: Total::get(), nays: Zero::zero(), turnout: Total::get(), dummy: PhantomData } } #[cfg(features = "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, - } + Self { ayes, nays: turnout - ayes, turnout, dummy: PhantomData } } } diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index c81aa8b09b1fe..cd602e70ded12 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -18,12 +18,15 @@ //! 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_support::{ + assert_ok, + traits::{Currency, EnsureOrigin}, +}; use frame_system::RawOrigin; use sp_runtime::traits::{Bounded, Hash}; -use assert_matches::assert_matches; -use crate::Pallet as Referenda; const SEED: u32 = 0; @@ -121,10 +124,7 @@ fn skip_timeout_period(index: ReferendumIndex) { } fn nudge(index: ReferendumIndex) { - assert_ok!(Referenda::::nudge_referendum( - RawOrigin::Root.into(), - index, - )); + assert_ok!(Referenda::::nudge_referendum(RawOrigin::Root.into(), index,)); } fn alarm_time(index: ReferendumIndex) -> T::BlockNumber { @@ -134,18 +134,18 @@ fn alarm_time(index: ReferendumIndex) -> T::BlockNumber { fn is_confirming(index: ReferendumIndex) -> bool { let status = Referenda::::ensure_ongoing(index).unwrap(); - matches!(status, ReferendumStatus { - deciding: Some(DecidingStatus { confirming: Some(_), .. }), - .. - }) + matches!( + status, + ReferendumStatus { deciding: Some(DecidingStatus { confirming: Some(_), .. }), .. } + ) } fn is_not_confirming(index: ReferendumIndex) -> bool { let status = Referenda::::ensure_ongoing(index).unwrap(); - matches!(status, ReferendumStatus { - deciding: Some(DecidingStatus { confirming: None, .. }), - .. - }) + matches!( + status, + ReferendumStatus { deciding: Some(DecidingStatus { confirming: None, .. }), .. } + ) } benchmarks! { diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 1ac40a64230cf..f1fc78c232fed 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -40,7 +40,8 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Dispatchable, One, Saturating, Zero}, DispatchError, Perbill, + traits::{AtLeast32BitUnsigned, Dispatchable, One, Saturating, Zero}, + DispatchError, Perbill, }; use sp_std::{fmt::Debug, prelude::*}; @@ -727,7 +728,9 @@ impl Pallet { if status.decision_deposit.is_some() { let prepare_end = status.submitted.saturating_add(track.prepare_period); if now >= prepare_end { - if let Some(set_alarm) = Self::ready_for_deciding(now, &track, index, &mut status) { + if let Some(set_alarm) = + Self::ready_for_deciding(now, &track, index, &mut status) + { alarm = alarm.min(set_alarm); } dirty = true; @@ -743,7 +746,11 @@ impl Pallet { Self::ensure_no_alarm(&mut status); Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); return ( - ReferendumInfo::TimedOut(now, status.submission_deposit, status.decision_deposit), + ReferendumInfo::TimedOut( + now, + status.submission_deposit, + status.decision_deposit, + ), true, ) } diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 7b1fbd1b56478..8f8ed47616a3b 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -33,7 +33,7 @@ use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, Hash, IdentityLookup}, - Perbill, DispatchResult, + DispatchResult, Perbill, }; const MAX_PROPOSALS: u32 = 100; @@ -290,20 +290,14 @@ impl VoteTally for Tally { #[cfg(feature = "runtime-benchmarks")] fn unanimity() -> Self { - Self { - ayes: 100, - nays: 0, - } + 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, - } + Self { ayes, nays: turnout - ayes } } } diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index da58d19374362..c2810c57f2112 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -172,10 +172,7 @@ pub trait TracksInfo { fn tracks() -> &'static [(Self::Id, TrackInfo)]; fn track_for(origin: &Self::Origin) -> Result; fn info(id: Self::Id) -> Option<&'static TrackInfo> { - Self::tracks() - .iter() - .find(|x| &x.0 == &id) - .map(|x| &x.1) + Self::tracks().iter().find(|x| &x.0 == &id).map(|x| &x.1) } } From 213c320c909f7dcba15dcd88cf1ce20738fb4524 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 19 Dec 2021 10:00:01 +0000 Subject: [PATCH 32/66] Formatting --- frame/referenda/src/benchmarking.rs | 161 ++++++++++++---------------- 1 file changed, 68 insertions(+), 93 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index cd602e70ded12..287f37a944f7c 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -63,6 +63,33 @@ fn place_deposit(index: ReferendumIndex) { )); } +fn fill_queue(index: ReferendumIndex, spaces: u32, pass_after: u32) { + // First, create enough other referendums to fill the track. + let mut others = vec![]; + for _ in 0..info::(index).max_deciding { + let (_caller, index) = create_referendum::(); + place_deposit::(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::(); + place_deposit::(index); + make_passing_after::(index, Perbill::from_percent(pass_after)); + others.push(index); + } + + // Skip to when they can start being decided. + skip_prepare_period::(index); + + // Manually nudge the other referenda first to ensure that they begin. + for i in others.into_iter() { + Referenda::::nudge_referendum(RawOrigin::Root.into(), i); + } +} + fn info(index: ReferendumIndex) -> &'static TrackInfoOf { let status = Referenda::::ensure_ongoing(index).unwrap(); T::Tracks::info(status.track).expect("Id value returned from T::Tracks") @@ -163,21 +190,9 @@ benchmarks! { assert_matches!(status, ReferendumInfo::Ongoing(_)); } - // TODO: Track at capacity. - // TODO: Track not at capacity and vote failing. - // TODO: Track at capacity and vote passing. - place_decision_deposit { - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - let origin = move || RawOrigin::Signed(caller.clone()); - assert_ok!(Referenda::::submit( - origin().into(), - RawOrigin::Root.into(), - T::Hashing::hash_of(&0), - AtOrAfter::After(0u32.into()) - )); - let index = ReferendumCount::::get().checked_sub(1).unwrap(); - }: _(origin(), index) + place_decision_deposit_within_pp { + let (caller, index) = create_referendum::(); + }: place_decision_deposit(RawOrigin::Signed(caller), index) verify { let status = ReferendumInfoFor::::get(index).unwrap(); assert_matches!(status, ReferendumInfo::Ongoing(ReferendumStatus { @@ -186,6 +201,38 @@ benchmarks! { })); } + // DONE: Track at capacity and worst-case queue. + place_decision_deposit_track_at_capacity { + let (caller, index) = create_referendum::(); + fill_queue::(index, 1, 90); + }: place_decision_deposit(RawOrigin::Signed(caller), index) + verify { + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); + } + + // DONE: Track not at capacity and vote passing. + place_decision_deposit_passing { + let (caller, index) = create_referendum::(); + skip_prepare_period::(index); + make_passing::(index); + }: place_decision_deposit(RawOrigin::Signed(caller), index) + verify { + let status = Referenda::::ensure_ongoing(index).unwrap(); + assert!(is_confirming::(index)); + } + + // DONE: Track not at capacity and vote failing. + place_decision_deposit_failing { + let (caller, index) = create_referendum::(); + skip_prepare_period::(index); + }: place_decision_deposit(RawOrigin::Signed(caller), index) + verify { + let status = Referenda::::ensure_ongoing(index).unwrap(); + assert!(is_not_confirming::(index)); + } + refund_decision_deposit { let caller = funded_account::("caller", 0); whitelist_account!(caller); @@ -246,11 +293,11 @@ benchmarks! { // DONE: not deciding, not queued, DD paid, PP not done // Not deciding -> not deciding (queued) - // not deciding, not queued, DD paid, PP (just) done, track full + // DONE: not deciding, not queued, DD paid, PP (just) done, track full // Not deciding (queued) -> not deciding (queued) - // TODO: not deciding, queued, since removed (insertion needed) - // TODO: not deciding, queued, still in (slide needed) + // DONE: not deciding, queued, since removed (insertion needed) + // DONE: not deciding, queued, still in (slide needed) // Not deciding -> deciding // DONE: not deciding, not queued, DD paid, PP (just) done, track empty, passing @@ -273,31 +320,7 @@ benchmarks! { // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); place_deposit::(index); - - // Then, create enough other referendums to fill the track. - let mut others = vec![]; - for _ in 0..info::(index).max_deciding { - let (_caller, index) = create_referendum::(); - place_deposit::(index); - others.push(index); - } - - // We will also need enough referenda which are queued and passing, we want `MaxQueued` - // in order to ensure ours will not be queued and thus can force an insertion later. - for _ in 0..T::MaxQueued::get() { - let (_caller, index) = create_referendum::(); - place_deposit::(index); - make_passing_after::(index, Perbill::from_percent(90)); - others.push(index); - } - - // Skip to when they can start being decided. - skip_prepare_period::(index); - - // Manually nudge the other referenda first to ensure that they begin. - for i in others.into_iter() { - Referenda::::nudge_referendum(RawOrigin::Root.into(), i); - } + fill_queue::(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. @@ -318,31 +341,7 @@ benchmarks! { // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); place_deposit::(index); - - // Then, create enough other referendums to fill the track. - let mut others = vec![]; - for _ in 0..info::(index).max_deciding { - let (_caller, index) = create_referendum::(); - place_deposit::(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 1..T::MaxQueued::get() { - let (_caller, index) = create_referendum::(); - place_deposit::(index); - make_passing_after::(index, Perbill::from_percent(90)); - others.push(index); - } - - // Skip to when they can start being decided. - skip_prepare_period::(index); - - // Manually nudge the other referenda first to ensure that they begin. - for i in others.into_iter() { - Referenda::::nudge_referendum(RawOrigin::Root.into(), i); - } + fill_queue::(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. @@ -368,31 +367,7 @@ benchmarks! { // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); place_deposit::(index); - - // Then, create enough other referendums to fill the track. - let mut others = vec![]; - for _ in 0..info::(index).max_deciding { - let (_caller, index) = create_referendum::(); - place_deposit::(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 1..T::MaxQueued::get() { - let (_caller, index) = create_referendum::(); - place_deposit::(index); - make_passing::(index); - others.push(index); - } - - // Skip to when they can start being decided. - skip_prepare_period::(index); - - // Manually nudge the other referenda first to ensure that they begin. - for i in others.into_iter() { - Referenda::::nudge_referendum(RawOrigin::Root.into(), i); - } + fill_queue::(index, 1, 0); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); From fcf11e201f01758f57d0d674b9c6332e67f647d6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 19 Dec 2021 18:37:58 +0000 Subject: [PATCH 33/66] Finished up benchmarks --- Cargo.lock | 1 + bin/node/cli/src/chain_spec.rs | 1 + bin/node/runtime/Cargo.toml | 24 +- bin/node/runtime/src/lib.rs | 102 ++++ frame/referenda/src/benchmarking.rs | 223 ++++---- frame/referenda/src/lib.rs | 469 +++++++++++------ frame/referenda/src/mock.rs | 12 - frame/referenda/src/tests.rs | 4 +- frame/referenda/src/types.rs | 114 ++-- frame/referenda/src/weights.rs | 777 +++++++++++++--------------- 10 files changed, 944 insertions(+), 783 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a53842eb29171..dfda05a833bed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4741,6 +4741,7 @@ dependencies = [ "pallet-proxy", "pallet-randomness-collective-flip", "pallet-recovery", + "pallet-referenda", "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index b29248519cc08..f8cc9abda6990 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -363,6 +363,7 @@ pub fn testnet_genesis( transaction_storage: Default::default(), scheduler: Default::default(), transaction_payment: Default::default(), + referenda: Default::default(), } } diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index fa879adf5cbb4..a453ed4ae5dd9 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -82,6 +82,7 @@ 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-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 } @@ -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", @@ -194,6 +195,7 @@ runtime-benchmarks = [ "pallet-collective/runtime-benchmarks", "pallet-contracts/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 +206,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 +221,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", ] @@ -235,34 +238,35 @@ try-runtime = [ "pallet-collective/try-runtime", "pallet-contracts/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. diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 97de54fc21e8c..605e1ca35e6dd 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -693,6 +693,105 @@ impl pallet_bags_list::Config for Runtime { type BagThresholds = BagThresholds; } +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 for TracksInfo { + type Id = u8; + type Origin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] { + static DATA: [(u8, pallet_referenda::TrackInfo); 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 { + if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { + match system_origin { + frame_system::RawOrigin::Root => Ok(0), + _ => Err(()), + } + } else { + Err(()) + } + } +} + +// TODO: Replace with conviction voting tally. +use scale_info::TypeInfo; +#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, Default)] +pub struct Tally { + pub ayes: u32, + pub nays: u32, +} + +impl frame_support::traits::VoteTally 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 } + } +} + +impl pallet_referenda::Config for Runtime { + type WeightInfo = pallet_referenda::weights::SubstrateWeight; + type Call = Call; + type Event = Event; + type Scheduler = Scheduler; + type Currency = pallet_balances::Pallet; + type CancelOrigin = EnsureRoot; + type KillOrigin = EnsureRoot; + type Slash = (); + type Votes = u32; + type Tally = Tally; + 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; @@ -1329,6 +1428,7 @@ construct_runtime!( TransactionStorage: pallet_transaction_storage, BagsList: pallet_bags_list, ChildBounties: pallet_child_bounties, + Referenda: pallet_referenda, } ); @@ -1698,6 +1798,7 @@ impl_runtime_apis! { list_benchmark!(list, extra, pallet_offences, OffencesBench::); list_benchmark!(list, extra, pallet_preimage, Preimage); list_benchmark!(list, extra, pallet_proxy, Proxy); + list_benchmark!(list, extra, pallet_referenda, Referenda); list_benchmark!(list, extra, pallet_scheduler, Scheduler); list_benchmark!(list, extra, pallet_session, SessionBench::); list_benchmark!(list, extra, pallet_staking, Staking); @@ -1777,6 +1878,7 @@ impl_runtime_apis! { add_benchmark!(params, batches, pallet_offences, OffencesBench::); add_benchmark!(params, batches, pallet_preimage, Preimage); add_benchmark!(params, batches, pallet_proxy, Proxy); + add_benchmark!(params, batches, pallet_referenda, Referenda); add_benchmark!(params, batches, pallet_scheduler, Scheduler); add_benchmark!(params, batches, pallet_session, SessionBench::); add_benchmark!(params, batches, pallet_staking, Staking); diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index 287f37a944f7c..ebe0863aea24c 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -63,7 +63,13 @@ fn place_deposit(index: ReferendumIndex) { )); } -fn fill_queue(index: ReferendumIndex, spaces: u32, pass_after: u32) { +fn nudge(index: ReferendumIndex) { + assert_ok!(Referenda::::nudge_referendum(RawOrigin::Root.into(), index)); +} + +fn fill_queue(index: ReferendumIndex, spaces: u32, pass_after: u32) + -> Vec +{ // First, create enough other referendums to fill the track. let mut others = vec![]; for _ in 0..info::(index).max_deciding { @@ -85,9 +91,9 @@ fn fill_queue(index: ReferendumIndex, spaces: u32, pass_after: u32) { skip_prepare_period::(index); // Manually nudge the other referenda first to ensure that they begin. - for i in others.into_iter() { - Referenda::::nudge_referendum(RawOrigin::Root.into(), i); - } + others.iter().for_each(|&i| nudge::(i)); + + others } fn info(index: ReferendumIndex) -> &'static TrackInfoOf { @@ -101,15 +107,10 @@ fn make_passing_after(index: ReferendumIndex, period_portion: Perbill Referenda::::access_poll(index, |status| { if let PollStatus::Ongoing(tally) = status { *tally = T::Tally::from_requirements(turnout, approval); - dbg!(&*tally); } }); } -fn make_just_passing(index: ReferendumIndex) { - make_passing_after::(index, Perbill::one()); -} - fn make_passing(index: ReferendumIndex) { Referenda::::access_poll(index, |status| { if let PollStatus::Ongoing(tally) = status { @@ -150,10 +151,6 @@ fn skip_timeout_period(index: ReferendumIndex) { frame_system::Pallet::::set_block_number(timeout_period_over); } -fn nudge(index: ReferendumIndex) { - assert_ok!(Referenda::::nudge_referendum(RawOrigin::Root.into(), index,)); -} - fn alarm_time(index: ReferendumIndex) -> T::BlockNumber { let status = Referenda::::ensure_ongoing(index).unwrap(); status.alarm.unwrap().0 @@ -186,23 +183,17 @@ benchmarks! { AtOrAfter::After(0u32.into()) ) verify { let index = ReferendumCount::::get().checked_sub(1).unwrap(); - let status = ReferendumInfoFor::::get(index).unwrap(); - assert_matches!(status, ReferendumInfo::Ongoing(_)); + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Ongoing(_))); } - place_decision_deposit_within_pp { + place_decision_deposit_preparing { let (caller, index) = create_referendum::(); }: place_decision_deposit(RawOrigin::Signed(caller), index) verify { - let status = ReferendumInfoFor::::get(index).unwrap(); - assert_matches!(status, ReferendumInfo::Ongoing(ReferendumStatus { - decision_deposit: Some(..), - .. - })); + assert!(Referenda::::ensure_ongoing(index).unwrap().decision_deposit.is_some()); } - // DONE: Track at capacity and worst-case queue. - place_decision_deposit_track_at_capacity { + place_decision_deposit_queued { let (caller, index) = create_referendum::(); fill_queue::(index, 1, 90); }: place_decision_deposit(RawOrigin::Signed(caller), index) @@ -212,111 +203,110 @@ benchmarks! { assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); } - // DONE: Track not at capacity and vote passing. + place_decision_deposit_not_queued { + let (caller, index) = create_referendum::(); + fill_queue::(index, 0, 90); + }: place_decision_deposit(RawOrigin::Signed(caller), index) + verify { + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); + } + place_decision_deposit_passing { let (caller, index) = create_referendum::(); skip_prepare_period::(index); make_passing::(index); }: place_decision_deposit(RawOrigin::Signed(caller), index) verify { - let status = Referenda::::ensure_ongoing(index).unwrap(); assert!(is_confirming::(index)); } - // DONE: Track not at capacity and vote failing. place_decision_deposit_failing { let (caller, index) = create_referendum::(); skip_prepare_period::(index); }: place_decision_deposit(RawOrigin::Signed(caller), index) verify { - let status = Referenda::::ensure_ongoing(index).unwrap(); assert!(is_not_confirming::(index)); } refund_decision_deposit { - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - let origin = move || RawOrigin::Signed(caller.clone()); - assert_ok!(Referenda::::submit( - origin().into(), - RawOrigin::Root.into(), - T::Hashing::hash_of(&0), - AtOrAfter::After(0u32.into()) - )); - let index = ReferendumCount::::get().checked_sub(1).unwrap(); - assert_ok!(Referenda::::place_decision_deposit(origin().into(), index)); + let (caller, index) = create_referendum::(); + place_deposit::(index); assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); - }: _(origin(), index) + }: _(RawOrigin::Signed(caller), index) verify { - let status = ReferendumInfoFor::::get(index).unwrap(); - assert_matches!(status, ReferendumInfo::Cancelled(_, _, None)); + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, _, None))); } cancel { - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - let origin = move || RawOrigin::Signed(caller.clone()); - assert_ok!(Referenda::::submit( - origin().into(), - RawOrigin::Root.into(), - T::Hashing::hash_of(&0), - AtOrAfter::After(0u32.into()) - )); - let index = ReferendumCount::::get().checked_sub(1).unwrap(); - assert_ok!(Referenda::::place_decision_deposit(origin().into(), index)); - }: _(T::CancelOrigin::successful_origin(), index) + let (_caller, index) = create_referendum::(); + place_deposit::(index); + }: _(T::CancelOrigin::successful_origin(), index) verify { - let status = ReferendumInfoFor::::get(index).unwrap(); - assert_matches!(status, ReferendumInfo::Cancelled(..)); + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(..))); } kill { - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - let origin = move || RawOrigin::Signed(caller.clone()); - assert_ok!(Referenda::::submit( - origin().into(), - RawOrigin::Root.into(), - T::Hashing::hash_of(&0), - AtOrAfter::After(0u32.into()) - )); - let index = ReferendumCount::::get().checked_sub(1).unwrap(); - assert_ok!(Referenda::::place_decision_deposit(origin().into(), index)); - }: _(T::KillOrigin::successful_origin(), index) + let (_caller, index) = create_referendum::(); + place_deposit::(index); + }: _(T::KillOrigin::successful_origin(), index) verify { - let status = ReferendumInfoFor::::get(index).unwrap(); - assert_matches!(status, ReferendumInfo::Killed(..)); + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Killed(..))); } - // Not deciding -> not deciding - // DONE: not deciding, not queued, no DD paid, PP done - // DONE: not deciding, not queued, DD paid, PP not done - - // Not deciding -> not deciding (queued) - // DONE: not deciding, not queued, DD paid, PP (just) done, track full - - // Not deciding (queued) -> not deciding (queued) - // DONE: not deciding, queued, since removed (insertion needed) - // DONE: not deciding, queued, still in (slide needed) - - // Not deciding -> deciding - // DONE: not deciding, not queued, DD paid, PP (just) done, track empty, passing - // DONE: not deciding, not queued, DD paid, PP (just) done, track empty, failing - - // Deciding -> deciding - // DONE: deciding, passing, not confirming - // DONE: deciding, passing, confirming, confirmation period not over - // DONE: deciding, failing, confirming, decision period not over - // DONE: deciding, failing, not confirming, decision period not over, alarm reset + one_fewer_deciding_queue_empty { + let (_caller, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + nudge::(index); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); + assert_eq!(DecidingCount::::get(&track), 1); + }: one_fewer_deciding(RawOrigin::Root, track.clone()) + verify { + assert_eq!(DecidingCount::::get(&track), 0); + } - // Deciding -> end - // DONE: deciding, passing, confirming, confirmation period over (accepted) - // DONE: deciding, failing, decision period over (rejected) + one_fewer_deciding_failing { + let (_caller, index) = create_referendum::(); + // No spaces free in the queue. + let queued = fill_queue::(index, 0, 90); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + let deciding_count = DecidingCount::::get(&track); + }: one_fewer_deciding(RawOrigin::Root, track.clone()) + verify { + assert_eq!(DecidingCount::::get(&track), deciding_count); + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); + assert!(queued.into_iter().skip(1).all(|i| Referenda::::ensure_ongoing(i) + .unwrap() + .deciding + .map_or(true, |d| d.confirming.is_none()) + )); + } - // Not deciding -> end - // DONE: not deciding, timeout + one_fewer_deciding_passing { + let (_caller, index) = create_referendum::(); + // No spaces free in the queue. + let queued = fill_queue::(index, 0, 0); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + let deciding_count = DecidingCount::::get(&track); + }: one_fewer_deciding(RawOrigin::Root, track.clone()) + verify { + assert_eq!(DecidingCount::::get(&track), deciding_count); + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); + assert!(queued.into_iter().skip(1).all(|i| Referenda::::ensure_ongoing(i) + .unwrap() + .deciding + .map_or(true, |d| d.confirming.is_some()) + )); + } - nudge_referendum_queued_insert { + nudge_referendum_requeued_insertion { // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); place_deposit::(index); @@ -324,7 +314,7 @@ benchmarks! { // Now nudge ours, with the track now full and the queue full of referenda with votes, // ours will not be in the queue. - Referenda::::nudge_referendum(RawOrigin::Root.into(), index); + nudge::(index); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); @@ -337,7 +327,7 @@ benchmarks! { assert_eq!(t[t.len() - 1].0, index); } - nudge_referendum_queued_slide { + nudge_referendum_requeued_slide { // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); place_deposit::(index); @@ -345,7 +335,7 @@ benchmarks! { // Now nudge ours, with the track now full, ours will be queued, but with no votes, it // will have the worst position. - Referenda::::nudge_referendum(RawOrigin::Root.into(), index); + nudge::(index); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); @@ -359,7 +349,7 @@ benchmarks! { assert_eq!(t[t.len() - 1].0, index); } - nudge_referendum_dd_pp_not_queued_track_full { + 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. @@ -380,7 +370,24 @@ benchmarks! { assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); } - nudge_referendum_no_dd_pp { + nudge_referendum_not_queued { + // First create our referendum and place the deposit. It will be failing. + let (_caller, index) = create_referendum::(); + place_deposit::(index); + fill_queue::(index, 0, 0); + + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert!(TrackQueue::::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::::get(&track).len() as u32, T::MaxQueued::get()); + assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); + } + + nudge_referendum_no_deposit { let (_caller, index) = create_referendum::(); skip_prepare_period::(index); }: nudge_referendum(RawOrigin::Root, index) @@ -389,7 +396,7 @@ benchmarks! { assert_matches!(status, ReferendumStatus { deciding: None, .. }); } - nudge_referendum_dd_no_pp { + nudge_referendum_preparing { let (_caller, index) = create_referendum::(); place_deposit::(index); }: nudge_referendum(RawOrigin::Root, index) @@ -398,7 +405,7 @@ benchmarks! { assert_matches!(status, ReferendumStatus { deciding: None, .. }); } - nudge_referendum_timeout { + nudge_referendum_timed_out { let (_caller, index) = create_referendum::(); skip_timeout_period::(index); }: nudge_referendum(RawOrigin::Root, index) @@ -407,7 +414,7 @@ benchmarks! { assert_matches!(info, ReferendumInfo::TimedOut(..)); } - nudge_referendum_dd_pp_failing { + nudge_referendum_begin_deciding_failing { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); @@ -416,7 +423,7 @@ benchmarks! { assert!(is_not_confirming::(index)); } - nudge_referendum_dd_pp_passing { + nudge_referendum_begin_deciding_passing { let (_caller, index) = create_referendum::(); place_deposit::(index); make_passing::(index); @@ -426,7 +433,7 @@ benchmarks! { assert!(is_confirming::(index)); } - nudge_referendum_deciding_passing_not_confirming { + nudge_referendum_begin_confirming { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); @@ -438,7 +445,7 @@ benchmarks! { assert!(is_confirming::(index)); } - nudge_referendum_deciding_failing_but_confirming { + nudge_referendum_end_confirming { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); @@ -451,7 +458,7 @@ benchmarks! { assert!(!is_confirming::(index)); } - nudge_referendum_deciding_failing_not_confirming { + nudge_referendum_continue_not_confirming { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); @@ -465,7 +472,7 @@ benchmarks! { assert!(!is_confirming::(index)); } - nudge_referendum_deciding_passing_and_confirming { + nudge_referendum_continue_confirming { let (_caller, index) = create_referendum::(); place_deposit::(index); make_passing::(index); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index f1fc78c232fed..26c1c8ac50f5e 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -282,7 +282,7 @@ pub mod pallet { /// - `enactment_moment`: The moment that the proposal should be enacted. /// /// Emits `Submitted`. - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::submit())] pub fn submit( origin: OriginFor, proposal_origin: PalletsOriginOf, @@ -310,7 +310,7 @@ pub mod pallet { decision_deposit: None, deciding: None, tally: Default::default(), - ayes_in_queue: None, + in_queue: false, alarm: Self::set_alarm(nudge_call, now + T::UndecidingTimeout::get()), }; ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); @@ -319,23 +319,23 @@ pub mod pallet { Ok(()) } - #[pallet::weight(0)] + #[pallet::weight(ServiceBranch::max_weight_of_deposit::())] pub fn place_decision_deposit( origin: OriginFor, index: ReferendumIndex, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let mut status = Self::ensure_ongoing(index)?; ensure!(status.decision_deposit.is_none(), Error::::HaveDeposit); let track = Self::track(status.track).ok_or(Error::::NoTrack)?; status.decision_deposit = Some(Self::take_deposit(who, track.decision_deposit)?); let now = frame_system::Pallet::::block_number(); - let info = Self::service_referendum(now, index, status).0; + let (info, _, branch) = Self::service_referendum(now, index, status); ReferendumInfoFor::::insert(index, info); - Ok(()) + Ok(branch.weight_of_deposit::().into()) } - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::refund_decision_deposit())] pub fn refund_decision_deposit( origin: OriginFor, index: ReferendumIndex, @@ -351,7 +351,7 @@ pub mod pallet { Ok(()) } - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::cancel())] pub fn cancel(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::CancelOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; @@ -370,7 +370,7 @@ pub mod pallet { Ok(()) } - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::kill())] pub fn kill(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::KillOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; @@ -388,29 +388,49 @@ pub mod pallet { } /// Advance a referendum onto its next logical state. Only used internally. - #[pallet::weight(0)] - pub fn nudge_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { + #[pallet::weight(ServiceBranch::max_weight_of_nudge::())] + pub fn nudge_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResultWithPostInfo { ensure_root(origin)?; let now = frame_system::Pallet::::block_number(); let mut status = Self::ensure_ongoing(index)?; // This is our wake-up, so we can disregard the alarm. status.alarm = None; - let (info, dirty) = Self::service_referendum(now, index, status); + let (info, dirty, branch) = Self::service_referendum(now, index, status); if dirty { ReferendumInfoFor::::insert(index, info); } - Ok(()) + Ok(Some(branch.weight_of_nudge::()).into()) } /// Advance a track onto its next logical state. Only used internally. - #[pallet::weight(0)] - pub fn defer_one_fewer_deciding( + /// + /// Action item for when there is now one fewer referendum in the deciding phase and the + /// `DecidingCount` is not yet updated. This means that we should either: + /// - begin deciding another referendum (and leave `DecidingCount` alone); or + /// - decrement `DecidingCount`. + #[pallet::weight(OneFewerDecidingBranch::max_weight::())] + pub fn one_fewer_deciding( origin: OriginFor, track: TrackIdOf, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { ensure_root(origin)?; - Self::act_one_fewer_deciding(track); - Ok(()) + let track_info = T::Tracks::info(track).ok_or(Error::::BadTrack)?; + let mut track_queue = TrackQueue::::get(track); + let branch = if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { + let now = frame_system::Pallet::::block_number(); + let (maybe_alarm, branch) + = Self::begin_deciding(&mut status, now, track_info); + if let Some(set_alarm) = maybe_alarm { + Self::ensure_alarm_at(&mut status, index, set_alarm); + } + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + TrackQueue::::insert(track, track_queue); + branch.into() + } else { + DecidingCount::::mutate(track, |x| x.saturating_dec()); + OneFewerDecidingBranch::QueueEmpty + }; + Ok(Some(branch.weight::()).into()) } } } @@ -466,6 +486,139 @@ impl Polls for Pallet { } } +enum BeginDecidingBranch { + Passing, + Failing, +} + +enum ServiceBranch { + Fail, + NoDeposit, + Preparing, + Queued, + NotQueued, + RequeuedInsertion, + RequeuedSlide, + BeginDecidingPassing, + BeginDecidingFailing, + BeginConfirming, + ContinueConfirming, + EndConfirming, + ContinueNotConfirming, + Approved, + Rejected, + TimedOut, +} + +impl From for ServiceBranch { + fn from(x: BeginDecidingBranch) -> Self { + use {BeginDecidingBranch::*, ServiceBranch::*}; + match x { + Passing => BeginDecidingPassing, + Failing => BeginDecidingFailing, + } + } +} + +impl ServiceBranch { + fn weight_of_nudge(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(), + } + } + + fn max_weight_of_nudge() -> 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()) + } + + fn weight_of_deposit(self) -> Option { + 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, + }) + } + + fn max_weight_of_deposit() -> 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()) + } +} + +enum OneFewerDecidingBranch { + QueueEmpty, + BeginDecidingPassing, + BeginDecidingFailing, +} + +impl From for OneFewerDecidingBranch { + fn from(x: BeginDecidingBranch) -> Self { + use {BeginDecidingBranch::*, OneFewerDecidingBranch::*}; + match x { + Passing => BeginDecidingPassing, + Failing => BeginDecidingFailing, + } + } +} + +impl OneFewerDecidingBranch { + fn weight(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(), + } + } + + fn max_weight() -> 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()) + } +} + impl Pallet { pub fn ensure_ongoing(index: ReferendumIndex) -> Result, DispatchError> { match ReferendumInfoFor::::get(index) { @@ -515,7 +668,7 @@ impl Pallet { .map(|x| (when, x)); debug_assert!( maybe_result.is_some(), - "Unable to schedule a new alarm at #{} (now: #{})?!", + "Unable to schedule a new alarm at #{:?} (now: #{:?})?!", when, frame_system::Pallet::::block_number() ); @@ -530,11 +683,9 @@ impl Pallet { /// This will properly set up the `confirming` item. fn begin_deciding( status: &mut ReferendumStatusOf, - index: ReferendumIndex, now: T::BlockNumber, track: &TrackInfoOf, - ) -> Option { - println!("Begining deciding for ref #{:?} at block #{:?}", index, now); + ) -> (Option, BeginDecidingBranch) { let is_passing = Self::is_passing( &status.tally, Zero::zero(), @@ -542,43 +693,42 @@ impl Pallet { &track.min_turnout, &track.min_approval, ); - dbg!( - is_passing, - &status.tally, - now, - track.decision_period, - &track.min_turnout, - &track.min_approval, - ); - status.ayes_in_queue = None; + status.in_queue = false; let confirming = if is_passing { Some(now.saturating_add(track.confirm_period)) } else { None }; let deciding_status = DecidingStatus { since: now, confirming }; let alarm = Self::decision_time(&deciding_status, &status.tally, track); status.deciding = Some(deciding_status); - Some(alarm) + let branch = if is_passing { + BeginDecidingBranch::Passing + } else { + BeginDecidingBranch::Failing + }; + (Some(alarm), branch) } + /// If it returns `Some`, deciding has begun and it needs waking at the given block number. The + /// second item is the flag for whether it is confirming or not. + /// + /// If `None`, then it is queued and should be nudged automatically as the queue gets drained. fn ready_for_deciding( now: T::BlockNumber, track: &TrackInfoOf, index: ReferendumIndex, status: &mut ReferendumStatusOf, - ) -> Option { - println!("ready_for_deciding ref #{:?}", index); + ) -> (Option, ServiceBranch) { let deciding_count = DecidingCount::::get(status.track); if deciding_count < track.max_deciding { // Begin deciding. - println!("Beginning deciding..."); DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); - Self::begin_deciding(status, index, now, track) + let r = Self::begin_deciding(status, now, track); + (r.0, r.1.into()) } else { // Add to queue. - println!("Queuing for decision."); let item = (index, status.tally.ayes()); - status.ayes_in_queue = Some(status.tally.ayes()); + status.in_queue = true; TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)); - None + (None, ServiceBranch::Queued) } } @@ -596,7 +746,7 @@ impl Pallet { } } - /// Schedule a call to `act_one_fewer_deciding` function via the dispatchable + /// Schedule a call to `one_fewer_deciding` function via the dispatchable /// `defer_one_fewer_deciding`. We could theoretically call it immediately (and it would be /// overall more efficient), however the weights become rather less easy to measure. fn note_one_fewer_deciding(track: TrackIdOf, _track_info: &TrackInfoOf) { @@ -611,42 +761,16 @@ impl Pallet { None, 128u8, frame_system::RawOrigin::Root.into(), - MaybeHashed::Value(Call::defer_one_fewer_deciding { track }.into()), + MaybeHashed::Value(Call::one_fewer_deciding { track }.into()), ); debug_assert!( maybe_result.is_ok(), - "Unable to schedule a new alarm at #{} (now: #{})?!", + "Unable to schedule a new alarm at #{:?} (now: #{:?})?!", when, now ); } - /// Action item for when there is now one fewer referendum in the deciding phase and the - /// `DecidingCount` is not yet updated. This means that we should either: - /// - begin deciding another referendum (and leave `DecidingCount` alone); or - /// - decrement `DecidingCount`. - /// - /// We defer the actual action to `act_one_fewer_deciding` function via a call in order to - /// simplify weight considerations. - fn act_one_fewer_deciding(track: TrackIdOf) { - if let Some(track_info) = T::Tracks::info(track) { - println!("One fewer deciding on {:?}", track); - let mut track_queue = TrackQueue::::get(track); - if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { - println!("Ref #{:?} ready for deciding", index); - let now = frame_system::Pallet::::block_number(); - let maybe_set_alarm = Self::begin_deciding(&mut status, index, now, track_info); - if let Some(set_alarm) = maybe_set_alarm { - Self::ensure_alarm_at(&mut status, index, set_alarm); - } - ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); - TrackQueue::::insert(track, track_queue); - } else { - DecidingCount::::mutate(track, |x| x.saturating_dec()); - } - } - } - /// Ensure that a `service_referendum` alarm happens for the referendum `index` at `alarm`. /// /// This will do nothing if the alarm is already set. @@ -658,13 +782,11 @@ impl Pallet { alarm: T::BlockNumber, ) -> bool { if status.alarm.as_ref().map_or(true, |&(when, _)| when != alarm) { - println!("Setting alarm at block #{:?} for ref {:?}", alarm, index); // Either no alarm or one that was different Self::ensure_no_alarm(status); status.alarm = Self::set_alarm(Call::nudge_referendum { index }, alarm); true } else { - println!("Keeping alarm at block #{:?} for ref {:?}", alarm, index); false } } @@ -694,124 +816,142 @@ impl Pallet { now: T::BlockNumber, index: ReferendumIndex, mut status: ReferendumStatusOf, - ) -> (ReferendumInfoOf, bool) { - println!("#{:?}: Servicing #{:?}", now, index); + ) -> (ReferendumInfoOf, bool, ServiceBranch) { let mut dirty = false; // Should it begin being decided? let track = match Self::track(status.track) { Some(x) => x, - None => return (ReferendumInfo::Ongoing(status), false), + None => return (ReferendumInfo::Ongoing(status), false, ServiceBranch::Fail), }; let timeout = status.submitted + T::UndecidingTimeout::get(); // Default the alarm to the submission timeout. let mut alarm = timeout; - if status.deciding.is_none() { - // Are we already queued for deciding? - if let Some(_) = status.ayes_in_queue.as_ref() { - println!("Already queued..."); - // Does our position in the queue need updating? - let ayes = status.tally.ayes(); - let mut queue = TrackQueue::::get(status.track); - let maybe_old_pos = queue.iter().position(|(x, _)| *x == index); - let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x); - if maybe_old_pos.is_none() && new_pos > 0 { - // Just insert. - queue.force_insert_keep_right(new_pos, (index, ayes)); - } else if let Some(old_pos) = maybe_old_pos { - // We were in the queue - slide into the correct position. - queue[old_pos].1 = ayes; - queue.slide(old_pos, new_pos); - } - TrackQueue::::insert(status.track, queue); - } else { - // Are we ready for deciding? - if status.decision_deposit.is_some() { - let prepare_end = status.submitted.saturating_add(track.prepare_period); - if now >= prepare_end { - if let Some(set_alarm) = - Self::ready_for_deciding(now, &track, index, &mut status) - { - alarm = alarm.min(set_alarm); + let branch; + match &mut status.deciding { + None => { + // Are we already queued for deciding? + if status.in_queue { + // Does our position in the queue need updating? + let ayes = status.tally.ayes(); + let mut queue = TrackQueue::::get(status.track); + let maybe_old_pos = queue.iter().position(|(x, _)| *x == index); + let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x); + branch = if maybe_old_pos.is_none() && new_pos > 0 { + // Just insert. + queue.force_insert_keep_right(new_pos, (index, ayes)); + ServiceBranch::RequeuedInsertion + } else if let Some(old_pos) = maybe_old_pos { + // We were in the queue - slide into the correct position. + queue[old_pos].1 = ayes; + queue.slide(old_pos, new_pos); + ServiceBranch::RequeuedSlide + } else { + ServiceBranch::NotQueued + }; + TrackQueue::::insert(status.track, queue); + } else { + // Are we ready for deciding? + branch = if status.decision_deposit.is_some() { + let prepare_end = status.submitted.saturating_add(track.prepare_period); + if now >= prepare_end { + let (maybe_alarm, branch) = Self::ready_for_deciding(now, &track, index, &mut status); + if let Some(set_alarm) = maybe_alarm { + alarm = alarm.min(set_alarm); + } + dirty = true; + branch + } else { + alarm = alarm.min(prepare_end); + ServiceBranch::Preparing } - dirty = true; } else { - println!("Not deciding, have DD, within PP: Setting alarm to end of PP"); - alarm = alarm.min(prepare_end); + ServiceBranch::NoDeposit } } - } - // If we didn't move into being decided, then check the timeout. - if status.deciding.is_none() && now >= timeout { - // Too long without being decided - end it. - Self::ensure_no_alarm(&mut status); - Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); - return ( - ReferendumInfo::TimedOut( - now, - status.submission_deposit, - status.decision_deposit, - ), - true, - ) - } - } else if let Some(deciding) = &mut status.deciding { - let is_passing = Self::is_passing( - &status.tally, - now.saturating_sub(deciding.since), - track.decision_period, - &track.min_turnout, - &track.min_approval, - ); - if is_passing { - if deciding.confirming.map_or(false, |c| now >= c) { - // Passed! - Self::ensure_no_alarm(&mut status); - Self::note_one_fewer_deciding(status.track, track); - let (desired, call_hash) = (status.enactment, status.proposal_hash); - Self::schedule_enactment(index, track, desired, status.origin, call_hash); - Self::deposit_event(Event::::Confirmed { index, tally: status.tally }); - return ( - ReferendumInfo::Approved( - now, - status.submission_deposit, - status.decision_deposit, - ), - true, - ) - } - if deciding.confirming.is_none() { - // Start confirming - dirty = true; - deciding.confirming = Some(now.saturating_add(track.confirm_period)); - } - } else { - if now >= deciding.since.saturating_add(track.decision_period) { - // Failed! + // If we didn't move into being decided, then check the timeout. + if status.deciding.is_none() && now >= timeout { + // Too long without being decided - end it. Self::ensure_no_alarm(&mut status); - Self::note_one_fewer_deciding(status.track, track); - Self::deposit_event(Event::::Rejected { index, tally: status.tally }); + Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); return ( - ReferendumInfo::Rejected( + ReferendumInfo::TimedOut( now, status.submission_deposit, status.decision_deposit, ), true, + ServiceBranch::TimedOut, ) } - if deciding.confirming.is_some() { - // Stop confirming - dirty = true; - deciding.confirming = None; - } - } - dbg!(&deciding, &status.tally); - alarm = Self::decision_time(&deciding, &status.tally, track); - println!("decision time for ref #{:?} is {:?}", index, alarm); + }, + Some(deciding) => { + let is_passing = Self::is_passing( + &status.tally, + now.saturating_sub(deciding.since), + track.decision_period, + &track.min_turnout, + &track.min_approval, + ); + branch = if is_passing { + match deciding.confirming.clone() { + Some(t) if now >= t => { + // Passed! + Self::ensure_no_alarm(&mut status); + Self::note_one_fewer_deciding(status.track, track); + let (desired, call_hash) = (status.enactment, status.proposal_hash); + Self::schedule_enactment(index, track, desired, status.origin, call_hash); + Self::deposit_event(Event::::Confirmed { index, tally: status.tally }); + return ( + ReferendumInfo::Approved( + now, + status.submission_deposit, + status.decision_deposit, + ), + true, + ServiceBranch::Approved, + ) + } + Some(_) => { + ServiceBranch::ContinueConfirming + } + None => { + // Start confirming + dirty = true; + deciding.confirming = Some(now.saturating_add(track.confirm_period)); + ServiceBranch::BeginConfirming + } + } + } else { + if now >= deciding.since.saturating_add(track.decision_period) { + // Failed! + Self::ensure_no_alarm(&mut status); + Self::note_one_fewer_deciding(status.track, track); + Self::deposit_event(Event::::Rejected { index, tally: status.tally }); + return ( + ReferendumInfo::Rejected( + now, + status.submission_deposit, + status.decision_deposit, + ), + true, + ServiceBranch::Rejected, + ) + } + if deciding.confirming.is_some() { + // Stop confirming + dirty = true; + deciding.confirming = None; + ServiceBranch::EndConfirming + } else { + ServiceBranch::ContinueNotConfirming + } + }; + alarm = Self::decision_time(&deciding, &status.tally, track); + }, } let dirty_alarm = Self::ensure_alarm_at(&mut status, index, alarm); - (ReferendumInfo::Ongoing(status), dirty_alarm || dirty) + (ReferendumInfo::Ongoing(status), dirty_alarm || dirty, branch) } fn decision_time( @@ -826,7 +966,6 @@ impl Pallet { let until_approval = track.min_approval.delay(approval); let until_turnout = track.min_turnout.delay(turnout); let offset = until_turnout.max(until_approval); - dbg!(approval, turnout, until_approval, until_turnout, offset); deciding.since.saturating_add(offset * track.decision_period) }) } diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 8f8ed47616a3b..0aecd868727e4 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -36,8 +36,6 @@ use sp_runtime::{ DispatchResult, Perbill, }; -const MAX_PROPOSALS: u32 = 100; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -135,16 +133,6 @@ impl pallet_balances::Config for Test { type WeightInfo = (); } parameter_types! { - pub const LaunchPeriod: u64 = 2; - pub const VotingPeriod: u64 = 2; - pub const FastTrackVotingPeriod: u64 = 2; - pub const MinimumDeposit: u64 = 1; - pub const VoteLockingPeriod: u64 = 3; - pub const CooloffPeriod: u64 = 2; - pub const MaxVotes: u32 = 100; - pub const MaxProposals: u32 = MAX_PROPOSALS; - pub static PreimageByteDeposit: u64 = 0; - pub static InstantAllowed: bool = false; pub static AlarmInterval: u64 = 1; pub const SubmissionDeposit: u64 = 2; pub const MaxQueued: u32 = 3; diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index da4eed49fe21f..11d7002c31774 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -324,7 +324,7 @@ fn tracks_are_distinguished() { decision_deposit: Some(Deposit { who: 3, amount: 10 }), deciding: None, tally: Tally { ayes: 0, nays: 0 }, - ayes_in_queue: None, + in_queue: false, alarm: Some((5, (5, 0))), }) ), @@ -340,7 +340,7 @@ fn tracks_are_distinguished() { decision_deposit: Some(Deposit { who: 4, amount: 1 }), deciding: None, tally: Tally { ayes: 0, nays: 0 }, - ayes_in_queue: None, + in_queue: false, alarm: Some((3, (3, 0))), }) ), diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index c2810c57f2112..13cce36334ab1 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -18,6 +18,7 @@ //! Miscellaneous additional datatypes. use super::*; +use sp_std::fmt::Debug; use codec::{Decode, Encode, EncodeLike}; use frame_support::{traits::schedule::Anon, Parameter}; use scale_info::TypeInfo; @@ -38,7 +39,6 @@ pub type ReferendumInfoOf = ReferendumInfo< ::BlockNumber, ::Hash, BalanceOf, - VotesOf, TallyOf, ::AccountId, ScheduleAddressOf, @@ -49,7 +49,6 @@ pub type ReferendumStatusOf = ReferendumStatus< ::BlockNumber, ::Hash, BalanceOf, - VotesOf, TallyOf, ::AccountId, ScheduleAddressOf, @@ -144,26 +143,26 @@ pub struct Deposit { #[derive(Clone, Encode, TypeInfo)] pub struct TrackInfo { /// Name of this track. - pub(crate) name: &'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(crate) max_deciding: u32, + pub max_deciding: u32, /// Amount that must be placed on deposit before a decision can be made. - pub(crate) decision_deposit: Balance, + pub decision_deposit: Balance, /// Amount of time this must be submitted for before a decision can be made. - pub(crate) prepare_period: Moment, + pub prepare_period: Moment, /// Amount of time that a decision may take to be approved prior to cancellation. - pub(crate) decision_period: Moment, + pub decision_period: Moment, /// Amount of time that the approval criteria must hold before it can be approved. - pub(crate) confirm_period: Moment, + pub confirm_period: Moment, /// Minimum amount of time that an approved proposal must be in the dispatch queue. - pub(crate) min_enactment_period: Moment, + 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(crate) min_approval: Curve, + 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(crate) min_turnout: Curve, + pub min_turnout: Curve, } pub trait TracksInfo { @@ -199,23 +198,14 @@ impl AtOrAfter { /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct ReferendumStatus< - TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Moment: Parameter - + Eq - + PartialEq - + sp_std::fmt::Debug - + Encode - + Decode - + TypeInfo - + Clone - + EncodeLike, - Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + 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, @@ -236,41 +226,27 @@ pub struct ReferendumStatus< pub(crate) deciding: Option>, /// The current tally of votes in this referendum. pub(crate) tally: Tally, - /// The number of aye votes we are in the track queue for, if any. `None` if we're not - /// yet in the deciding queue or are already deciding. If a vote results in fewer ayes - /// in the `tally` than this, then the voter is required to pay to reorder the track queue. - /// Automatic advancement is scheduled when ayes_in_queue is Some value greater than the - /// ayes in `tally`. - pub(crate) ayes_in_queue: Option, + /// 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)] pub enum ReferendumInfo< - TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Moment: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, - Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, + 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, - Votes, - Tally, - AccountId, - ScheduleAddress, - >, + ReferendumStatus, ), /// Referendum finished with approval. Submission deposit is held. Approved(Moment, Deposit, Option>), @@ -285,25 +261,15 @@ pub enum ReferendumInfo< } impl< - TrackId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Origin: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Moment: Parameter - + Eq - + PartialEq - + sp_std::fmt::Debug - + Encode - + Decode - + TypeInfo - + Clone - + EncodeLike, - Hash: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Balance: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Votes: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - Tally: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - AccountId: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - ScheduleAddress: Eq + PartialEq + sp_std::fmt::Debug + Encode + Decode + TypeInfo + Clone, - > ReferendumInfo -{ + 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 { pub fn take_decision_deposit(&mut self) -> Result>, ()> { use ReferendumInfo::*; match self { @@ -342,7 +308,7 @@ impl Curve { } #[cfg(feature = "std")] -impl sp_std::fmt::Debug for Curve { +impl Debug for Curve { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { match self { Self::LinearDecreasing { begin, delta } => { diff --git a/frame/referenda/src/weights.rs b/frame/referenda/src/weights.rs index 638852d3c7e19..caae772424af1 100644 --- a/frame/referenda/src/weights.rs +++ b/frame/referenda/src/weights.rs @@ -15,26 +15,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_democracy +//! Autogenerated weights for pallet_referenda //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-09-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-19, STEPS: `1`, REPEAT: 100, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/substrate +// ../../../target/release/substrate // benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_democracy -// --extrinsic=* +// --chain +// dev +// --steps +// 1 +// --repeat +// 100 +// --pallet +// pallet-referenda +// --extrinsic +// * +// --raw // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/democracy/src/weights.rs -// --template=./.maintain/frame-weight-template.hbs - +// --output +// ../../../frame/referenda/src/weights.rs +// --template +// ../../../.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -43,267 +49,241 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for pallet_democracy. +/// Weight functions needed for pallet_referenda. pub trait WeightInfo { - fn propose() -> Weight; - fn second(s: u32, ) -> Weight; - fn vote_new(r: u32, ) -> Weight; - fn vote_existing(r: u32, ) -> Weight; - fn emergency_cancel() -> Weight; - fn blacklist(p: u32, ) -> Weight; - fn external_propose(v: u32, ) -> Weight; - fn external_propose_majority() -> Weight; - fn external_propose_default() -> Weight; - fn fast_track() -> Weight; - fn veto_external(v: u32, ) -> Weight; - fn cancel_proposal(p: u32, ) -> Weight; - fn cancel_referendum() -> Weight; - fn cancel_queued(r: u32, ) -> Weight; - fn on_initialize_base(r: u32, ) -> Weight; - fn on_initialize_base_with_launch_period(r: u32, ) -> Weight; - fn delegate(r: u32, ) -> Weight; - fn undelegate(r: u32, ) -> Weight; - fn clear_public_proposals() -> Weight; - fn note_preimage(b: u32, ) -> Weight; - fn note_imminent_preimage(b: u32, ) -> Weight; - fn reap_preimage(b: u32, ) -> Weight; - fn unlock_remove(r: u32, ) -> Weight; - fn unlock_set(r: u32, ) -> Weight; - fn remove_vote(r: u32, ) -> Weight; - fn remove_other_vote(r: u32, ) -> Weight; + 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_democracy using the Substrate node and recommended hardware. +/// Weights for pallet_referenda using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Democracy PublicPropCount (r:1 w:1) - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - // Storage: Democracy DepositOf (r:0 w:1) - fn propose() -> Weight { - (67_388_000 as Weight) + // Storage: Referenda ReferendumCount (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:0 w:1) + fn submit() -> Weight { + (28_000_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 { + (32_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Democracy DepositOf (r:1 w:1) - fn second(s: u32, ) -> Weight { - (41_157_000 as Weight) - // Standard Error: 0 - .saturating_add((157_000 as Weight).saturating_mul(s 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 { + (35_000_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 { + (34_000_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 { + (36_000_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 { + (35_000_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 { + (19_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - fn vote_new(r: u32, ) -> Weight { - (46_406_000 as Weight) - // Standard Error: 1_000 - .saturating_add((170_000 as Weight).saturating_mul(r as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn cancel() -> Weight { + (27_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - fn vote_existing(r: u32, ) -> Weight { - (46_071_000 as Weight) - // Standard Error: 1_000 - .saturating_add((166_000 as Weight).saturating_mul(r as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn kill() -> Weight { + (45_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Cancellations (r:1 w:1) - fn emergency_cancel() -> Weight { - (27_699_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Blacklist (r:0 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn blacklist(p: u32, ) -> Weight { - (82_703_000 as Weight) - // Standard Error: 4_000 - .saturating_add((500_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - fn external_propose(v: u32, ) -> Weight { - (13_747_000 as Weight) - // Standard Error: 0 - .saturating_add((76_000 as Weight).saturating_mul(v as Weight)) + // Storage: Referenda TrackQueue (r:1 w:0) + // Storage: Referenda DecidingCount (r:1 w:1) + fn one_fewer_deciding_queue_empty() -> Weight { + (5_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - // Storage: Democracy NextExternal (r:0 w:1) - fn external_propose_majority() -> Weight { - (3_070_000 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 { + (73_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } - // Storage: Democracy NextExternal (r:0 w:1) - fn external_propose_default() -> Weight { - (3_080_000 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_passing() -> Weight { + (72_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:0 w:1) - fn fast_track() -> Weight { - (29_129_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 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 { + (32_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:1) - fn veto_external(v: u32, ) -> Weight { - (30_105_000 as Weight) - // Standard Error: 0 - .saturating_add((104_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn cancel_proposal(p: u32, ) -> Weight { - (55_228_000 as Weight) - // Standard Error: 1_000 - .saturating_add((457_000 as Weight).saturating_mul(p 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 { + (32_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:0 w:1) - fn cancel_referendum() -> Weight { - (17_319_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 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 { + (35_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Scheduler Lookup (r:1 w:1) + // 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 { + (34_000_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 cancel_queued(r: u32, ) -> Weight { - (29_738_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_153_000 as Weight).saturating_mul(r as Weight)) + fn nudge_referendum_no_deposit() -> Weight { + (18_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base(r: u32, ) -> Weight { - (2_165_000 as Weight) - // Standard Error: 3_000 - .saturating_add((5_577_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy LastTabledWasExternal (r:1 w:0) - // Storage: Democracy NextExternal (r:1 w:0) - // Storage: Democracy PublicProps (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - (9_396_000 as Weight) - // Standard Error: 4_000 - .saturating_add((5_604_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy VotingOf (r:3 w:3) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - fn delegate(r: u32, ) -> Weight { - (57_783_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_623_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy VotingOf (r:2 w:2) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - fn undelegate(r: u32, ) -> Weight { - (26_027_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_593_000 as Weight).saturating_mul(r as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_preparing() -> Weight { + (18_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) } - // Storage: Democracy PublicProps (r:0 w:1) - fn clear_public_proposals() -> Weight { - (2_780_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_preimage(b: u32, ) -> Weight { - (46_416_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_imminent_preimage(b: u32, ) -> Weight { - (29_735_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn nudge_referendum_timed_out() -> Weight { + (13_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - // Storage: Democracy Preimages (r:1 w:1) - // Storage: System Account (r:1 w:0) - fn reap_preimage(b: u32, ) -> Weight { - (41_276_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn unlock_remove(r: u32, ) -> Weight { - (40_348_000 as Weight) - // Standard Error: 1_000 - .saturating_add((60_000 as Weight).saturating_mul(r 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 { + (20_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn unlock_set(r: u32, ) -> Weight { - (37_475_000 as Weight) - // Standard Error: 1_000 - .saturating_add((151_000 as Weight).saturating_mul(r 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 { + (20_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - fn remove_vote(r: u32, ) -> Weight { - (19_970_000 as Weight) - // Standard Error: 1_000 - .saturating_add((153_000 as Weight).saturating_mul(r as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_begin_confirming() -> Weight { + (19_000_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 { + (19_000_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 { + (19_000_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 { + (19_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - fn remove_other_vote(r: u32, ) -> Weight { - (20_094_000 as Weight) - // Standard Error: 1_000 - .saturating_add((157_000 as Weight).saturating_mul(r 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 { + (38_000_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 { + (22_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -311,234 +291,207 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Democracy PublicPropCount (r:1 w:1) - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - // Storage: Democracy DepositOf (r:0 w:1) - fn propose() -> Weight { - (67_388_000 as Weight) + // Storage: Referenda ReferendumCount (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:0 w:1) + fn submit() -> Weight { + (28_000_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 { + (32_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: Democracy DepositOf (r:1 w:1) - fn second(s: u32, ) -> Weight { - (41_157_000 as Weight) - // Standard Error: 0 - .saturating_add((157_000 as Weight).saturating_mul(s 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 { + (35_000_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 { + (34_000_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 { + (36_000_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 { + (35_000_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 { + (19_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - fn vote_new(r: u32, ) -> Weight { - (46_406_000 as Weight) - // Standard Error: 1_000 - .saturating_add((170_000 as Weight).saturating_mul(r as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn cancel() -> Weight { + (27_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - fn vote_existing(r: u32, ) -> Weight { - (46_071_000 as Weight) - // Standard Error: 1_000 - .saturating_add((166_000 as Weight).saturating_mul(r as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn kill() -> Weight { + (45_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Cancellations (r:1 w:1) - fn emergency_cancel() -> Weight { - (27_699_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Blacklist (r:0 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn blacklist(p: u32, ) -> Weight { - (82_703_000 as Weight) - // Standard Error: 4_000 - .saturating_add((500_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - fn external_propose(v: u32, ) -> Weight { - (13_747_000 as Weight) - // Standard Error: 0 - .saturating_add((76_000 as Weight).saturating_mul(v as Weight)) + // Storage: Referenda TrackQueue (r:1 w:0) + // Storage: Referenda DecidingCount (r:1 w:1) + fn one_fewer_deciding_queue_empty() -> Weight { + (5_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - // Storage: Democracy NextExternal (r:0 w:1) - fn external_propose_majority() -> Weight { - (3_070_000 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 { + (73_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } - // Storage: Democracy NextExternal (r:0 w:1) - fn external_propose_default() -> Weight { - (3_080_000 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_passing() -> Weight { + (72_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:0 w:1) - fn fast_track() -> Weight { - (29_129_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 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 { + (32_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:1) - fn veto_external(v: u32, ) -> Weight { - (30_105_000 as Weight) - // Standard Error: 0 - .saturating_add((104_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn cancel_proposal(p: u32, ) -> Weight { - (55_228_000 as Weight) - // Standard Error: 1_000 - .saturating_add((457_000 as Weight).saturating_mul(p 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 { + (32_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:0 w:1) - fn cancel_referendum() -> Weight { - (17_319_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 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 { + (35_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: Scheduler Lookup (r:1 w:1) + // 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 { + (34_000_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 cancel_queued(r: u32, ) -> Weight { - (29_738_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_153_000 as Weight).saturating_mul(r as Weight)) + fn nudge_referendum_no_deposit() -> Weight { + (18_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base(r: u32, ) -> Weight { - (2_165_000 as Weight) - // Standard Error: 3_000 - .saturating_add((5_577_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy LastTabledWasExternal (r:1 w:0) - // Storage: Democracy NextExternal (r:1 w:0) - // Storage: Democracy PublicProps (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - (9_396_000 as Weight) - // Standard Error: 4_000 - .saturating_add((5_604_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy VotingOf (r:3 w:3) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - fn delegate(r: u32, ) -> Weight { - (57_783_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_623_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy VotingOf (r:2 w:2) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - fn undelegate(r: u32, ) -> Weight { - (26_027_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_593_000 as Weight).saturating_mul(r as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_preparing() -> Weight { + (18_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) } - // Storage: Democracy PublicProps (r:0 w:1) - fn clear_public_proposals() -> Weight { - (2_780_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_preimage(b: u32, ) -> Weight { - (46_416_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_imminent_preimage(b: u32, ) -> Weight { - (29_735_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn nudge_referendum_timed_out() -> Weight { + (13_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - // Storage: Democracy Preimages (r:1 w:1) - // Storage: System Account (r:1 w:0) - fn reap_preimage(b: u32, ) -> Weight { - (41_276_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn unlock_remove(r: u32, ) -> Weight { - (40_348_000 as Weight) - // Standard Error: 1_000 - .saturating_add((60_000 as Weight).saturating_mul(r 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 { + (20_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn unlock_set(r: u32, ) -> Weight { - (37_475_000 as Weight) - // Standard Error: 1_000 - .saturating_add((151_000 as Weight).saturating_mul(r 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 { + (20_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - fn remove_vote(r: u32, ) -> Weight { - (19_970_000 as Weight) - // Standard Error: 1_000 - .saturating_add((153_000 as Weight).saturating_mul(r as Weight)) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_begin_confirming() -> Weight { + (19_000_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 { + (19_000_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 { + (19_000_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 { + (19_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - fn remove_other_vote(r: u32, ) -> Weight { - (20_094_000 as Weight) - // Standard Error: 1_000 - .saturating_add((157_000 as Weight).saturating_mul(r 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 { + (38_000_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 { + (22_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } From a3d67ee7814d7cd3557253a6563d6c7d5af633fd Mon Sep 17 00:00:00 2001 From: Parity Bot Date: Sun, 19 Dec 2021 18:46:59 +0000 Subject: [PATCH 34/66] 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 --- frame/referenda/src/weights.rs | 135 ++++++++++++++++----------------- 1 file changed, 64 insertions(+), 71 deletions(-) diff --git a/frame/referenda/src/weights.rs b/frame/referenda/src/weights.rs index caae772424af1..202901bdd10bd 100644 --- a/frame/referenda/src/weights.rs +++ b/frame/referenda/src/weights.rs @@ -18,29 +18,22 @@ //! 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: `1`, REPEAT: 100, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 +// target/release/substrate // benchmark -// --chain -// dev -// --steps -// 1 -// --repeat -// 100 -// --pallet -// pallet-referenda -// --extrinsic -// * -// --raw +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_referenda +// --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --output -// ../../../frame/referenda/src/weights.rs -// --template -// ../../../.maintain/frame-weight-template.hbs +// --heap-pages=4096 +// --output=./frame/referenda/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -87,14 +80,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - (28_000_000 as 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 { - (32_000_000 as Weight) + (48_113_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -102,7 +95,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_queued() -> Weight { - (35_000_000 as Weight) + (53_624_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -110,7 +103,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_not_queued() -> Weight { - (34_000_000 as Weight) + (52_560_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -118,7 +111,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_passing() -> Weight { - (36_000_000 as Weight) + (54_067_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -126,34 +119,34 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_failing() -> Weight { - (35_000_000 as 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 { - (19_000_000 as 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 { - (27_000_000 as 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 { - (45_000_000 as 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 { - (5_000_000 as Weight) + (8_904_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -161,7 +154,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_failing() -> Weight { - (73_000_000 as Weight) + (181_387_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -169,7 +162,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_passing() -> Weight { - (72_000_000 as Weight) + (179_753_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -177,7 +170,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_insertion() -> Weight { - (32_000_000 as Weight) + (53_592_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -185,7 +178,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_slide() -> Weight { - (32_000_000 as Weight) + (53_173_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -194,7 +187,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_queued() -> Weight { - (35_000_000 as Weight) + (55_770_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -203,27 +196,27 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_not_queued() -> Weight { - (34_000_000 as 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 { - (18_000_000 as 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 { - (18_000_000 as 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 { - (13_000_000 as Weight) + (20_256_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -231,7 +224,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_failing() -> Weight { - (20_000_000 as Weight) + (30_964_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -239,35 +232,35 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_passing() -> Weight { - (20_000_000 as 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 { - (19_000_000 as 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 { - (19_000_000 as 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 { - (19_000_000 as 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 { - (19_000_000 as Weight) + (29_661_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -276,14 +269,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn nudge_referendum_approved() -> Weight { - (38_000_000 as 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 { - (22_000_000 as Weight) + (32_726_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -295,14 +288,14 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - (28_000_000 as 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 { - (32_000_000 as Weight) + (48_113_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -310,7 +303,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_queued() -> Weight { - (35_000_000 as Weight) + (53_624_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -318,7 +311,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_not_queued() -> Weight { - (34_000_000 as Weight) + (52_560_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -326,7 +319,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_passing() -> Weight { - (36_000_000 as Weight) + (54_067_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -334,34 +327,34 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_failing() -> Weight { - (35_000_000 as 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 { - (19_000_000 as 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 { - (27_000_000 as 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 { - (45_000_000 as 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 { - (5_000_000 as Weight) + (8_904_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -369,7 +362,7 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_failing() -> Weight { - (73_000_000 as Weight) + (181_387_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -377,7 +370,7 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_passing() -> Weight { - (72_000_000 as Weight) + (179_753_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -385,7 +378,7 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_insertion() -> Weight { - (32_000_000 as Weight) + (53_592_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -393,7 +386,7 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_slide() -> Weight { - (32_000_000 as Weight) + (53_173_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -402,7 +395,7 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_queued() -> Weight { - (35_000_000 as Weight) + (55_770_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -411,27 +404,27 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_not_queued() -> Weight { - (34_000_000 as 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 { - (18_000_000 as 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 { - (18_000_000 as 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 { - (13_000_000 as Weight) + (20_256_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -439,7 +432,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_failing() -> Weight { - (20_000_000 as Weight) + (30_964_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -447,35 +440,35 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_passing() -> Weight { - (20_000_000 as 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 { - (19_000_000 as 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 { - (19_000_000 as 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 { - (19_000_000 as 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 { - (19_000_000 as Weight) + (29_661_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -484,14 +477,14 @@ impl WeightInfo for () { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn nudge_referendum_approved() -> Weight { - (38_000_000 as 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 { - (22_000_000 as Weight) + (32_726_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } From 72193b3789dd859966168b3eef52699286dafb82 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 19 Dec 2021 18:53:30 +0000 Subject: [PATCH 35/66] Events finished --- frame/referenda/src/lib.rs | 166 +++++++------------------------------ 1 file changed, 28 insertions(+), 138 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 26c1c8ac50f5e..92bdcac13f87a 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -45,6 +45,7 @@ use sp_runtime::{ }; use sp_std::{fmt::Debug, prelude::*}; +mod branch; mod types; pub mod weights; pub use pallet::*; @@ -55,6 +56,7 @@ pub use types::{ TrackInfoOf, TracksInfo, VotesOf, }; pub use weights::WeightInfo; +use branch::{ServiceBranch, BeginDecidingBranch, OneFewerDecidingBranch}; #[cfg(test)] mod mock; @@ -192,7 +194,7 @@ pub mod pallet { proposal_hash: T::Hash, }, /// A referendum has moved into the deciding phase. - StartedDeciding { + DecisionStarted { /// Index of the referendum. index: ReferendumIndex, /// The track (and by extension proposal dispatch origin) of this referendum. @@ -202,6 +204,14 @@ pub mod pallet { /// The current tally of votes in this referendum. tally: T::Tally, }, + ConfirmStarted { + /// Index of the referendum. + index: ReferendumIndex, + }, + ConfirmAborted { + /// Index of the referendum. + index: ReferendumIndex, + }, /// A referendum has ended its confirmation phase and is ready for approval. Confirmed { /// Index of the referendum. @@ -419,7 +429,7 @@ pub mod pallet { let branch = if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { let now = frame_system::Pallet::::block_number(); let (maybe_alarm, branch) - = Self::begin_deciding(&mut status, now, track_info); + = Self::begin_deciding(&mut status, index, now, track_info); if let Some(set_alarm) = maybe_alarm { Self::ensure_alarm_at(&mut status, index, set_alarm); } @@ -486,139 +496,6 @@ impl Polls for Pallet { } } -enum BeginDecidingBranch { - Passing, - Failing, -} - -enum ServiceBranch { - Fail, - NoDeposit, - Preparing, - Queued, - NotQueued, - RequeuedInsertion, - RequeuedSlide, - BeginDecidingPassing, - BeginDecidingFailing, - BeginConfirming, - ContinueConfirming, - EndConfirming, - ContinueNotConfirming, - Approved, - Rejected, - TimedOut, -} - -impl From for ServiceBranch { - fn from(x: BeginDecidingBranch) -> Self { - use {BeginDecidingBranch::*, ServiceBranch::*}; - match x { - Passing => BeginDecidingPassing, - Failing => BeginDecidingFailing, - } - } -} - -impl ServiceBranch { - fn weight_of_nudge(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(), - } - } - - fn max_weight_of_nudge() -> 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()) - } - - fn weight_of_deposit(self) -> Option { - 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, - }) - } - - fn max_weight_of_deposit() -> 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()) - } -} - -enum OneFewerDecidingBranch { - QueueEmpty, - BeginDecidingPassing, - BeginDecidingFailing, -} - -impl From for OneFewerDecidingBranch { - fn from(x: BeginDecidingBranch) -> Self { - use {BeginDecidingBranch::*, OneFewerDecidingBranch::*}; - match x { - Passing => BeginDecidingPassing, - Failing => BeginDecidingFailing, - } - } -} - -impl OneFewerDecidingBranch { - fn weight(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(), - } - } - - fn max_weight() -> 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()) - } -} - impl Pallet { pub fn ensure_ongoing(index: ReferendumIndex) -> Result, DispatchError> { match ReferendumInfoFor::::get(index) { @@ -683,6 +560,7 @@ impl Pallet { /// This will properly set up the `confirming` item. fn begin_deciding( status: &mut ReferendumStatusOf, + index: ReferendumIndex, now: T::BlockNumber, track: &TrackInfoOf, ) -> (Option, BeginDecidingBranch) { @@ -694,8 +572,18 @@ impl Pallet { &track.min_approval, ); status.in_queue = false; - let confirming = - if is_passing { Some(now.saturating_add(track.confirm_period)) } else { None }; + Self::deposit_event(Event::::DecisionStarted { + index, + tally: status.tally.clone(), + proposal_hash: status.proposal_hash.clone(), + track: status.track.clone(), + }); + let confirming = if is_passing { + Self::deposit_event(Event::::ConfirmStarted { index }); + Some(now.saturating_add(track.confirm_period)) + } else { + None + }; let deciding_status = DecidingStatus { since: now, confirming }; let alarm = Self::decision_time(&deciding_status, &status.tally, track); status.deciding = Some(deciding_status); @@ -721,7 +609,7 @@ impl Pallet { if deciding_count < track.max_deciding { // Begin deciding. DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); - let r = Self::begin_deciding(status, now, track); + let r = Self::begin_deciding(status, index, now, track); (r.0, r.1.into()) } else { // Add to queue. @@ -918,6 +806,7 @@ impl Pallet { // Start confirming dirty = true; deciding.confirming = Some(now.saturating_add(track.confirm_period)); + Self::deposit_event(Event::::ConfirmStarted { index }); ServiceBranch::BeginConfirming } } @@ -941,6 +830,7 @@ impl Pallet { // Stop confirming dirty = true; deciding.confirming = None; + Self::deposit_event(Event::::ConfirmAborted { index }); ServiceBranch::EndConfirming } else { ServiceBranch::ContinueNotConfirming From d785347ce3fd1db94dca9205d2c83fc2f6047d13 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 19 Dec 2021 18:56:23 +0000 Subject: [PATCH 36/66] Missing file --- frame/referenda/src/branch.rs | 152 ++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 frame/referenda/src/branch.rs diff --git a/frame/referenda/src/branch.rs b/frame/referenda/src/branch.rs new file mode 100644 index 0000000000000..69c54415a9eae --- /dev/null +++ b/frame/referenda/src/branch.rs @@ -0,0 +1,152 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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.0sp_runtime::{DispatchResult, traits::One}asp_runtime::{DispatchResult, traits::AtLeast32BitUnsigned} 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 crate::weights::WeightInfo; +use super::Config; + +pub enum BeginDecidingBranch { + Passing, + Failing, +} + +pub enum ServiceBranch { + Fail, + NoDeposit, + Preparing, + Queued, + NotQueued, + RequeuedInsertion, + RequeuedSlide, + BeginDecidingPassing, + BeginDecidingFailing, + BeginConfirming, + ContinueConfirming, + EndConfirming, + ContinueNotConfirming, + Approved, + Rejected, + TimedOut, +} + +impl From for ServiceBranch { + fn from(x: BeginDecidingBranch) -> Self { + use {BeginDecidingBranch::*, ServiceBranch::*}; + match x { + Passing => BeginDecidingPassing, + Failing => BeginDecidingFailing, + } + } +} + +impl ServiceBranch { + pub fn weight_of_nudge(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(), + } + } + + pub fn max_weight_of_nudge() -> 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()) + } + + pub fn weight_of_deposit(self) -> Option { + 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, + }) + } + + pub fn max_weight_of_deposit() -> 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()) + } +} + +pub enum OneFewerDecidingBranch { + QueueEmpty, + BeginDecidingPassing, + BeginDecidingFailing, +} + +impl From for OneFewerDecidingBranch { + fn from(x: BeginDecidingBranch) -> Self { + use {BeginDecidingBranch::*, OneFewerDecidingBranch::*}; + match x { + Passing => BeginDecidingPassing, + Failing => BeginDecidingFailing, + } + } +} + +impl OneFewerDecidingBranch { + pub fn weight(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(), + } + } + + pub fn max_weight() -> 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()) + } +} From 5e5576477b3727ad3311d8e08b1df5b39b0a9e74 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 19 Dec 2021 19:00:14 +0000 Subject: [PATCH 37/66] No GenesisConfig for Referenda --- bin/node/cli/src/chain_spec.rs | 1 - frame/referenda/src/lib.rs | 19 ------------------- frame/referenda/src/mock.rs | 10 ++-------- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index f8cc9abda6990..b29248519cc08 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -363,7 +363,6 @@ pub fn testnet_genesis( transaction_storage: Default::default(), scheduler: Default::default(), transaction_payment: Default::default(), - referenda: Default::default(), } } diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 92bdcac13f87a..a9b590174f962 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -162,25 +162,6 @@ pub mod pallet { #[pallet::storage] pub type DecidingCount = StorageMap<_, Twox64Concat, TrackIdOf, u32, ValueQuery>; - #[pallet::genesis_config] - pub struct GenesisConfig { - _phantom: sp_std::marker::PhantomData, - } - - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { _phantom: Default::default() } - } - } - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - ReferendumCount::::put(0 as ReferendumIndex); - } - } - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 0aecd868727e4..6da91bf149b8d 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -237,14 +237,8 @@ impl Config for Test { pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)], - } - .assimilate_storage(&mut t) - .unwrap(); - pallet_referenda::GenesisConfig::::default() - .assimilate_storage(&mut t) - .unwrap(); + let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]; + pallet_balances::GenesisConfig:: { balances }.assimilate_storage(&mut t).unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext From 8315533f9f0927aebae38ee7a3537af98684fe7b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 19 Dec 2021 19:00:20 +0000 Subject: [PATCH 38/66] Formatting --- bin/node/runtime/src/lib.rs | 40 ++++++++--------- frame/referenda/src/benchmarking.rs | 8 ++-- frame/referenda/src/branch.rs | 31 +++++++------ frame/referenda/src/lib.rs | 67 ++++++++++++++++------------- frame/referenda/src/mock.rs | 4 +- frame/referenda/src/types.rs | 21 ++++----- 6 files changed, 95 insertions(+), 76 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 605e1ca35e6dd..2664d2269ac13 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -704,28 +704,26 @@ impl pallet_referenda::TracksInfo for TracksInfo { type Id = u8; type Origin = ::PalletsOrigin; fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] { - static DATA: [(u8, pallet_referenda::TrackInfo); 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), - }, + static DATA: [(u8, pallet_referenda::TrackInfo); 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 { diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index ebe0863aea24c..cfa5cc7705fb4 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -67,9 +67,11 @@ fn nudge(index: ReferendumIndex) { assert_ok!(Referenda::::nudge_referendum(RawOrigin::Root.into(), index)); } -fn fill_queue(index: ReferendumIndex, spaces: u32, pass_after: u32) - -> Vec -{ +fn fill_queue( + index: ReferendumIndex, + spaces: u32, + pass_after: u32, +) -> Vec { // First, create enough other referendums to fill the track. let mut others = vec![]; for _ in 0..info::(index).max_deciding { diff --git a/frame/referenda/src/branch.rs b/frame/referenda/src/branch.rs index 69c54415a9eae..dd42ef3479215 100644 --- a/frame/referenda/src/branch.rs +++ b/frame/referenda/src/branch.rs @@ -15,8 +15,8 @@ //! Helpers for managing the different weights in various algorithmic branches. -use crate::weights::WeightInfo; use super::Config; +use crate::weights::WeightInfo; pub enum BeginDecidingBranch { Passing, @@ -44,7 +44,8 @@ pub enum ServiceBranch { impl From for ServiceBranch { fn from(x: BeginDecidingBranch) -> Self { - use {BeginDecidingBranch::*, ServiceBranch::*}; + use BeginDecidingBranch::*; + use ServiceBranch::*; match x { Passing => BeginDecidingPassing, Failing => BeginDecidingFailing, @@ -75,8 +76,7 @@ impl ServiceBranch { } pub fn max_weight_of_nudge() -> frame_support::weights::Weight { - 0 - .max(T::WeightInfo::nudge_referendum_no_deposit()) + 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()) @@ -101,15 +101,22 @@ impl ServiceBranch { 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, + BeginConfirming | + ContinueConfirming | + EndConfirming | + ContinueNotConfirming | + Approved | + Rejected | + RequeuedInsertion | + RequeuedSlide | + TimedOut | + Fail | + NoDeposit => return None, }) } pub fn max_weight_of_deposit() -> frame_support::weights::Weight { - 0 - .max(T::WeightInfo::place_decision_deposit_preparing()) + 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()) @@ -125,7 +132,8 @@ pub enum OneFewerDecidingBranch { impl From for OneFewerDecidingBranch { fn from(x: BeginDecidingBranch) -> Self { - use {BeginDecidingBranch::*, OneFewerDecidingBranch::*}; + use BeginDecidingBranch::*; + use OneFewerDecidingBranch::*; match x { Passing => BeginDecidingPassing, Failing => BeginDecidingFailing, @@ -144,8 +152,7 @@ impl OneFewerDecidingBranch { } pub fn max_weight() -> frame_support::weights::Weight { - 0 - .max(T::WeightInfo::one_fewer_deciding_queue_empty()) + 0.max(T::WeightInfo::one_fewer_deciding_queue_empty()) .max(T::WeightInfo::one_fewer_deciding_passing()) .max(T::WeightInfo::one_fewer_deciding_failing()) } diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index a9b590174f962..cbbce5d138ebb 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -48,6 +48,7 @@ use sp_std::{fmt::Debug, prelude::*}; mod branch; mod types; pub mod weights; +use branch::{BeginDecidingBranch, OneFewerDecidingBranch, ServiceBranch}; pub use pallet::*; pub use types::{ AtOrAfter, BalanceOf, CallOf, Curve, DecidingStatus, DecidingStatusOf, Deposit, InsertSorted, @@ -56,7 +57,6 @@ pub use types::{ TrackInfoOf, TracksInfo, VotesOf, }; pub use weights::WeightInfo; -use branch::{ServiceBranch, BeginDecidingBranch, OneFewerDecidingBranch}; #[cfg(test)] mod mock; @@ -380,7 +380,10 @@ pub mod pallet { /// Advance a referendum onto its next logical state. Only used internally. #[pallet::weight(ServiceBranch::max_weight_of_nudge::())] - pub fn nudge_referendum(origin: OriginFor, index: ReferendumIndex) -> DispatchResultWithPostInfo { + pub fn nudge_referendum( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResultWithPostInfo { ensure_root(origin)?; let now = frame_system::Pallet::::block_number(); let mut status = Self::ensure_ongoing(index)?; @@ -407,20 +410,21 @@ pub mod pallet { ensure_root(origin)?; let track_info = T::Tracks::info(track).ok_or(Error::::BadTrack)?; let mut track_queue = TrackQueue::::get(track); - let branch = if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { - let now = frame_system::Pallet::::block_number(); - let (maybe_alarm, branch) - = Self::begin_deciding(&mut status, index, now, track_info); - if let Some(set_alarm) = maybe_alarm { - Self::ensure_alarm_at(&mut status, index, set_alarm); - } - ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); - TrackQueue::::insert(track, track_queue); - branch.into() - } else { - DecidingCount::::mutate(track, |x| x.saturating_dec()); - OneFewerDecidingBranch::QueueEmpty - }; + let branch = + if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { + let now = frame_system::Pallet::::block_number(); + let (maybe_alarm, branch) = + Self::begin_deciding(&mut status, index, now, track_info); + if let Some(set_alarm) = maybe_alarm { + Self::ensure_alarm_at(&mut status, index, set_alarm); + } + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + TrackQueue::::insert(track, track_queue); + branch.into() + } else { + DecidingCount::::mutate(track, |x| x.saturating_dec()); + OneFewerDecidingBranch::QueueEmpty + }; Ok(Some(branch.weight::()).into()) } } @@ -568,11 +572,8 @@ impl Pallet { let deciding_status = DecidingStatus { since: now, confirming }; let alarm = Self::decision_time(&deciding_status, &status.tally, track); status.deciding = Some(deciding_status); - let branch = if is_passing { - BeginDecidingBranch::Passing - } else { - BeginDecidingBranch::Failing - }; + let branch = + if is_passing { BeginDecidingBranch::Passing } else { BeginDecidingBranch::Failing }; (Some(alarm), branch) } @@ -723,7 +724,8 @@ impl Pallet { branch = if status.decision_deposit.is_some() { let prepare_end = status.submitted.saturating_add(track.prepare_period); if now >= prepare_end { - let (maybe_alarm, branch) = Self::ready_for_deciding(now, &track, index, &mut status); + let (maybe_alarm, branch) = + Self::ready_for_deciding(now, &track, index, &mut status); if let Some(set_alarm) = maybe_alarm { alarm = alarm.min(set_alarm); } @@ -768,8 +770,17 @@ impl Pallet { Self::ensure_no_alarm(&mut status); Self::note_one_fewer_deciding(status.track, track); let (desired, call_hash) = (status.enactment, status.proposal_hash); - Self::schedule_enactment(index, track, desired, status.origin, call_hash); - Self::deposit_event(Event::::Confirmed { index, tally: status.tally }); + Self::schedule_enactment( + index, + track, + desired, + status.origin, + call_hash, + ); + Self::deposit_event(Event::::Confirmed { + index, + tally: status.tally, + }); return ( ReferendumInfo::Approved( now, @@ -779,17 +790,15 @@ impl Pallet { true, ServiceBranch::Approved, ) - } - Some(_) => { - ServiceBranch::ContinueConfirming - } + }, + Some(_) => ServiceBranch::ContinueConfirming, None => { // Start confirming dirty = true; deciding.confirming = Some(now.saturating_add(track.confirm_period)); Self::deposit_event(Event::::ConfirmStarted { index }); ServiceBranch::BeginConfirming - } + }, } } else { if now >= deciding.since.saturating_add(track.decision_period) { diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 6da91bf149b8d..da385804d6e6f 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -238,7 +238,9 @@ impl Config for Test { pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]; - pallet_balances::GenesisConfig:: { balances }.assimilate_storage(&mut t).unwrap(); + pallet_balances::GenesisConfig:: { balances } + .assimilate_storage(&mut t) + .unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 13cce36334ab1..8ead396e2de68 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -18,11 +18,11 @@ //! Miscellaneous additional datatypes. use super::*; -use sp_std::fmt::Debug; use codec::{Decode, Encode, EncodeLike}; use frame_support::{traits::schedule::Anon, Parameter}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; +use sp_std::fmt::Debug; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -261,15 +261,16 @@ pub enum ReferendumInfo< } 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: 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 +{ pub fn take_decision_deposit(&mut self) -> Result>, ()> { use ReferendumInfo::*; match self { From 54210df4e5634e72e596b96414fee988e0e8e9f6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 19 Dec 2021 19:11:34 +0000 Subject: [PATCH 39/66] Docs --- frame/referenda/src/lib.rs | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index cbbce5d138ebb..442834ac6f015 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -265,9 +265,8 @@ pub mod pallet { impl Pallet { /// Propose a referendum on a privileged action. /// - /// The dispatch origin of this call must be _Signed_ and the sender must - /// have funds to cover the deposit. - /// + /// - `origin`: must be `Signed` and the account must have `SubmissionDeposit` funds + /// available. /// - `proposal_origin`: The origin from which the proposal should be executed. /// - `proposal_hash`: The hash of the proposal preimage. /// - `enactment_moment`: The moment that the proposal should be enacted. @@ -310,6 +309,14 @@ pub mod pallet { Ok(()) } + /// Post the Decision Deposit for a referendum. + /// + /// - `origin`: must be `Signed` and the account must have funds available for the + /// referendum's track's Decision Deposit. + /// - `index`: The index of the submitted referendum whose Decision Deposit is yet to be + /// posted. + /// + /// TODO: Emits `DecisionDepositPlaced`. #[pallet::weight(ServiceBranch::max_weight_of_deposit::())] pub fn place_decision_deposit( origin: OriginFor, @@ -326,6 +333,13 @@ pub mod pallet { Ok(branch.weight_of_deposit::().into()) } + /// Refund the Decision Deposit for a closed referendum back to the depositor. + /// + /// - `origin`: must be `Signed` or `Root`. + /// - `index`: The index of a closed referendum whose Decision Deposit has not yet been + /// refunded. + /// + /// TODO: Emits `DecisionDepositRefunded`. #[pallet::weight(T::WeightInfo::refund_decision_deposit())] pub fn refund_decision_deposit( origin: OriginFor, @@ -342,6 +356,12 @@ pub mod pallet { Ok(()) } + /// Cancel an ongoing referendum. + /// + /// - `origin`: must be the `CancelOrigin`. + /// - `index`: The index of the referendum to be cancelled. + /// + /// Emits `Cancelled`. #[pallet::weight(T::WeightInfo::cancel())] pub fn cancel(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::CancelOrigin::ensure_origin(origin)?; @@ -361,6 +381,12 @@ pub mod pallet { Ok(()) } + /// Cancel an ongoing referendum and slash the deposits. + /// + /// - `origin`: must be the `KillOrigin`. + /// - `index`: The index of the referendum to be cancelled. + /// + /// Emits `Killed`. #[pallet::weight(T::WeightInfo::kill())] pub fn kill(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::KillOrigin::ensure_origin(origin)?; @@ -379,6 +405,9 @@ pub mod pallet { } /// Advance a referendum onto its next logical state. Only used internally. + /// + /// - `origin`: must be `Root`. + /// - `index`: the referendum to be advanced. #[pallet::weight(ServiceBranch::max_weight_of_nudge::())] pub fn nudge_referendum( origin: OriginFor, @@ -398,6 +427,9 @@ pub mod pallet { /// Advance a track onto its next logical state. Only used internally. /// + /// - `origin`: must be `Root`. + /// - `track`: the track to be advanced. + /// /// Action item for when there is now one fewer referendum in the deciding phase and the /// `DecidingCount` is not yet updated. This means that we should either: /// - begin deciding another referendum (and leave `DecidingCount` alone); or From 633ca159cc0a58cb792b3f62ad42614ca9b278fa Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 20 Dec 2021 17:12:47 +0000 Subject: [PATCH 40/66] Docs --- frame/referenda/src/branch.rs | 13 +++++++- frame/referenda/src/lib.rs | 59 ++++++++++++++++++++++++++++++----- frame/referenda/src/mock.rs | 2 +- frame/referenda/src/types.rs | 29 +++++++++++++++++ 4 files changed, 93 insertions(+), 10 deletions(-) diff --git a/frame/referenda/src/branch.rs b/frame/referenda/src/branch.rs index dd42ef3479215..9f1f9598a1052 100644 --- a/frame/referenda/src/branch.rs +++ b/frame/referenda/src/branch.rs @@ -18,11 +18,13 @@ 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, @@ -54,7 +56,8 @@ impl From for ServiceBranch { } impl ServiceBranch { - pub fn weight_of_nudge(self) -> frame_support::weights::Weight { + /// Return the weight of the `nudge` function when it takes the branch denoted by `self`. + pub fn weight_of_nudge(self) -> frame_support::weights::Weight { use ServiceBranch::*; match self { NoDeposit => T::WeightInfo::nudge_referendum_no_deposit(), @@ -75,6 +78,7 @@ impl ServiceBranch { } } + /// Return the maximum possible weight of the `nudge` function. pub fn max_weight_of_nudge() -> frame_support::weights::Weight { 0.max(T::WeightInfo::nudge_referendum_no_deposit()) .max(T::WeightInfo::nudge_referendum_preparing()) @@ -93,6 +97,8 @@ impl ServiceBranch { .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(self) -> Option { use ServiceBranch::*; Some(match self { @@ -115,6 +121,7 @@ impl ServiceBranch { }) } + /// Return the maximum possible weight of the `place_decision_deposit` function. pub fn max_weight_of_deposit() -> frame_support::weights::Weight { 0.max(T::WeightInfo::place_decision_deposit_preparing()) .max(T::WeightInfo::place_decision_deposit_queued()) @@ -124,6 +131,7 @@ impl ServiceBranch { } } +/// Branches that the `one_fewer_deciding` function may take. pub enum OneFewerDecidingBranch { QueueEmpty, BeginDecidingPassing, @@ -142,6 +150,8 @@ impl From for OneFewerDecidingBranch { } impl OneFewerDecidingBranch { + /// Return the weight of the `one_fewer_deciding` function when it takes the branch denoted + /// by `self`. pub fn weight(self) -> frame_support::weights::Weight { use OneFewerDecidingBranch::*; match self { @@ -151,6 +161,7 @@ impl OneFewerDecidingBranch { } } + /// Return the maximum possible weight of the `one_fewer_deciding` function. pub fn max_weight() -> frame_support::weights::Weight { 0.max(T::WeightInfo::one_fewer_deciding_queue_empty()) .max(T::WeightInfo::one_fewer_deciding_passing()) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 442834ac6f015..e01d074762fbf 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -174,6 +174,31 @@ pub mod pallet { /// The hash of the proposal up for referendum. proposal_hash: T::Hash, }, + /// The decision deposit has been placed. + DecisionDepositPlaced { + /// Index of the referendum. + index: ReferendumIndex, + /// The account who placed the deposit. + who: T::AccountId, + /// The amount placed by the account. + amount: BalanceOf, + }, + /// The decision deposit has been refunded. + DecisionDepositRefunded { + /// Index of the referendum. + index: ReferendumIndex, + /// The account who placed the deposit. + who: T::AccountId, + /// The amount placed by the account. + amount: BalanceOf, + }, + /// A deposit has been slashaed. + DepositSlashed { + /// The account who placed the deposit. + who: T::AccountId, + /// The amount placed by the account. + amount: BalanceOf, + }, /// A referendum has moved into the deciding phase. DecisionStarted { /// Index of the referendum. @@ -316,7 +341,7 @@ pub mod pallet { /// - `index`: The index of the submitted referendum whose Decision Deposit is yet to be /// posted. /// - /// TODO: Emits `DecisionDepositPlaced`. + /// Emits `DecisionDepositPlaced`. #[pallet::weight(ServiceBranch::max_weight_of_deposit::())] pub fn place_decision_deposit( origin: OriginFor, @@ -326,10 +351,12 @@ pub mod pallet { let mut status = Self::ensure_ongoing(index)?; ensure!(status.decision_deposit.is_none(), Error::::HaveDeposit); let track = Self::track(status.track).ok_or(Error::::NoTrack)?; - status.decision_deposit = Some(Self::take_deposit(who, track.decision_deposit)?); + status.decision_deposit = Some(Self::take_deposit(who.clone(), track.decision_deposit)?); let now = frame_system::Pallet::::block_number(); let (info, _, branch) = Self::service_referendum(now, index, status); ReferendumInfoFor::::insert(index, info); + let e = Event::::DecisionDepositPlaced { index, who, amount: track.decision_deposit }; + Self::deposit_event(e); Ok(branch.weight_of_deposit::().into()) } @@ -339,7 +366,7 @@ pub mod pallet { /// - `index`: The index of a closed referendum whose Decision Deposit has not yet been /// refunded. /// - /// TODO: Emits `DecisionDepositRefunded`. + /// Emits `DecisionDepositRefunded`. #[pallet::weight(T::WeightInfo::refund_decision_deposit())] pub fn refund_decision_deposit( origin: OriginFor, @@ -351,8 +378,14 @@ pub mod pallet { .take_decision_deposit() .map_err(|_| Error::::Unfinished)? .ok_or(Error::::NoDeposit)?; - Self::refund_deposit(Some(deposit)); + Self::refund_deposit(Some(deposit.clone())); ReferendumInfoFor::::insert(index, info); + let e = Event::::DecisionDepositRefunded { + index, + who: deposit.who, + amount: deposit.amount, + }; + Self::deposit_event(e); Ok(()) } @@ -386,7 +419,7 @@ pub mod pallet { /// - `origin`: must be the `KillOrigin`. /// - `index`: The index of the referendum to be cancelled. /// - /// Emits `Killed`. + /// Emits `Killed` and `DepositSlashed`. #[pallet::weight(T::WeightInfo::kill())] pub fn kill(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::KillOrigin::ensure_origin(origin)?; @@ -397,8 +430,8 @@ pub mod pallet { } Self::note_one_fewer_deciding(status.track, track); Self::deposit_event(Event::::Killed { index, tally: status.tally }); - Self::slash_deposit(Some(status.submission_deposit)); - Self::slash_deposit(status.decision_deposit); + Self::slash_deposit(Some(status.submission_deposit.clone())); + Self::slash_deposit(status.decision_deposit.clone()); let info = ReferendumInfo::Killed(frame_system::Pallet::::block_number()); ReferendumInfoFor::::insert(index, info); Ok(()) @@ -514,6 +547,8 @@ impl Polls for Pallet { } impl Pallet { + /// Check that referendum `index` is in the `Ongoing` state and return the `ReferendumStatus` + /// value, or `Err` otherwise. pub fn ensure_ongoing(index: ReferendumIndex) -> Result, DispatchError> { match ReferendumInfoFor::::get(index) { Some(ReferendumInfo::Ongoing(status)) => Ok(status), @@ -544,7 +579,7 @@ impl Pallet { debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed"); } - /// Set an alarm. + /// Set an alarm to dispatch `call` at block number `when`. fn set_alarm( call: impl Into>, when: T::BlockNumber, @@ -866,6 +901,8 @@ impl Pallet { (ReferendumInfo::Ongoing(status), dirty_alarm || dirty, branch) } + /// Determine the point at which a referendum will be accepted, move into confirmation with the + /// given `tally` or end with rejection (whichever happens sooner). fn decision_time( deciding: &DecidingStatusOf, tally: &T::Tally, @@ -882,6 +919,7 @@ impl Pallet { }) } + /// Cancel the alarm in `status`, if one exists. fn ensure_no_alarm(status: &mut ReferendumStatusOf) { if let Some((_, last_alarm)) = status.alarm.take() { // Incorrect alarm - cancel it. @@ -909,15 +947,20 @@ impl Pallet { fn slash_deposit(deposit: Option>>) { if let Some(Deposit { who, amount }) = deposit { T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); + Self::deposit_event(Event::::DepositSlashed { who, amount }); } } + /// Get the track info value for the track `id`. fn track(id: TrackIdOf) -> Option<&'static TrackInfoOf> { let tracks = T::Tracks::tracks(); let index = tracks.binary_search_by_key(&id, |x| x.0).unwrap_or_else(|x| x); Some(&tracks[index].1) } + /// Determine whether the given `tally` would result in a referendum passing at `elapsed` blocks + /// into a total decision `period`, given the two curves for `turnout_needed` and + /// `approval_needed`. fn is_passing( tally: &T::Tally, elapsed: T::BlockNumber, diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index da385804d6e6f..711189b6cbc92 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -23,7 +23,7 @@ use codec::{Decode, Encode}; use frame_support::{ assert_ok, ord_parameter_types, parameter_types, traits::{ - ConstU32, ConstU64, Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, OriginTrait, + ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, PreimageRecipient, SortedMembers, }, weights::Weight, diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 8ead396e2de68..3fb32a4b5825e 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -165,11 +165,21 @@ pub struct TrackInfo { pub min_turnout: Curve, } +/// Information on the voting tracks. pub trait TracksInfo { + /// 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)]; + + /// Determine the voting track for the given `origin`. fn track_for(origin: &Self::Origin) -> Result; + + /// 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> { Self::tracks().iter().find(|x| &x.0 == &id).map(|x| &x.1) } @@ -271,6 +281,8 @@ impl< ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, > ReferendumInfo { + /// 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>, ()> { use ReferendumInfo::*; match self { @@ -284,6 +296,8 @@ impl< } } +/// 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)] #[cfg_attr(not(feature = "std"), derive(RuntimeDebug))] pub enum Curve { @@ -292,17 +306,32 @@ pub enum Curve { } 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, } } + + /// 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 } => (*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) } From 72fbcff64345cd2bfe58fd24ea7f6a52d422099d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 20 Dec 2021 17:39:13 +0000 Subject: [PATCH 41/66] Docs --- frame/referenda/src/lib.rs | 41 ++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index e01d074762fbf..ff1943ccdc69b 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -15,12 +15,45 @@ //! # Referenda Pallet //! -//! - [`Config`] -//! - [`Call`] -//! //! ## Overview //! -//! The Referenda pallet handles the administration of general stakeholder voting. +//! A pallet for executing referenda. No voting logic is present here, and the `Polls` and +//! `PollStatus` traits are used to allow the voting logic (likely in a pallet) to be utilized. +//! +//! A referendum is a vote on whether a proposal should be dispatched from a particular origin. The +//! origin is used to determine which one of several _tracks_ that a referendum happens under. +//! Tracks each have their own configuration which governs the voting process and parameters. +//! +//! A referendum's lifecycle has three main stages: Preparation, deciding and conclusion. +//! Referenda are considered "ongoing" immediately after submission until their eventual +//! conclusion, and votes may be cast throughout. +//! +//! In order to progress from preparating to being decided, three things must be in place: +//! - There must have been a *Decision Deposit* placed, an amount determined by the track. Anyone +//! may place this deposit. +//! - A period must have elapsed since submission of the referendum. This period is known as the +//! *Preparation Period* and is determined by the track. +//! - The track must not already be at capacity with referendum being decided. The maximum number of +//! referenda which may be being decided simultaneously is determined by the track. +//! +//! In order to become concluded, one of three things must happen: +//! - The referendum should remain in an unbroken _Passing_ state for a period of time. This +//! is known as the _Confirmation Period_ and is determined by the track. A referendum is considered +//! _Passing_ when there is a sufficiently high turnout and approval, given the amount of time it +//! has been being decided. Generally the threshold for what counts as being "sufficiently high" +//! will reduce over time. The curves setting these thresholds are determined by the track. In this +//! case, the referendum is considered _Approved_ and the proposal is scheduled for dispatch. +//! - The referendum reaches the end of its deciding phase outside not _Passing_. It ends in +//! rejection and the proposal is not dispatched. +//! - The referendum is cancelled. +//! +//! A general time-out is also in place and referenda which exist in preparation for too long may +//! conclude without ever entering into a deciding stage. +//! +//! Once a referendum is concluded, the decision deposit may be refunded. +//! +//! - [`Config`] +//! - [`Call`] #![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] From b2c9c7963048b111ab752b89e11c31e14e160deb Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 4 Jan 2022 15:58:00 +0100 Subject: [PATCH 42/66] Per-class conviction voting --- frame/conviction-voting/src/lib.rs | 186 +++++++++++++++------------ frame/conviction-voting/src/tests.rs | 91 ++++++++++--- frame/conviction-voting/src/types.rs | 19 ++- frame/conviction-voting/src/vote.rs | 76 ++++++----- frame/democracy/src/lib.rs | 8 -- frame/referenda/src/lib.rs | 32 +++-- frame/referenda/src/mock.rs | 2 +- frame/support/src/traits/voting.rs | 24 ++-- 8 files changed, 277 insertions(+), 161 deletions(-) diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 960963fb99106..3817ef468b4d2 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -44,8 +44,8 @@ mod vote; pub mod weights; pub use conviction::Conviction; pub use pallet::*; -pub use types::{Delegations, Tally, UnvoteScope}; -pub use vote::{AccountVote, Vote, Voting}; +pub use types::{Delegations, Tally, UnvoteScope,}; +pub use vote::{AccountVote, Vote, Voting, Casting, Delegating}; pub use weights::WeightInfo; #[cfg(test)] @@ -64,8 +64,15 @@ type VotingOf = Voting< ::BlockNumber, ReferendumIndexOf, >; +#[allow(dead_code)] +type DelegatingOf = Delegating< + BalanceOf, + ::AccountId, + ::BlockNumber, +>; type TallyOf = Tally, ::MaxTurnout>; type ReferendumIndexOf = <::Referenda as Polls>>::Index; +type ClassOf = <::Referenda as Polls>>::Class; #[frame_support::pallet] pub mod pallet { @@ -111,21 +118,28 @@ pub mod pallet { type VoteLockingPeriod: Get; } - /// All votes for a particular voter. We store the balance for the number of votes that we - /// have recorded. The second item is the total amount of delegations, that will be added. - /// - /// TWOX-NOTE: SAFE as `AccountId`s are crypto hashes anyway. + /// 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 = - StorageMap<_, Twox64Concat, T::AccountId, VotingOf, 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. + pub type VotingFor = StorageDoubleMap<_, + Twox64Concat, + T::AccountId, + Twox64Concat, + ClassOf, + VotingOf, + 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] - #[pallet::getter(fn locks)] - pub type Locks = StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber>; + pub type ClassLocksFor = StorageMap<_, + Twox64Concat, + T::AccountId, + Vec<(ClassOf, BalanceOf)>, + ValueQuery, + >; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -138,20 +152,8 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// Value too low - ValueLow, /// Referendum is not ongoing. NotOngoing, - /// Referendum's decision deposit is already paid. - HaveDeposit, - /// Proposal does not exist - ProposalMissing, - /// Cannot cancel the same proposal twice - AlreadyCanceled, - /// Proposal already made - DuplicateProposal, - /// Vote given for invalid referendum - ReferendumInvalid, /// The given account did not vote on the referendum. NotVoter, /// The actor has no permission to conduct the action. @@ -167,10 +169,10 @@ pub mod pallet { VotesExist, /// Delegation to oneself makes no sense. Nonsense, - /// Invalid upper bound. - WrongUpperBound, /// Maximum number of votes reached. MaxVotesReached, + /// The class must be supplied since it is not easily determinable from the state. + ClassNeeded, } #[pallet::call] @@ -222,12 +224,13 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] pub fn delegate( origin: OriginFor, + class: ClassOf, to: T::AccountId, conviction: Conviction, balance: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let votes = Self::try_delegate(who, to, conviction, balance)?; + let votes = Self::try_delegate(who, class, to, conviction, balance)?; Ok(Some(T::WeightInfo::delegate(votes)).into()) } @@ -247,9 +250,9 @@ pub mod pallet { // 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) -> DispatchResultWithPostInfo { + pub fn undelegate(origin: OriginFor, class: ClassOf) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let votes = Self::try_undelegate(who)?; + let votes = Self::try_undelegate(who, class)?; Ok(Some(T::WeightInfo::undelegate(votes)).into()) } @@ -264,9 +267,9 @@ pub mod pallet { T::WeightInfo::unlock_set(T::MaxVotes::get()) .max(T::WeightInfo::unlock_remove(T::MaxVotes::get())) )] - pub fn unlock(origin: OriginFor, target: T::AccountId) -> DispatchResult { + pub fn unlock(origin: OriginFor, class: ClassOf, target: T::AccountId) -> DispatchResult { ensure_signed(origin)?; - Self::update_lock(&target); + Self::update_lock(&class, &target); Ok(()) } @@ -298,9 +301,13 @@ pub mod pallet { /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. /// Weight is calculated for the maximum number of vote. #[pallet::weight(T::WeightInfo::remove_vote(T::MaxVotes::get()))] - pub fn remove_vote(origin: OriginFor, index: ReferendumIndexOf) -> DispatchResult { + pub fn remove_vote( + origin: OriginFor, + class: Option>, + index: ReferendumIndexOf, + ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::try_remove_vote(&who, index, UnvoteScope::Any) + Self::try_remove_vote(&who, index, class, UnvoteScope::Any) } /// Remove a vote for a referendum. @@ -322,11 +329,12 @@ pub mod pallet { pub fn remove_other_vote( origin: OriginFor, target: T::AccountId, + class: ClassOf, index: ReferendumIndexOf, ) -> DispatchResult { let who = ensure_signed(origin)?; let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; - Self::try_remove_vote(&target, index, scope)?; + Self::try_remove_vote(&target, index, Some(class), scope)?; Ok(()) } } @@ -341,9 +349,9 @@ impl Pallet { ) -> DispatchResult { ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); T::Referenda::try_access_poll(ref_index, |poll_status| { - let tally = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; - VotingFor::::try_mutate(who, |voting| { - if let Voting::Direct { ref mut votes, delegations, .. } = voting { + let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; + VotingFor::::try_mutate(who, &class, |voting| { + if let Voting::Casting(Casting { ref mut votes, delegations, .. }) = voting { match votes.binary_search_by_key(&ref_index, |i| i.0) { Ok(i) => { // Shouldn't be possible to fail, but we handle it gracefully. @@ -371,12 +379,7 @@ impl Pallet { } // Extend the lock to `balance` (rather than setting it) since we don't know what // other votes are in place. - T::Currency::extend_lock( - CONVICTION_VOTING_ID, - who, - vote.balance(), - WithdrawReasons::TRANSFER, - ); + Self::extend_lock(who, &class, vote.balance()); Ok(()) }) }) @@ -391,17 +394,21 @@ impl Pallet { fn try_remove_vote( who: &T::AccountId, ref_index: ReferendumIndexOf, + class_hint: Option>, scope: UnvoteScope, ) -> DispatchResult { - VotingFor::::try_mutate(who, |voting| { - if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting { + let class = class_hint + .or_else(|| Some(T::Referenda::as_ongoing(ref_index)?.1)) + .ok_or(Error::::ClassNeeded)?; + VotingFor::::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(&ref_index, |i| i.0) .map_err(|_| Error::::NotVoter)?; let v = votes.remove(i); T::Referenda::try_access_poll(ref_index, |poll_status| match poll_status { - PollStatus::Ongoing(tally) => { + PollStatus::Ongoing(tally, _) => { ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); // Shouldn't be possible to fail, but we handle it gracefully. tally.remove(v.1).ok_or(ArithmeticError::Underflow)?; @@ -433,19 +440,19 @@ impl Pallet { } /// Return the number of votes for `who` - fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { - VotingFor::::mutate(who, |voting| match voting { - Voting::Delegating { delegations, .. } => { + fn increase_upstream_delegation(who: &T::AccountId, class: &ClassOf, amount: Delegations>) -> u32 { + VotingFor::::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::Direct { votes, delegations, .. } => { + Voting::Casting(Casting { votes, delegations, .. }) => { *delegations = delegations.saturating_add(amount); for &(ref_index, account_vote) in votes.iter() { if let AccountVote::Standard { vote, .. } = account_vote { T::Referenda::access_poll(ref_index, |poll_status| { - if let PollStatus::Ongoing(tally) = poll_status { + if let PollStatus::Ongoing(tally, _) = poll_status { tally.increase(vote.aye, amount); } }); @@ -457,19 +464,19 @@ impl Pallet { } /// Return the number of votes for `who` - fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations>) -> u32 { - VotingFor::::mutate(who, |voting| match voting { - Voting::Delegating { delegations, .. } => { + fn reduce_upstream_delegation(who: &T::AccountId, class: &ClassOf, amount: Delegations>) -> u32 { + VotingFor::::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::Direct { votes, delegations, .. } => { + Voting::Casting(Casting { votes, delegations, .. }) => { *delegations = delegations.saturating_sub(amount); for &(ref_index, account_vote) in votes.iter() { if let AccountVote::Standard { vote, .. } = account_vote { T::Referenda::access_poll(ref_index, |poll_status| { - if let PollStatus::Ongoing(tally) = poll_status { + if let PollStatus::Ongoing(tally, _) = poll_status { tally.reduce(vote.aye, amount); } }); @@ -485,42 +492,38 @@ impl Pallet { /// Return the upstream number of votes. fn try_delegate( who: T::AccountId, + class: ClassOf, target: T::AccountId, conviction: Conviction, balance: BalanceOf, ) -> Result { ensure!(who != target, Error::::Nonsense); ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); - let votes = VotingFor::::try_mutate(&who, |voting| -> Result { - let mut old = Voting::Delegating { + let votes = VotingFor::::try_mutate(&who, &class, |voting| -> Result { + let mut old = Voting::Delegating(Delegating { balance, target: target.clone(), conviction, delegations: Default::default(), prior: Default::default(), - }; + }); sp_std::mem::swap(&mut old, voting); match old { - Voting::Delegating { balance, target, conviction, delegations, prior, .. } => { + Voting::Delegating(Delegating { balance, target, conviction, delegations, prior, .. }) => { // remove any delegation votes to our current target. - Self::reduce_upstream_delegation(&target, conviction.votes(balance)); + Self::reduce_upstream_delegation(&target, &class, conviction.votes(balance)); voting.set_common(delegations, prior); }, - Voting::Direct { votes, delegations, prior } => { + Voting::Casting(Casting { votes, delegations, prior }) => { // here we just ensure that we're currently idling with no votes recorded. ensure!(votes.is_empty(), Error::::VotesExist); voting.set_common(delegations, prior); }, } - let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance)); + 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. - T::Currency::extend_lock( - CONVICTION_VOTING_ID, - &who, - balance, - WithdrawReasons::TRANSFER, - ); + Self::extend_lock(&who, &class, balance); Ok(votes) })?; Self::deposit_event(Event::::Delegated(who, target)); @@ -530,15 +533,18 @@ impl Pallet { /// Attempt to end the current delegation. /// /// Return the number of votes of upstream. - fn try_undelegate(who: T::AccountId) -> Result { - let votes = VotingFor::::try_mutate(&who, |voting| -> Result { + fn try_undelegate( + who: T::AccountId, + class: ClassOf, + ) -> Result { + let votes = VotingFor::::try_mutate(&who, &class, |voting| -> Result { let mut old = Voting::default(); sp_std::mem::swap(&mut old, voting); match old { - Voting::Delegating { balance, target, conviction, delegations, mut prior } => { + Voting::Delegating(Delegating { balance, target, conviction, delegations, mut prior }) => { // remove any delegation votes to our current target. let votes = - Self::reduce_upstream_delegation(&target, conviction.votes(balance)); + Self::reduce_upstream_delegation(&target, &class, conviction.votes(balance)); let now = frame_system::Pallet::::block_number(); let lock_periods = conviction.lock_periods().into(); prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); @@ -546,20 +552,42 @@ impl Pallet { Ok(votes) }, - Voting::Direct { .. } => Err(Error::::NotDelegating.into()), + Voting::Casting(_) => Err(Error::::NotDelegating.into()), } })?; Self::deposit_event(Event::::Undelegated(who)); Ok(votes) } + fn extend_lock(who: &T::AccountId, class: &ClassOf, amount: BalanceOf) { + ClassLocksFor::::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(who: &T::AccountId) { - let lock_needed = VotingFor::::mutate(who, |voting| { + fn update_lock(class: &ClassOf, who: &T::AccountId) { + let class_lock_needed = VotingFor::::mutate(who, class, |voting| { voting.rejig(frame_system::Pallet::::block_number()); voting.locked_balance() }); + let lock_needed = ClassLocksFor::::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 { diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index c0b725a18e3fc..98c06139f774c 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -17,6 +17,8 @@ //! The crate's tests. +use std::collections::BTreeMap; + use super::*; use crate as pallet_conviction_voting; use frame_support::{ @@ -162,28 +164,71 @@ impl, A> Get for TotalIssuanceOf { } } +#[derive(Clone)] +pub enum TestPollState { + Ongoing(TallyOf, u8), + Completed(u64, bool), +} +use TestPollState::*; + +parameter_types! { + pub static Polls: BTreeMap = vec![ + (1, Completed(1, true)), + (2, Completed(2, false)), + (3, Ongoing(Tally::from_parts(1, 2, 3), 0)), + ].into_iter().collect(); +} + pub struct TestReferenda; -impl Polls> for TestReferenda { +impl frame_support::traits::Polls> for TestReferenda { type Index = u8; type Votes = u64; type Moment = u64; - fn is_active(_index: u8) -> bool { - false + type Class = u8; + fn classes() -> Vec { vec![0, 1, 2] } + fn as_ongoing(index: u8) -> Option<(TallyOf, Self::Class)> { + Polls::get().remove(&index) + .and_then(|x| if let TestPollState::Ongoing(t, c) = x { Some((t, c)) } else { None }) } fn access_poll( - _index: Self::Index, - f: impl FnOnce(PollStatus<&mut TallyOf, u64>) -> R, + index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, u64, u8>) -> R, ) -> R { - f(PollStatus::None) + 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( - _index: Self::Index, - f: impl FnOnce(PollStatus<&mut TallyOf, u64>) -> Result, + index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, u64, u8>) -> Result, ) -> Result { - f(PollStatus::None) - } - fn tally(_index: Self::Index) -> Option> { - None + 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) } } @@ -250,14 +295,30 @@ fn big_nay(who: u64) -> AccountVote { AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } } -fn tally(_r: u32) -> TallyOf { - todo!() +fn tally(index: u8) -> TallyOf { + >>::as_ongoing(index) + .expect("No tally") + .0 +} + +#[test] +#[ignore] +#[should_panic(expected = "No tally")] +fn unknown_poll_should_panic() { + let _ = tally(0); +} + +#[test] +#[ignore] +#[should_panic(expected = "No tally")] +fn completed_poll_should_panic() { + let _ = tally(1); } #[test] fn basic_stuff() { new_test_ext_execute_with_cond(|_x| { - let _ = tally(0); + assert_eq!(tally(3), Tally::from_parts(1, 2, 3)); let _ = aye(0); let _ = nay(0); let _ = big_aye(0); diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 17565be794c04..94602f61c21ed 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -22,6 +22,7 @@ use std::marker::PhantomData; use super::*; use crate::{AccountVote, Conviction, Vote}; use codec::{Decode, Encode}; +use frame_support::{CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use frame_support::traits::VoteTally; use scale_info::TypeInfo; use sp_runtime::{ @@ -30,8 +31,8 @@ use sp_runtime::{ }; /// Info regarding an ongoing referendum. -#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct Tally { +#[derive(CloneNoBound, DefaultNoBound, PartialEqNoBound, EqNoBound, RuntimeDebugNoBound, TypeInfo, Encode, Decode)] +pub struct Tally { /// 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. @@ -42,7 +43,10 @@ pub struct Tally { dummy: PhantomData, } -impl> VoteTally +impl< + Votes: Clone + Default + PartialEq + Eq + sp_std::fmt::Debug + Copy + AtLeast32BitUnsigned, + Total: Get, +> VoteTally for Tally { fn ayes(&self) -> Votes { @@ -70,7 +74,10 @@ impl> VoteTally } } -impl> Tally { +impl< + Votes: Clone + Default + PartialEq + Eq + sp_std::fmt::Debug + Copy + AtLeast32BitUnsigned, + Total: Get, +> Tally { /// Create a new tally. pub fn new(vote: Vote, balance: Votes) -> Self { let Delegations { votes, capital } = vote.conviction.votes(balance); @@ -82,6 +89,10 @@ impl> Tally } } + 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) -> Option<()> { match vote { diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index 15d97289dd33d..3479a8a61139f 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -130,39 +130,61 @@ impl PriorLock { + /// The amount of balance delegated. + pub balance: Balance, + /// The account to which the voting power is delegated. + pub target: AccountId, + /// Th conviction with which the voting power is delegated. When this gets undelegated, the + /// relevant lock begins. + pub conviction: Conviction, + /// The total amount of delegations that this account has received, post-conviction-weighting. + pub delegations: Delegations, + /// Any pre-existing locks from past voting/delegating activity. + pub prior: PriorLock, +} + +/// Information concerning the direct vote-casting of some voting power. +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Casting { + /// The current votes of the account. + pub votes: Vec<(ReferendumIndex, AccountVote)>, + /// The total amount of delegations that this account has received, post-conviction-weighting. + pub delegations: Delegations, + /// Any pre-existing locks from past voting/delegating activity. + pub prior: PriorLock, +} + /// An indicator for what an account is doing; it can either be delegating or voting. #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] pub enum Voting { - /// The account is voting directly. `delegations` is the total amount of post-conviction voting - /// weight that it controls from those that have delegated to it. - Direct { - /// The current votes of the account. - votes: Vec<(ReferendumIndex, AccountVote)>, - /// The total amount of delegations that this account has received. - delegations: Delegations, - /// Any pre-existing locks from past voting/delegating activity. - prior: PriorLock, - }, + /// The account is voting directly. + Casting(Casting), /// The account is delegating `balance` of its balance to a `target` account with `conviction`. - Delegating { - balance: Balance, - target: AccountId, - conviction: Conviction, - /// The total amount of delegations that this account has received. - delegations: Delegations, - /// Any pre-existing locks from past voting/delegating activity. - prior: PriorLock, - }, + Delegating(Delegating), } impl Default for Voting { fn default() -> Self { - Voting::Direct { + Voting::Casting(Casting { votes: Vec::new(), delegations: Default::default(), prior: PriorLock(Zero::zero(), Default::default()), + }) + } +} + +impl AsMut> + for Voting +{ + fn as_mut(&mut self) -> &mut PriorLock { + match self { + Voting::Casting(Casting { prior, .. }) => prior, + Voting::Delegating(Delegating { prior, .. }) => prior, } } } @@ -175,19 +197,15 @@ impl< > Voting { pub fn rejig(&mut self, now: BlockNumber) { - match self { - Voting::Direct { prior, .. } => prior, - Voting::Delegating { prior, .. } => prior, - } - .rejig(now); + AsMut::>::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::Direct { votes, prior, .. } => + Voting::Casting(Casting { votes, prior, .. }) => votes.iter().map(|i| i.1.balance()).fold(prior.locked(), |a, i| a.max(i)), - Voting::Delegating { balance, .. } => *balance, + Voting::Delegating(Delegating { balance, .. }) => *balance, } } @@ -197,8 +215,8 @@ impl< prior: PriorLock, ) { let (d, p) = match self { - Voting::Direct { ref mut delegations, ref mut prior, .. } => (delegations, prior), - Voting::Delegating { ref mut delegations, ref mut prior, .. } => (delegations, prior), + 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; diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 1e5f3c403006a..04005b6ab254c 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -437,14 +437,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 = 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" diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index ff1943ccdc69b..590ff49e55395 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -534,48 +534,52 @@ impl Polls for Pallet { type Moment = T::BlockNumber; type Class = TrackIdOf; + fn classes() -> Vec { + T::Tracks::tracks().iter().map(|x| x.0).collect() + } + fn access_poll( index: Self::Index, - f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber>) -> R, + f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>) -> R, ) -> R { match ReferendumInfoFor::::get(index) { Some(ReferendumInfo::Ongoing(mut status)) => { - let result = f(PollStatus::Ongoing(&mut status.tally)); + let result = f(PollStatus::Ongoing(&mut status.tally, status.track)); let now = frame_system::Pallet::::block_number(); Self::ensure_alarm_at(&mut status, index, now + One::one()); ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); result }, - Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), - Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)), + Some(ReferendumInfo::Approved(end, ..)) + => f(PollStatus::Completed(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) + => f(PollStatus::Completed(end, false)), _ => f(PollStatus::None), } } fn try_access_poll( index: Self::Index, - f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber>) -> Result, + f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>) -> Result, ) -> Result { match ReferendumInfoFor::::get(index) { Some(ReferendumInfo::Ongoing(mut status)) => { - let result = f(PollStatus::Ongoing(&mut status.tally))?; + let result = f(PollStatus::Ongoing(&mut status.tally, status.track))?; let now = frame_system::Pallet::::block_number(); Self::ensure_alarm_at(&mut status, index, now + One::one()); ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); Ok(result) }, - Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), - Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)), + Some(ReferendumInfo::Approved(end, ..)) + => f(PollStatus::Completed(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) + => f(PollStatus::Completed(end, false)), _ => f(PollStatus::None), } } - fn tally(index: Self::Index) -> Option { - Some(Self::ensure_ongoing(index).ok()?.tally) - } - - fn is_active(index: Self::Index) -> Option> { - Self::ensure_ongoing(index).ok().map(|e| e.track) + fn as_ongoing(index: Self::Index) -> Option<(T::Tally, TrackIdOf)> { + Self::ensure_ongoing(index).ok().map(|x| (x.tally, x.track)) } } diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 711189b6cbc92..cac0f36d602b1 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -336,7 +336,7 @@ pub fn tally(r: ReferendumIndex) -> Tally { pub fn set_tally(index: ReferendumIndex, ayes: u32, nays: u32) { >::access_poll(index, |status| { - let tally = status.ensure_ongoing().unwrap(); + let tally = status.ensure_ongoing().unwrap().0; tally.ayes = ayes; tally.nays = nays; }); diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 8dc487a243cc6..c23dad5cdb1c3 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -104,16 +104,16 @@ pub trait VoteTally { fn from_requirements(turnout: Perbill, approval: Perbill) -> Self; } -pub enum PollStatus { +pub enum PollStatus { None, - Ongoing(Tally), + Ongoing(Tally, Class), Completed(Moment, bool), } -impl PollStatus { - pub fn ensure_ongoing(self) -> Option { +impl PollStatus { + pub fn ensure_ongoing(self) -> Option<(Tally, Class)> { match self { - Self::Ongoing(t) => Some(t), + Self::Ongoing(t, c) => Some((t, c)), _ => None, } } @@ -125,20 +125,22 @@ pub trait Polls { type Class: Parameter + Member; type Moment; - /// `Some` if the referendum `index` can be voted on, along with the class of referendum. Once - /// this is `None`, existing votes may be cancelled permissionlessly. - fn is_active(index: Self::Index) -> Option; + /// Provides a slice of values that `T` may take. + fn classes() -> Vec; + /// `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 tally(index: Self::Index) -> Option; + fn as_ongoing(index: Self::Index) -> Option<(Tally, Self::Class)>; fn access_poll( index: Self::Index, - f: impl FnOnce(PollStatus<&mut Tally, Self::Moment>) -> R, + f: impl FnOnce(PollStatus<&mut Tally, Self::Moment, Self::Class>) -> R, ) -> R; fn try_access_poll( index: Self::Index, - f: impl FnOnce(PollStatus<&mut Tally, Self::Moment>) -> Result, + f: impl FnOnce(PollStatus<&mut Tally, Self::Moment, Self::Class>) -> Result, ) -> Result; } From 65296a6330f15f9a6b41aa016241c5a5d91ac59f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 4 Jan 2022 16:54:51 +0100 Subject: [PATCH 43/66] New test & mock utils --- frame/conviction-voting/src/tests.rs | 113 ++++++++++++--------------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 98c06139f774c..2e0cf7f3291cc 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -22,25 +22,15 @@ use std::collections::BTreeMap; use super::*; use crate as pallet_conviction_voting; use frame_support::{ - ord_parameter_types, parameter_types, - traits::{ConstU32, Contains, EqualPrivilegeOnly, OnInitialize, SortedMembers}, - weights::Weight, + ord_parameter_types, parameter_types, assert_ok, + traits::{ConstU32, Contains, SortedMembers}, }; -use frame_system::EnsureRoot; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, - Perbill, }; -const AYE: Vote = Vote { aye: true, conviction: Conviction::None }; -const NAY: Vote = Vote { aye: false, conviction: Conviction::None }; -const BIG_AYE: Vote = Vote { aye: true, conviction: Conviction::Locked1x }; -const BIG_NAY: Vote = Vote { aye: false, conviction: Conviction::Locked1x }; - -const MAX_PROPOSALS: u32 = 100; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -52,7 +42,6 @@ frame_support::construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, Voting: pallet_conviction_voting::{Pallet, Call, Storage, Event}, } ); @@ -96,22 +85,6 @@ impl frame_system::Config for Test { type OnSetCode = (); type MaxConsumers = ConstU32<16>; } -parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; -} -impl pallet_scheduler::Config for Test { - type Event = Event; - type Origin = Origin; - type PalletsOrigin = OriginCaller; - type Call = Call; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureRoot; - type MaxScheduledPerBlock = (); - type WeightInfo = (); - type OriginPrivilegeCmp = EqualPrivilegeOnly; - type PreimageProvider = (); - type NoPreimagePostponement = (); -} parameter_types! { pub const ExistentialDeposit: u64 = 1; pub const MaxLocks: u32 = 10; @@ -136,7 +109,7 @@ parameter_types! { pub const VoteLockingPeriod: u64 = 3; pub const CooloffPeriod: u64 = 2; pub const MaxVotes: u32 = 100; - pub const MaxProposals: u32 = MAX_PROPOSALS; + pub const MaxProposals: u32 = 100; pub static PreimageByteDeposit: u64 = 0; pub static InstantAllowed: bool = false; } @@ -175,7 +148,7 @@ parameter_types! { pub static Polls: BTreeMap = vec![ (1, Completed(1, true)), (2, Completed(2, false)), - (3, Ongoing(Tally::from_parts(1, 2, 3), 0)), + (3, Ongoing(Tally::from_parts(0, 0, 0), 0)), ].into_iter().collect(); } @@ -254,12 +227,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } -/// Execute the function two times, with `true` and with `false`. -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)); -} - #[test] fn params_should_work() { new_test_ext().execute_with(|| { @@ -270,59 +237,83 @@ fn params_should_work() { fn next_block() { System::set_block_number(System::block_number() + 1); - Scheduler::on_initialize(System::block_number()); } -fn fast_forward_to(n: u64) { +#[allow(dead_code)] +fn run_to(n: u64) { while System::block_number() < n { next_block(); } } -fn aye(who: u64) -> AccountVote { - AccountVote::Standard { vote: AYE, balance: Balances::free_balance(&who) } -} - -fn nay(who: u64) -> AccountVote { - AccountVote::Standard { vote: NAY, balance: Balances::free_balance(&who) } -} - -fn big_aye(who: u64) -> AccountVote { - AccountVote::Standard { vote: BIG_AYE, balance: Balances::free_balance(&who) } +fn aye(amount: u64, conviction: u8) -> AccountVote { + let vote = Vote { aye: true, conviction: conviction.try_into().unwrap() }; + AccountVote::Standard { vote, balance: amount } } -fn big_nay(who: u64) -> AccountVote { - AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } +fn nay(amount: u64, conviction: u8) -> AccountVote { + let vote = Vote { aye: false, conviction: conviction.try_into().unwrap() }; + AccountVote::Standard { vote, balance: amount } } fn tally(index: u8) -> TallyOf { >>::as_ongoing(index) - .expect("No tally") + .expect("No poll") .0 } +fn class(index: u8) -> u8 { + >>::as_ongoing(index) + .expect("No poll") + .1 +} + #[test] #[ignore] -#[should_panic(expected = "No tally")] +#[should_panic(expected = "No poll")] fn unknown_poll_should_panic() { let _ = tally(0); } #[test] #[ignore] -#[should_panic(expected = "No tally")] +#[should_panic(expected = "No poll")] fn completed_poll_should_panic() { let _ = tally(1); } #[test] fn basic_stuff() { - new_test_ext_execute_with_cond(|_x| { - assert_eq!(tally(3), Tally::from_parts(1, 2, 3)); - let _ = aye(0); - let _ = nay(0); - let _ = big_aye(0); - let _ = big_nay(0); - fast_forward_to(1); + 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); }); } From 37d8c7548a8fd55eba64b27d68d13d46d1d8309b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 6 Jan 2022 11:53:23 +0100 Subject: [PATCH 44/66] More tests --- frame/conviction-voting/src/lib.rs | 11 +- frame/conviction-voting/src/tests.rs | 241 ++++++++++++++++++++++----- frame/conviction-voting/src/vote.rs | 3 +- 3 files changed, 206 insertions(+), 49 deletions(-) diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 3817ef468b4d2..f8d43b9b3c34f 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -500,18 +500,20 @@ impl Pallet { ensure!(who != target, Error::::Nonsense); ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); let votes = VotingFor::::try_mutate(&who, &class, |voting| -> Result { - let mut old = Voting::Delegating(Delegating { + let old = sp_std::mem::replace(voting, Voting::Delegating(Delegating { balance, target: target.clone(), conviction, delegations: Default::default(), prior: Default::default(), - }); - sp_std::mem::swap(&mut old, voting); + })); match old { - Voting::Delegating(Delegating { balance, target, conviction, delegations, prior, .. }) => { + Voting::Delegating(Delegating { balance, target, conviction, delegations, mut prior, .. }) => { // remove any delegation votes to our current target. Self::reduce_upstream_delegation(&target, &class, conviction.votes(balance)); + let now = frame_system::Pallet::::block_number(); + let lock_periods = conviction.lock_periods().into(); + prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); voting.set_common(delegations, prior); }, Voting::Casting(Casting { votes, delegations, prior }) => { @@ -520,6 +522,7 @@ impl Pallet { 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. diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 2e0cf7f3291cc..b8bf587acccb0 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -21,10 +21,7 @@ use std::collections::BTreeMap; use super::*; use crate as pallet_conviction_voting; -use frame_support::{ - ord_parameter_types, parameter_types, assert_ok, - traits::{ConstU32, Contains, SortedMembers}, -}; +use frame_support::{parameter_types, assert_ok, traits::{ConstU32, Contains, ConstU64}}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -55,7 +52,6 @@ impl Contains for BaseFilter { } parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(1_000_000); } @@ -74,7 +70,7 @@ impl frame_system::Config for Test { type Lookup = IdentityLookup; type Header = Header; type Event = Event; - type BlockHashCount = BlockHashCount; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -85,50 +81,18 @@ impl frame_system::Config for Test { type OnSetCode = (); type MaxConsumers = ConstU32<16>; } -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 MaxLocks = ConstU32<10>; type Balance = u64; type Event = Event; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } -parameter_types! { - pub const LaunchPeriod: u64 = 2; - pub const VotingPeriod: u64 = 2; - pub const FastTrackVotingPeriod: u64 = 2; - pub const MinimumDeposit: u64 = 1; - pub const EnactmentPeriod: u64 = 2; - pub const VoteLockingPeriod: u64 = 3; - pub const CooloffPeriod: u64 = 2; - pub const MaxVotes: u32 = 100; - pub const MaxProposals: u32 = 100; - pub static PreimageByteDeposit: u64 = 0; - pub static InstantAllowed: bool = false; -} -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 for OneToFive { - fn sorted_members() -> Vec { - vec![1, 2, 3, 4, 5] - } - #[cfg(feature = "runtime-benchmarks")] - fn add(_m: &u64) {} -} pub struct TotalIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); impl, A> Get for TotalIssuanceOf { @@ -137,7 +101,7 @@ impl, A> Get for TotalIssuanceOf { } } -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum TestPollState { Ongoing(TallyOf, u8), Completed(u64, bool), @@ -208,8 +172,8 @@ impl frame_support::traits::Polls> for TestReferenda { impl Config for Test { type Event = Event; type Currency = pallet_balances::Pallet; - type VoteLockingPeriod = VoteLockingPeriod; - type MaxVotes = MaxVotes; + type VoteLockingPeriod = ConstU64<3>; + type MaxVotes = ConstU32<100>; type WeightInfo = (); type MaxTurnout = TotalIssuanceOf; type Referenda = TestReferenda; @@ -317,3 +281,192 @@ fn basic_voting_works() { 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::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::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::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::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::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::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); + }); +} + + + +// TODO: Lock extension/unlocking with different classes active +// TODO: Lock amalgamation via multiple removed votes +// TODO: Lock amalgamation via switch to/from delegation +// TODO: Errors \ No newline at end of file diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index 3479a8a61139f..e0d06591f5a96 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -81,6 +81,7 @@ impl AccountVote { 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, @@ -205,7 +206,7 @@ impl< 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, .. }) => *balance, + Voting::Delegating(Delegating { balance, prior, .. }) => *balance.max(&prior.locked()), } } From 1ed444437703d5fedd4d8948fd35a9854bef5fe5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 6 Jan 2022 17:34:08 +0100 Subject: [PATCH 45/66] Tests --- frame/conviction-voting/src/tests.rs | 209 ++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 4 deletions(-) diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index b8bf587acccb0..a167e3ac33d37 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -446,7 +446,6 @@ fn classwise_delegation_works() { }); } - #[test] fn redelegation_after_vote_ending_should_keep_lock() { new_test_ext().execute_with(|| { @@ -464,9 +463,211 @@ fn redelegation_after_vote_ending_should_keep_lock() { }); } +#[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::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 10)); + 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); + }); +} -// TODO: Lock extension/unlocking with different classes active -// TODO: Lock amalgamation via multiple removed votes -// TODO: Lock amalgamation via switch to/from delegation // TODO: Errors \ No newline at end of file From 382439b8d108de5726b25bf9258dce9479d3a12b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 7 Jan 2022 14:00:19 +0100 Subject: [PATCH 46/66] =?UTF-8?q?Tests=20finished=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frame/conviction-voting/src/benchmarking.rs | 50 +-- frame/conviction-voting/src/lib.rs | 140 ++++---- frame/conviction-voting/src/tests.rs | 77 +++- frame/conviction-voting/src/vote.rs | 20 +- frame/conviction-voting/src/weights.rs | 375 +------------------- frame/referenda/src/lib.rs | 6 +- frame/support/src/traits.rs | 2 +- frame/support/src/traits/voting.rs | 6 +- 8 files changed, 201 insertions(+), 475 deletions(-) diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 34bcb0da301e6..406ff30cdbe78 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -57,7 +57,7 @@ fn add_proposal(n: u32) -> Result { Ok(proposal_hash) } -fn add_referendum(n: u32) -> Result { +fn add_referendum(n: u32) -> Result { let proposal_hash: T::Hash = T::Hashing::hash_of(&n); let vote_threshold = VoteThreshold::SimpleMajority; @@ -67,7 +67,7 @@ fn add_referendum(n: u32) -> Result { vote_threshold, 0u32.into(), ); - let referendum_index: ReferendumIndex = ReferendumCount::::get() - 1; + let referendum_index: PollIndex = PollCount::::get() - 1; T::Scheduler::schedule_named( (DEMOCRACY_ID, referendum_index).encode(), DispatchTime::At(2u32.into()), @@ -186,7 +186,7 @@ benchmarks! { let referendum_info = Democracy::::referendum_info(referendum_index) .ok_or("referendum doesn't exist")?; let tally = match referendum_info { - ReferendumInfo::Ongoing(r) => r.tally, + PollInfo::Ongoing(r) => r.tally, _ => return Err("referendum not ongoing".into()), }; assert_eq!(tally.nays, 1000u32.into(), "changed vote was not recorded"); @@ -198,10 +198,10 @@ benchmarks! { assert_ok!(Democracy::::referendum_status(referendum_index)); }: _(origin, referendum_index) verify { - // Referendum has been canceled + // Poll has been canceled assert_noop!( Democracy::::referendum_status(referendum_index), - Error::::ReferendumInvalid, + Error::::PollInvalid, ); } @@ -225,10 +225,10 @@ benchmarks! { assert_ok!(Democracy::::referendum_status(referendum_index)); }: _(origin, hash, Some(referendum_index)) verify { - // Referendum has been canceled + // Poll has been canceled assert_noop!( Democracy::::referendum_status(referendum_index), - Error::::ReferendumInvalid + Error::::PollInvalid ); } @@ -361,10 +361,10 @@ benchmarks! { // All but the new next external should be finished for i in 0 .. r { - if let Some(value) = ReferendumInfoOf::::get(i) { + if let Some(value) = PollInfoOf::::get(i) { match value { - ReferendumInfo::Finished { .. } => (), - ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()), + PollInfo::Finished { .. } => (), + PollInfo::Ongoing(_) => return Err("Poll was not finished".into()), } } } @@ -395,10 +395,10 @@ benchmarks! { // All should be finished for i in 0 .. r { - if let Some(value) = ReferendumInfoOf::::get(i) { + if let Some(value) = PollInfoOf::::get(i) { match value { - ReferendumInfo::Finished { .. } => (), - ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()), + PollInfo::Finished { .. } => (), + PollInfo::Ongoing(_) => return Err("Poll was not finished".into()), } } } @@ -412,11 +412,11 @@ benchmarks! { add_referendum::(i)?; } - for (key, mut info) in ReferendumInfoOf::::iter() { - if let ReferendumInfo::Ongoing(ref mut status) = info { + for (key, mut info) in PollInfoOf::::iter() { + if let PollInfo::Ongoing(ref mut status) = info { status.end += 100u32.into(); } - ReferendumInfoOf::::insert(key, info); + PollInfoOf::::insert(key, info); } assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); @@ -426,10 +426,10 @@ benchmarks! { verify { // All should be on going for i in 0 .. r { - if let Some(value) = ReferendumInfoOf::::get(i) { + if let Some(value) = PollInfoOf::::get(i) { match value { - ReferendumInfo::Finished { .. } => return Err("Referendum has been finished".into()), - ReferendumInfo::Ongoing(_) => (), + PollInfo::Finished { .. } => return Err("Poll has been finished".into()), + PollInfo::Ongoing(_) => (), } } } @@ -442,11 +442,11 @@ benchmarks! { add_referendum::(i)?; } - for (key, mut info) in ReferendumInfoOf::::iter() { - if let ReferendumInfo::Ongoing(ref mut status) = info { + for (key, mut info) in PollInfoOf::::iter() { + if let PollInfo::Ongoing(ref mut status) = info { status.end += 100u32.into(); } - ReferendumInfoOf::::insert(key, info); + PollInfoOf::::insert(key, info); } assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); @@ -458,10 +458,10 @@ benchmarks! { verify { // All should be on going for i in 0 .. r { - if let Some(value) = ReferendumInfoOf::::get(i) { + if let Some(value) = PollInfoOf::::get(i) { match value { - ReferendumInfo::Finished { .. } => return Err("Referendum has been finished".into()), - ReferendumInfo::Ongoing(_) => (), + PollInfo::Finished { .. } => return Err("Poll has been finished".into()), + PollInfo::Ongoing(_) => (), } } } diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index f8d43b9b3c34f..a751e3942115a 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -20,7 +20,7 @@ //! //! ## Overview //! -//! Pallet for managing actual voting in referenda. +//! Pallet for managing actual voting in polls. #![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] @@ -28,8 +28,8 @@ use frame_support::{ ensure, traits::{ - Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polls, ReservableCurrency, - WithdrawReasons, + Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling, + ReservableCurrency, WithdrawReasons, }, }; use sp_runtime::{ @@ -62,7 +62,7 @@ type VotingOf = Voting< BalanceOf, ::AccountId, ::BlockNumber, - ReferendumIndexOf, + PollIndexOf, >; #[allow(dead_code)] type DelegatingOf = Delegating< @@ -71,8 +71,8 @@ type DelegatingOf = Delegating< ::BlockNumber, >; type TallyOf = Tally, ::MaxTurnout>; -type ReferendumIndexOf = <::Referenda as Polls>>::Index; -type ClassOf = <::Referenda as Polls>>::Class; +type PollIndexOf = <::Polls as Polling>>::Index; +type ClassOf = <::Polls as Polling>>::Class; #[frame_support::pallet] pub mod pallet { @@ -95,8 +95,8 @@ pub mod pallet { type Currency: ReservableCurrency + LockableCurrency; - /// The implementation of the logic which conducts referenda. - type Referenda: Polls, Votes = BalanceOf, Moment = Self::BlockNumber>; + /// The implementation of the logic which conducts polls. + type Polls: Polling, Votes = BalanceOf, 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 @@ -152,54 +152,59 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// Referendum is not ongoing. + /// Poll is not ongoing. NotOngoing, - /// The given account did not vote on the referendum. + /// 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, - /// The account currently has votes attached to it and the operation cannot succeed until - /// these are removed, either through `unvote` or `reap_vote`. - VotesExist, /// 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 Pallet { - /// Vote in a referendum. If `vote.is_aye()`, the vote is to enact the proposal; + /// 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_. /// - /// - `ref_index`: The index of the referendum to vote for. + /// - `poll_index`: The index of the poll to vote for. /// - `vote`: The vote configuration. /// - /// Weight: `O(R)` where R is the number of referenda the voter has voted on. + /// Weight: `O(R)` where R is the number of polls the voter has voted on. #[pallet::weight( T::WeightInfo::vote_new(T::MaxVotes::get()) .max(T::WeightInfo::vote_existing(T::MaxVotes::get())) )] pub fn vote( origin: OriginFor, - #[pallet::compact] ref_index: ReferendumIndexOf, + #[pallet::compact] poll_index: PollIndexOf, vote: AccountVote>, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::try_vote(&who, ref_index, vote) + Self::try_vote(&who, poll_index, vote) } - /// Delegate the voting power (with some given conviction) of the sending account. + /// 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. @@ -210,6 +215,8 @@ pub mod pallet { /// 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 @@ -217,7 +224,7 @@ pub mod pallet { /// /// Emits `Delegated`. /// - /// Weight: `O(R)` where R is the number of referenda the voter delegating to has + /// Weight: `O(R)` where R is the number of polls the voter delegating to has /// voted on. Weight is charged as if maximum votes. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. @@ -235,7 +242,7 @@ pub mod pallet { Ok(Some(T::WeightInfo::delegate(votes)).into()) } - /// Undelegate the voting power of the sending account. + /// 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. @@ -243,9 +250,11 @@ pub mod pallet { /// 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 referenda the voter delegating to has + /// Weight: `O(R)` where R is the number of polls the voter delegating to has /// voted on. Weight is charged as if maximum votes. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. @@ -256,36 +265,35 @@ pub mod pallet { Ok(Some(T::WeightInfo::undelegate(votes)).into()) } - /// Unlock tokens that have an expired lock. + /// 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_set(T::MaxVotes::get()) - .max(T::WeightInfo::unlock_remove(T::MaxVotes::get())) - )] + #[pallet::weight(T::WeightInfo::unlock(T::MaxVotes::get()))] pub fn unlock(origin: OriginFor, class: ClassOf, target: T::AccountId) -> DispatchResult { ensure_signed(origin)?; Self::update_lock(&class, &target); Ok(()) } - /// Remove a vote for a referendum. + /// Remove a vote for a poll. /// /// If: - /// - the referendum was cancelled, or - /// - the referendum is ongoing, or - /// - the referendum has ended such that + /// - 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 referendum has ended and: + /// 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 @@ -294,43 +302,46 @@ pub mod pallet { /// of both the amount locked and the time is it locked for). /// /// The dispatch origin of this call must be _Signed_, and the signer must have a vote - /// registered for referendum `index`. + /// registered for poll `index`. /// - /// - `index`: The index of referendum of the vote to be removed. + /// - `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 referenda that `target` has voted on. + /// 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(T::MaxVotes::get()))] pub fn remove_vote( origin: OriginFor, class: Option>, - index: ReferendumIndexOf, + index: PollIndexOf, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::try_remove_vote(&who, index, class, UnvoteScope::Any) } - /// Remove a vote for a referendum. + /// 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 referendum was cancelled, because the voter lost the referendum or + /// 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 - /// referendum `index`. - /// - `index`: The index of referendum of the vote to be removed. + /// 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 referenda that `target` has voted on. + /// 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(T::MaxVotes::get()))] pub fn remove_other_vote( origin: OriginFor, target: T::AccountId, class: ClassOf, - index: ReferendumIndexOf, + index: PollIndexOf, ) -> DispatchResult { let who = ensure_signed(origin)?; let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; @@ -344,15 +355,15 @@ impl Pallet { /// Actually enact a vote, if legit. fn try_vote( who: &T::AccountId, - ref_index: ReferendumIndexOf, + poll_index: PollIndexOf, vote: AccountVote>, ) -> DispatchResult { ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); - T::Referenda::try_access_poll(ref_index, |poll_status| { + T::Polls::try_access_poll(poll_index, |poll_status| { let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; VotingFor::::try_mutate(who, &class, |voting| { if let Voting::Casting(Casting { ref mut votes, delegations, .. }) = voting { - match votes.binary_search_by_key(&ref_index, |i| i.0) { + 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)?; @@ -363,10 +374,10 @@ impl Pallet { }, Err(i) => { ensure!( - votes.len() as u32 <= T::MaxVotes::get(), + (votes.len() as u32) < T::MaxVotes::get(), Error::::MaxVotesReached ); - votes.insert(i, (ref_index, vote)); + votes.insert(i, (poll_index, vote)); }, } // Shouldn't be possible to fail, but we handle it gracefully. @@ -385,29 +396,29 @@ impl Pallet { }) } - /// Remove the account's vote for the given referendum if possible. This is possible when: - /// - The referendum has not finished. - /// - The referendum has finished and the voter lost their direction. - /// - The referendum has finished and the voter's lock period is up. + /// 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, - ref_index: ReferendumIndexOf, + poll_index: PollIndexOf, class_hint: Option>, scope: UnvoteScope, ) -> DispatchResult { let class = class_hint - .or_else(|| Some(T::Referenda::as_ongoing(ref_index)?.1)) + .or_else(|| Some(T::Polls::as_ongoing(poll_index)?.1)) .ok_or(Error::::ClassNeeded)?; VotingFor::::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(&ref_index, |i| i.0) + .binary_search_by_key(&poll_index, |i| i.0) .map_err(|_| Error::::NotVoter)?; let v = votes.remove(i); - T::Referenda::try_access_poll(ref_index, |poll_status| match poll_status { + T::Polls::try_access_poll(poll_index, |poll_status| match poll_status { PollStatus::Ongoing(tally, _) => { ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); // Shouldn't be possible to fail, but we handle it gracefully. @@ -424,14 +435,14 @@ impl Pallet { if now < unlock_at { ensure!( matches!(scope, UnvoteScope::Any), - Error::::NoPermission + Error::::NoPermissionYet ); prior.accumulate(unlock_at, balance) } } Ok(()) }, - PollStatus::None => Ok(()), // Referendum was cancelled. + PollStatus::None => Ok(()), // Poll was cancelled. }) } else { Ok(()) @@ -449,9 +460,9 @@ impl Pallet { }, Voting::Casting(Casting { votes, delegations, .. }) => { *delegations = delegations.saturating_add(amount); - for &(ref_index, account_vote) in votes.iter() { + for &(poll_index, account_vote) in votes.iter() { if let AccountVote::Standard { vote, .. } = account_vote { - T::Referenda::access_poll(ref_index, |poll_status| { + T::Polls::access_poll(poll_index, |poll_status| { if let PollStatus::Ongoing(tally, _) = poll_status { tally.increase(vote.aye, amount); } @@ -473,9 +484,9 @@ impl Pallet { }, Voting::Casting(Casting { votes, delegations, .. }) => { *delegations = delegations.saturating_sub(amount); - for &(ref_index, account_vote) in votes.iter() { + for &(poll_index, account_vote) in votes.iter() { if let AccountVote::Standard { vote, .. } = account_vote { - T::Referenda::access_poll(ref_index, |poll_status| { + T::Polls::access_poll(poll_index, |poll_status| { if let PollStatus::Ongoing(tally, _) = poll_status { tally.reduce(vote.aye, amount); } @@ -498,6 +509,7 @@ impl Pallet { balance: BalanceOf, ) -> Result { ensure!(who != target, Error::::Nonsense); + T::Polls::classes().binary_search(&class).map_err(|_| Error::::BadClass)?; ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); let votes = VotingFor::::try_mutate(&who, &class, |voting| -> Result { let old = sp_std::mem::replace(voting, Voting::Delegating(Delegating { @@ -518,7 +530,7 @@ impl Pallet { }, Voting::Casting(Casting { votes, delegations, prior }) => { // here we just ensure that we're currently idling with no votes recorded. - ensure!(votes.is_empty(), Error::::VotesExist); + ensure!(votes.is_empty(), Error::::AlreadyVoting); voting.set_common(delegations, prior); }, } @@ -541,9 +553,7 @@ impl Pallet { class: ClassOf, ) -> Result { let votes = VotingFor::::try_mutate(&who, &class, |voting| -> Result { - let mut old = Voting::default(); - sp_std::mem::swap(&mut old, voting); - match old { + 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 = diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index a167e3ac33d37..85a34b4a43a7e 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -21,7 +21,8 @@ use std::collections::BTreeMap; use super::*; use crate as pallet_conviction_voting; -use frame_support::{parameter_types, assert_ok, traits::{ConstU32, Contains, ConstU64}}; +use frame_support::{parameter_types, assert_ok, assert_noop}; +use frame_support::traits::{ConstU32, Contains, ConstU64, Polling}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -116,8 +117,8 @@ parameter_types! { ].into_iter().collect(); } -pub struct TestReferenda; -impl frame_support::traits::Polls> for TestReferenda { +pub struct TestPolls; +impl Polling> for TestPolls { type Index = u8; type Votes = u64; type Moment = u64; @@ -173,10 +174,10 @@ impl Config for Test { type Event = Event; type Currency = pallet_balances::Pallet; type VoteLockingPeriod = ConstU64<3>; - type MaxVotes = ConstU32<100>; + type MaxVotes = ConstU32<3>; type WeightInfo = (); type MaxTurnout = TotalIssuanceOf; - type Referenda = TestReferenda; + type Polls = TestPolls; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -221,13 +222,13 @@ fn nay(amount: u64, conviction: u8) -> AccountVote { } fn tally(index: u8) -> TallyOf { - >>::as_ongoing(index) + >>::as_ongoing(index) .expect("No poll") .0 } fn class(index: u8) -> u8 { - >>::as_ongoing(index) + >>::as_ongoing(index) .expect("No poll") .1 } @@ -670,4 +671,64 @@ fn lock_aggregation_over_different_classes_with_casting_works() { }); } -// TODO: Errors \ No newline at end of file +#[test] +fn errors_with_vote_work() { + new_test_ext().execute_with(|| { + assert_noop!(Voting::vote(Origin::signed(1), 0, aye(10, 0)), Error::::NotOngoing); + assert_noop!(Voting::vote(Origin::signed(1), 1, aye(10, 0)), Error::::NotOngoing); + assert_noop!(Voting::vote(Origin::signed(1), 2, aye(10, 0)), Error::::NotOngoing); + assert_noop!(Voting::vote(Origin::signed(1), 3, aye(11, 0)), Error::::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::::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::::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::::InsufficientFunds); + assert_noop!(Voting::delegate(Origin::signed(1), 3, 2, Conviction::None, 10), Error::::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::::AlreadyVoting); + + assert_noop!(Voting::undelegate(Origin::signed(1), 0), Error::::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::::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::::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::::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::::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::::ClassNeeded); + }); +} diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index e0d06591f5a96..a8b256a0a54cb 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -149,9 +149,9 @@ pub struct Delegating { /// Information concerning the direct vote-casting of some voting power. #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct Casting { +pub struct Casting { /// The current votes of the account. - pub votes: Vec<(ReferendumIndex, AccountVote)>, + pub votes: Vec<(PollIndex, AccountVote)>, /// The total amount of delegations that this account has received, post-conviction-weighting. pub delegations: Delegations, /// Any pre-existing locks from past voting/delegating activity. @@ -160,15 +160,15 @@ pub struct Casting { /// An indicator for what an account is doing; it can either be delegating or voting. #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub enum Voting { +pub enum Voting { /// The account is voting directly. - Casting(Casting), + Casting(Casting), /// The account is delegating `balance` of its balance to a `target` account with `conviction`. Delegating(Delegating), } -impl Default - for Voting +impl Default + for Voting { fn default() -> Self { Voting::Casting(Casting { @@ -179,8 +179,8 @@ impl Default } } -impl AsMut> - for Voting +impl AsMut> + for Voting { fn as_mut(&mut self) -> &mut PriorLock { match self { @@ -194,8 +194,8 @@ impl< Balance: Saturating + Ord + Zero + Copy, BlockNumber: Ord + Copy + Zero, AccountId, - ReferendumIndex, - > Voting + PollIndex, + > Voting { pub fn rejig(&mut self, now: BlockNumber) { AsMut::>::as_mut(self).rejig(now); diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs index 638852d3c7e19..335c36b1c939b 100644 --- a/frame/conviction-voting/src/weights.rs +++ b/frame/conviction-voting/src/weights.rs @@ -45,54 +45,18 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_democracy. pub trait WeightInfo { - fn propose() -> Weight; - fn second(s: u32, ) -> Weight; fn vote_new(r: u32, ) -> Weight; fn vote_existing(r: u32, ) -> Weight; - fn emergency_cancel() -> Weight; - fn blacklist(p: u32, ) -> Weight; - fn external_propose(v: u32, ) -> Weight; - fn external_propose_majority() -> Weight; - fn external_propose_default() -> Weight; - fn fast_track() -> Weight; - fn veto_external(v: u32, ) -> Weight; - fn cancel_proposal(p: u32, ) -> Weight; - fn cancel_referendum() -> Weight; - fn cancel_queued(r: u32, ) -> Weight; - fn on_initialize_base(r: u32, ) -> Weight; - fn on_initialize_base_with_launch_period(r: u32, ) -> Weight; fn delegate(r: u32, ) -> Weight; fn undelegate(r: u32, ) -> Weight; - fn clear_public_proposals() -> Weight; - fn note_preimage(b: u32, ) -> Weight; - fn note_imminent_preimage(b: u32, ) -> Weight; - fn reap_preimage(b: u32, ) -> Weight; - fn unlock_remove(r: u32, ) -> Weight; - fn unlock_set(r: u32, ) -> Weight; fn remove_vote(r: u32, ) -> Weight; fn remove_other_vote(r: u32, ) -> Weight; + fn unlock(r: u32, ) -> Weight; } /// Weights for pallet_democracy using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Democracy PublicPropCount (r:1 w:1) - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - // Storage: Democracy DepositOf (r:0 w:1) - fn propose() -> Weight { - (67_388_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - // Storage: Democracy DepositOf (r:1 w:1) - fn second(s: u32, ) -> Weight { - (41_157_000 as Weight) - // Standard Error: 0 - .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) @@ -113,109 +77,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Cancellations (r:1 w:1) - fn emergency_cancel() -> Weight { - (27_699_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Blacklist (r:0 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn blacklist(p: u32, ) -> Weight { - (82_703_000 as Weight) - // Standard Error: 4_000 - .saturating_add((500_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - fn external_propose(v: u32, ) -> Weight { - (13_747_000 as Weight) - // Standard Error: 0 - .saturating_add((76_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy NextExternal (r:0 w:1) - fn external_propose_majority() -> Weight { - (3_070_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy NextExternal (r:0 w:1) - fn external_propose_default() -> Weight { - (3_080_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:0 w:1) - fn fast_track() -> Weight { - (29_129_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:1) - fn veto_external(v: u32, ) -> Weight { - (30_105_000 as Weight) - // Standard Error: 0 - .saturating_add((104_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn cancel_proposal(p: u32, ) -> Weight { - (55_228_000 as Weight) - // Standard Error: 1_000 - .saturating_add((457_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - // Storage: Democracy ReferendumInfoOf (r:0 w:1) - fn cancel_referendum() -> Weight { - (17_319_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) - fn cancel_queued(r: u32, ) -> Weight { - (29_738_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_153_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base(r: u32, ) -> Weight { - (2_165_000 as Weight) - // Standard Error: 3_000 - .saturating_add((5_577_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy LastTabledWasExternal (r:1 w:0) - // Storage: Democracy NextExternal (r:1 w:0) - // Storage: Democracy PublicProps (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - (9_396_000 as Weight) - // Standard Error: 4_000 - .saturating_add((5_604_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - } // Storage: Democracy VotingOf (r:3 w:3) // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) @@ -239,56 +100,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) } - // Storage: Democracy PublicProps (r:0 w:1) - fn clear_public_proposals() -> Weight { - (2_780_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_preimage(b: u32, ) -> Weight { - (46_416_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_imminent_preimage(b: u32, ) -> Weight { - (29_735_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - // Storage: System Account (r:1 w:0) - fn reap_preimage(b: u32, ) -> Weight { - (41_276_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn unlock_remove(r: u32, ) -> Weight { - (40_348_000 as Weight) - // Standard Error: 1_000 - .saturating_add((60_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn unlock_set(r: u32, ) -> Weight { - (37_475_000 as Weight) - // Standard Error: 1_000 - .saturating_add((151_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_vote(r: u32, ) -> Weight { @@ -307,27 +118,17 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + fn unlock(r: u32, ) -> Weight { + (20_094_000 as Weight) + // Standard Error: 1_000 + .saturating_add((157_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Democracy PublicPropCount (r:1 w:1) - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - // Storage: Democracy DepositOf (r:0 w:1) - fn propose() -> Weight { - (67_388_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } - // Storage: Democracy DepositOf (r:1 w:1) - fn second(s: u32, ) -> Weight { - (41_157_000 as Weight) - // Standard Error: 0 - .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) @@ -348,109 +149,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Cancellations (r:1 w:1) - fn emergency_cancel() -> Weight { - (27_699_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Blacklist (r:0 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn blacklist(p: u32, ) -> Weight { - (82_703_000 as Weight) - // Standard Error: 4_000 - .saturating_add((500_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - fn external_propose(v: u32, ) -> Weight { - (13_747_000 as Weight) - // Standard Error: 0 - .saturating_add((76_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy NextExternal (r:0 w:1) - fn external_propose_majority() -> Weight { - (3_070_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy NextExternal (r:0 w:1) - fn external_propose_default() -> Weight { - (3_080_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:0 w:1) - fn fast_track() -> Weight { - (29_129_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:1) - fn veto_external(v: u32, ) -> Weight { - (30_105_000 as Weight) - // Standard Error: 0 - .saturating_add((104_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn cancel_proposal(p: u32, ) -> Weight { - (55_228_000 as Weight) - // Standard Error: 1_000 - .saturating_add((457_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } - // Storage: Democracy ReferendumInfoOf (r:0 w:1) - fn cancel_referendum() -> Weight { - (17_319_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) - fn cancel_queued(r: u32, ) -> Weight { - (29_738_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_153_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base(r: u32, ) -> Weight { - (2_165_000 as Weight) - // Standard Error: 3_000 - .saturating_add((5_577_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy LastTabledWasExternal (r:1 w:0) - // Storage: Democracy NextExternal (r:1 w:0) - // Storage: Democracy PublicProps (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - (9_396_000 as Weight) - // Standard Error: 4_000 - .saturating_add((5_604_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - } // Storage: Democracy VotingOf (r:3 w:3) // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) @@ -474,56 +172,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) } - // Storage: Democracy PublicProps (r:0 w:1) - fn clear_public_proposals() -> Weight { - (2_780_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_preimage(b: u32, ) -> Weight { - (46_416_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_imminent_preimage(b: u32, ) -> Weight { - (29_735_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - // Storage: System Account (r:1 w:0) - fn reap_preimage(b: u32, ) -> Weight { - (41_276_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn unlock_remove(r: u32, ) -> Weight { - (40_348_000 as Weight) - // Standard Error: 1_000 - .saturating_add((60_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn unlock_set(r: u32, ) -> Weight { - (37_475_000 as Weight) - // Standard Error: 1_000 - .saturating_add((151_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_vote(r: u32, ) -> Weight { @@ -542,4 +190,11 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } + fn unlock(r: u32, ) -> Weight { + (20_094_000 as Weight) + // Standard Error: 1_000 + .saturating_add((157_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } } diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 590ff49e55395..8dd725fa97c1a 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -17,7 +17,7 @@ //! //! ## Overview //! -//! A pallet for executing referenda. No voting logic is present here, and the `Polls` and +//! A pallet for executing referenda. No voting logic is present here, and the `Polling` and //! `PollStatus` traits are used to allow the voting logic (likely in a pallet) to be utilized. //! //! A referendum is a vote on whether a proposal should be dispatched from a particular origin. The @@ -67,7 +67,7 @@ use frame_support::{ DispatchTime, MaybeHashed, }, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, OriginTrait, PollStatus, - Polls, ReservableCurrency, VoteTally, + Polling, ReservableCurrency, VoteTally, }, BoundedVec, }; @@ -528,7 +528,7 @@ pub mod pallet { } } -impl Polls for Pallet { +impl Polling for Pallet { type Index = ReferendumIndex; type Votes = VotesOf; type Moment = T::BlockNumber; diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 723b097d80f8d..8e1410b22da6b 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -89,5 +89,5 @@ pub use dispatch::{EnsureOneOf, EnsureOrigin, OriginTrait, UnfilteredDispatchabl mod voting; pub use voting::{ - CurrencyToVote, PollStatus, Polls, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, + CurrencyToVote, PollStatus, Polling, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, }; diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index c23dad5cdb1c3..c7dce457092ce 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -119,13 +119,13 @@ impl PollStatus { } } -pub trait Polls { +pub trait Polling { type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; - type Class: Parameter + Member; + type Class: Parameter + Member + Ord + PartialOrd; type Moment; - /// Provides a slice of values that `T` may take. + /// Provides a vec of values that `T` may take. fn classes() -> Vec; /// `Some` if the referendum `index` can be voted on, along with the tally and class of From 0936fec8f5e4d0752f39fa38eb01edb8974dbb8b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 7 Jan 2022 16:34:31 +0100 Subject: [PATCH 47/66] Benchmarking stuff --- frame/conviction-voting/src/benchmarking.rs | 500 +------------------- frame/conviction-voting/src/lib.rs | 15 +- frame/conviction-voting/src/tests.rs | 22 + frame/conviction-voting/src/weights.rs | 70 +-- frame/referenda/src/lib.rs | 47 ++ frame/support/src/traits/voting.rs | 13 + 6 files changed, 121 insertions(+), 546 deletions(-) diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 406ff30cdbe78..677911cf9b20a 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Democracy pallet benchmarking. +//! ConvictionVoting pallet benchmarking. use super::*; @@ -30,12 +30,9 @@ use frame_support::{ use frame_system::{Pallet as System, RawOrigin}; use sp_runtime::traits::{BadOrigin, Bounded, One}; -use crate::Pallet as Democracy; +use crate::Pallet as ConvictionVoting; const SEED: u32 = 0; -const MAX_REFERENDUMS: u32 = 99; -const MAX_SECONDERS: u32 = 100; -const MAX_BYTES: u32 = 16_384; fn assert_last_event(generic_event: ::Event) { frame_system::Pallet::::assert_last_event(generic_event.into()); @@ -47,21 +44,11 @@ fn funded_account(name: &'static str, index: u32) -> T::AccountId { caller } -fn add_proposal(n: u32) -> Result { - let other = funded_account::("proposer", n); - let value = T::MinimumDeposit::get(); - let proposal_hash: T::Hash = T::Hashing::hash_of(&n); - - Democracy::::propose(RawOrigin::Signed(other).into(), proposal_hash, value.into())?; - - Ok(proposal_hash) -} - fn add_referendum(n: u32) -> Result { let proposal_hash: T::Hash = T::Hashing::hash_of(&n); let vote_threshold = VoteThreshold::SimpleMajority; - Democracy::::inject_referendum( + ConvictionVoting::::inject_referendum( T::LaunchPeriod::get(), proposal_hash, vote_threshold, @@ -87,43 +74,6 @@ fn account_vote(b: BalanceOf) -> AccountVote> { } benchmarks! { - propose { - let p = T::MaxProposals::get(); - - for i in 0 .. (p - 1) { - add_proposal::(i)?; - } - - let caller = funded_account::("caller", 0); - let proposal_hash: T::Hash = T::Hashing::hash_of(&0); - let value = T::MinimumDeposit::get(); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), proposal_hash, value.into()) - verify { - assert_eq!(Democracy::::public_props().len(), p as usize, "Proposals not created."); - } - - second { - let s in 0 .. MAX_SECONDERS; - - let caller = funded_account::("caller", 0); - let proposal_hash = add_proposal::(s)?; - - // Create s existing "seconds" - for i in 0 .. s { - let seconder = funded_account::("seconder", i); - Democracy::::second(RawOrigin::Signed(seconder).into(), 0, u32::MAX)?; - } - - let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; - assert_eq!(deposits.0.len(), (s + 1) as usize, "Seconds not recorded"); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), 0, u32::MAX) - verify { - let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; - assert_eq!(deposits.0.len(), (s + 2) as usize, "`second` benchmark did not work"); - } - vote_new { let r in 1 .. MAX_REFERENDUMS; @@ -133,7 +83,7 @@ benchmarks! { // We need to create existing direct votes for i in 0 .. r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; } let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, @@ -161,7 +111,7 @@ benchmarks! { // We need to create existing direct votes for i in 0 ..=r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; } let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, @@ -172,7 +122,7 @@ benchmarks! { // Change vote from aye to nay let nay = Vote { aye: false, conviction: Conviction::Locked1x }; let new_vote = AccountVote::Standard { vote: nay, balance: 1000u32.into() }; - let referendum_index = Democracy::::referendum_count() - 1; + let referendum_index = ConvictionVoting::::referendum_count() - 1; // This tests when a user changes a vote whitelist_account!(caller); @@ -183,7 +133,7 @@ benchmarks! { _ => return Err("Votes are not direct".into()), }; assert_eq!(votes.len(), (r + 1) as usize, "Vote was incorrectly added"); - let referendum_info = Democracy::::referendum_info(referendum_index) + let referendum_info = ConvictionVoting::::referendum_info(referendum_index) .ok_or("referendum doesn't exist")?; let tally = match referendum_info { PollInfo::Ongoing(r) => r.tally, @@ -192,281 +142,6 @@ benchmarks! { assert_eq!(tally.nays, 1000u32.into(), "changed vote was not recorded"); } - emergency_cancel { - let origin = T::CancellationOrigin::successful_origin(); - let referendum_index = add_referendum::(0)?; - assert_ok!(Democracy::::referendum_status(referendum_index)); - }: _(origin, referendum_index) - verify { - // Poll has been canceled - assert_noop!( - Democracy::::referendum_status(referendum_index), - Error::::PollInvalid, - ); - } - - blacklist { - let p in 1 .. T::MaxProposals::get(); - - // Place our proposal at the end to make sure it's worst case. - for i in 0 .. p - 1 { - add_proposal::(i)?; - } - // We should really add a lot of seconds here, but we're not doing it elsewhere. - - // Place our proposal in the external queue, too. - let hash = T::Hashing::hash_of(&0); - assert_ok!( - Democracy::::external_propose(T::ExternalOrigin::successful_origin(), hash.clone()) - ); - let origin = T::BlacklistOrigin::successful_origin(); - // Add a referendum of our proposal. - let referendum_index = add_referendum::(0)?; - assert_ok!(Democracy::::referendum_status(referendum_index)); - }: _(origin, hash, Some(referendum_index)) - verify { - // Poll has been canceled - assert_noop!( - Democracy::::referendum_status(referendum_index), - Error::::PollInvalid - ); - } - - // Worst case scenario, we external propose a previously blacklisted proposal - external_propose { - let v in 1 .. MAX_VETOERS as u32; - - let origin = T::ExternalOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&0); - // Add proposal to blacklist with block number 0 - Blacklist::::insert( - proposal_hash, - (T::BlockNumber::zero(), vec![T::AccountId::default(); v as usize]) - ); - }: _(origin, proposal_hash) - verify { - // External proposal created - ensure!(>::exists(), "External proposal didn't work"); - } - - external_propose_majority { - let origin = T::ExternalMajorityOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&0); - }: _(origin, proposal_hash) - verify { - // External proposal created - ensure!(>::exists(), "External proposal didn't work"); - } - - external_propose_default { - let origin = T::ExternalDefaultOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&0); - }: _(origin, proposal_hash) - verify { - // External proposal created - ensure!(>::exists(), "External proposal didn't work"); - } - - fast_track { - let origin_propose = T::ExternalDefaultOrigin::successful_origin(); - let proposal_hash: T::Hash = T::Hashing::hash_of(&0); - Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; - - // NOTE: Instant origin may invoke a little bit more logic, but may not always succeed. - let origin_fast_track = T::FastTrackOrigin::successful_origin(); - let voting_period = T::FastTrackVotingPeriod::get(); - let delay = 0u32; - }: _(origin_fast_track, proposal_hash, voting_period.into(), delay.into()) - verify { - assert_eq!(Democracy::::referendum_count(), 1, "referendum not created") - } - - veto_external { - // Existing veto-ers - let v in 0 .. MAX_VETOERS as u32; - - let proposal_hash: T::Hash = T::Hashing::hash_of(&v); - - let origin_propose = T::ExternalDefaultOrigin::successful_origin(); - Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; - - let mut vetoers: Vec = Vec::new(); - for i in 0 .. v { - vetoers.push(account("vetoer", i, SEED)); - } - vetoers.sort(); - Blacklist::::insert(proposal_hash, (T::BlockNumber::zero(), vetoers)); - - let origin = T::VetoOrigin::successful_origin(); - ensure!(NextExternal::::get().is_some(), "no external proposal"); - }: _(origin, proposal_hash) - verify { - assert!(NextExternal::::get().is_none()); - let (_, new_vetoers) = >::get(&proposal_hash).ok_or("no blacklist")?; - assert_eq!(new_vetoers.len(), (v + 1) as usize, "vetoers not added"); - } - - cancel_proposal { - let p in 1 .. T::MaxProposals::get(); - - // Place our proposal at the end to make sure it's worst case. - for i in 0 .. p { - add_proposal::(i)?; - } - }: _(RawOrigin::Root, 0) - - cancel_referendum { - let referendum_index = add_referendum::(0)?; - }: _(RawOrigin::Root, referendum_index) - - cancel_queued { - let r in 1 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; // This add one element in the scheduler - } - - let referendum_index = add_referendum::(r)?; - }: _(RawOrigin::Root, referendum_index) - - // This measures the path of `launch_next` external. Not currently used as we simply - // assume the weight is `MaxBlockWeight` when executing. - #[extra] - on_initialize_external { - let r in 0 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; - } - - assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); - - // Launch external - LastTabledWasExternal::::put(false); - - let origin = T::ExternalMajorityOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&r); - let call = Call::::external_propose_majority { proposal_hash }; - call.dispatch_bypass_filter(origin)?; - // External proposal created - ensure!(>::exists(), "External proposal didn't work"); - - let block_number = T::LaunchPeriod::get(); - - }: { Democracy::::on_initialize(block_number) } - verify { - // One extra because of next external - assert_eq!(Democracy::::referendum_count(), r + 1, "referenda not created"); - ensure!(!>::exists(), "External wasn't taken"); - - // All but the new next external should be finished - for i in 0 .. r { - if let Some(value) = PollInfoOf::::get(i) { - match value { - PollInfo::Finished { .. } => (), - PollInfo::Ongoing(_) => return Err("Poll was not finished".into()), - } - } - } - } - - // This measures the path of `launch_next` public. Not currently used as we simply - // assume the weight is `MaxBlockWeight` when executing. - #[extra] - on_initialize_public { - let r in 1 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; - } - - assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); - - // Launch public - assert!(add_proposal::(r).is_ok(), "proposal not created"); - LastTabledWasExternal::::put(true); - - let block_number = T::LaunchPeriod::get(); - - }: { Democracy::::on_initialize(block_number) } - verify { - // One extra because of next public - assert_eq!(Democracy::::referendum_count(), r + 1, "proposal not accepted"); - - // All should be finished - for i in 0 .. r { - if let Some(value) = PollInfoOf::::get(i) { - match value { - PollInfo::Finished { .. } => (), - PollInfo::Ongoing(_) => return Err("Poll was not finished".into()), - } - } - } - } - - // No launch no maturing referenda. - on_initialize_base { - let r in 1 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; - } - - for (key, mut info) in PollInfoOf::::iter() { - if let PollInfo::Ongoing(ref mut status) = info { - status.end += 100u32.into(); - } - PollInfoOf::::insert(key, info); - } - - assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); - assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); - - }: { Democracy::::on_initialize(1u32.into()) } - verify { - // All should be on going - for i in 0 .. r { - if let Some(value) = PollInfoOf::::get(i) { - match value { - PollInfo::Finished { .. } => return Err("Poll has been finished".into()), - PollInfo::Ongoing(_) => (), - } - } - } - } - - on_initialize_base_with_launch_period { - let r in 1 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; - } - - for (key, mut info) in PollInfoOf::::iter() { - if let PollInfo::Ongoing(ref mut status) = info { - status.end += 100u32.into(); - } - PollInfoOf::::insert(key, info); - } - - assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); - assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); - - let block_number = T::LaunchPeriod::get(); - - }: { Democracy::::on_initialize(block_number) } - verify { - // All should be on going - for i in 0 .. r { - if let Some(value) = PollInfoOf::::get(i) { - match value { - PollInfo::Finished { .. } => return Err("Poll has been finished".into()), - PollInfo::Ongoing(_) => (), - } - } - } - } - delegate { let r in 1 .. MAX_REFERENDUMS; @@ -476,7 +151,7 @@ benchmarks! { let caller = funded_account::("caller", 0); // Caller will initially delegate to `old_delegate` let old_delegate: T::AccountId = funded_account::("old_delegate", r); - Democracy::::delegate( + ConvictionVoting::::delegate( RawOrigin::Signed(caller.clone()).into(), old_delegate.clone(), Conviction::Locked1x, @@ -494,7 +169,7 @@ benchmarks! { // We need to create existing direct votes for the `new_delegate` for i in 0..r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote.clone())?; } let votes = match VotingOf::::get(&new_delegate) { Voting::Direct { votes, .. } => votes, @@ -526,7 +201,7 @@ benchmarks! { let caller = funded_account::("caller", 0); // Caller will delegate let the_delegate: T::AccountId = funded_account::("delegate", r); - Democracy::::delegate( + ConvictionVoting::::delegate( RawOrigin::Signed(caller.clone()).into(), the_delegate.clone(), Conviction::Locked1x, @@ -542,7 +217,7 @@ benchmarks! { let account_vote = account_vote::(initial_balance); for i in 0..r { let ref_idx = add_referendum::(i)?; - Democracy::::vote( + ConvictionVoting::::vote( RawOrigin::Signed(the_delegate.clone()).into(), ref_idx, account_vote.clone() @@ -563,99 +238,8 @@ benchmarks! { } } - clear_public_proposals { - add_proposal::(0)?; - - }: _(RawOrigin::Root) - - note_preimage { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let caller = funded_account::("caller", 0); - let encoded_proposal = vec![1; b as usize]; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) - verify { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - } - - note_imminent_preimage { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - // d + 1 to include the one we are testing - let encoded_proposal = vec![1; b as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - let block_number = T::BlockNumber::one(); - Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number)); - - let caller = funded_account::("caller", 0); - let encoded_proposal = vec![1; b as usize]; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) - verify { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - } - - reap_preimage { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let encoded_proposal = vec![1; b as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - - let submitter = funded_account::("submitter", b); - Democracy::::note_preimage(RawOrigin::Signed(submitter.clone()).into(), encoded_proposal.clone())?; - - // We need to set this otherwise we get `Early` error. - let block_number = T::VotingPeriod::get() + T::EnactmentPeriod::get() + T::BlockNumber::one(); - System::::set_block_number(block_number.into()); - - assert!(Preimages::::contains_key(proposal_hash)); - - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), proposal_hash.clone(), u32::MAX) - verify { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - assert!(!Preimages::::contains_key(proposal_hash)); - } - - // Test when unlock will remove locks - unlock_remove { - let r in 1 .. MAX_REFERENDUMS; - - let locker = funded_account::("locker", 0); - // Populate votes so things are locked - let base_balance: BalanceOf = 100u32.into(); - let small_vote = account_vote::(base_balance); - // Vote and immediately unvote - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; - Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_idx)?; - } - - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - }: unlock(RawOrigin::Signed(caller), locker.clone()) - verify { - // Note that we may want to add a `get_lock` api to actually verify - let voting = VotingOf::::get(&locker); - assert_eq!(voting.locked_balance(), BalanceOf::::zero()); - } - // Test when unlock will set a new value - unlock_set { + unlock { let r in 1 .. MAX_REFERENDUMS; let locker = funded_account::("locker", 0); @@ -664,13 +248,13 @@ benchmarks! { let small_vote = account_vote::(base_balance); for i in 0 .. r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; } // Create a big vote so lock increases let big_vote = account_vote::(base_balance * 10u32.into()); let referendum_index = add_referendum::(r)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), referendum_index, big_vote)?; + ConvictionVoting::::vote(RawOrigin::Signed(locker.clone()).into(), referendum_index, big_vote)?; let votes = match VotingOf::::get(&locker) { Voting::Direct { votes, .. } => votes, @@ -681,7 +265,7 @@ benchmarks! { let voting = VotingOf::::get(&locker); assert_eq!(voting.locked_balance(), base_balance * 10u32.into()); - Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), referendum_index)?; + ConvictionVoting::::remove_vote(RawOrigin::Signed(locker.clone()).into(), referendum_index)?; let caller = funded_account::("caller", 0); whitelist_account!(caller); @@ -706,7 +290,7 @@ benchmarks! { for i in 0 .. r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; } let votes = match VotingOf::::get(&caller) { @@ -735,7 +319,7 @@ benchmarks! { for i in 0 .. r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; } let votes = match VotingOf::::get(&caller) { @@ -755,56 +339,8 @@ benchmarks! { assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); } - #[extra] - enact_proposal_execute { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let proposer = funded_account::("proposer", 0); - let raw_call = Call::note_preimage { encoded_proposal: vec![1; b as usize] }; - let generic_call: T::Proposal = raw_call.into(); - let encoded_proposal = generic_call.encode(); - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; - - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - }: enact_proposal(RawOrigin::Root, proposal_hash, 0) - verify { - // Fails due to mismatched origin - assert_last_event::(Event::::Executed(0, Err(BadOrigin.into())).into()); - } - - #[extra] - enact_proposal_slash { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let proposer = funded_account::("proposer", 0); - // Random invalid bytes - let encoded_proposal = vec![200; b as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; - - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - let origin = RawOrigin::Root.into(); - let call = Call::::enact_proposal { proposal_hash, index: 0 }.encode(); - }: { - assert_eq!( - as Decode>::decode(&mut &*call) - .expect("call is encoded above, encoding must be correct") - .dispatch_bypass_filter(origin), - Err(Error::::PreimageInvalid.into()) - ); - } - impl_benchmark_test_suite!( - Democracy, + ConvictionVoting, crate::tests::new_test_ext(), crate::tests::Test ); diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index a751e3942115a..761561d452d5b 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -190,10 +190,7 @@ pub mod pallet { /// - `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(T::MaxVotes::get()) - .max(T::WeightInfo::vote_existing(T::MaxVotes::get())) - )] + #[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))] pub fn vote( origin: OriginFor, #[pallet::compact] poll_index: PollIndexOf, @@ -225,7 +222,7 @@ pub mod pallet { /// Emits `Delegated`. /// /// Weight: `O(R)` where R is the number of polls the voter delegating to has - /// voted on. Weight is charged as if maximum votes. + /// 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()))] @@ -255,7 +252,7 @@ pub mod pallet { /// Emits `Undelegated`. /// /// Weight: `O(R)` where R is the number of polls the voter delegating to has - /// voted on. Weight is charged as if maximum votes. + /// 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()))] @@ -274,7 +271,7 @@ pub mod pallet { /// - `target`: The account to remove the lock on. /// /// Weight: `O(R)` with R number of vote of target. - #[pallet::weight(T::WeightInfo::unlock(T::MaxVotes::get()))] + #[pallet::weight(T::WeightInfo::unlock())] pub fn unlock(origin: OriginFor, class: ClassOf, target: T::AccountId) -> DispatchResult { ensure_signed(origin)?; Self::update_lock(&class, &target); @@ -310,7 +307,7 @@ pub mod pallet { /// /// 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(T::MaxVotes::get()))] + #[pallet::weight(T::WeightInfo::remove_vote())] pub fn remove_vote( origin: OriginFor, class: Option>, @@ -336,7 +333,7 @@ pub mod pallet { /// /// 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(T::MaxVotes::get()))] + #[pallet::weight(T::WeightInfo::remove_other_vote())] pub fn remove_other_vote( origin: OriginFor, target: T::AccountId, diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 85a34b4a43a7e..f6467d08bc0d2 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -168,6 +168,28 @@ impl Polling> for TestPolls { Polls::set(polls); Ok(r) } + + #[cfg(feature = "runtime-benchmarks")] + fn create_ongoing(period: Self::Moment, class: Self::Class) -> Result { + let mut polls = Polls::get(); + let i = polls.keys().rev().next().map_or(0, |x| x + 1); + polls.insert(i, (class, Tally::default())); + 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(t, _)) => {}, + _ => return Err(()), + } + let now = frame_system::Pallet::::block_number(); + polls.insert(i, Completed(now, approved)); + Polls::set(polls); + Ok(()) + } } impl Config for Test { diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs index 335c36b1c939b..c60784eb949fa 100644 --- a/frame/conviction-voting/src/weights.rs +++ b/frame/conviction-voting/src/weights.rs @@ -45,13 +45,13 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_democracy. pub trait WeightInfo { - fn vote_new(r: u32, ) -> Weight; - fn vote_existing(r: u32, ) -> Weight; + fn vote_new() -> Weight; + fn vote_existing() -> Weight; fn delegate(r: u32, ) -> Weight; fn undelegate(r: u32, ) -> Weight; - fn remove_vote(r: u32, ) -> Weight; - fn remove_other_vote(r: u32, ) -> Weight; - fn unlock(r: u32, ) -> Weight; + fn remove_vote() -> Weight; + fn remove_other_vote() -> Weight; + fn unlock() -> Weight; } /// Weights for pallet_democracy using the Substrate node and recommended hardware. @@ -60,22 +60,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - fn vote_new(r: u32, ) -> Weight { + fn vote_new() -> Weight { (46_406_000 as Weight) - // Standard Error: 1_000 - .saturating_add((170_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - fn vote_existing(r: u32, ) -> Weight { + fn vote_existing() -> Weight { (46_071_000 as Weight) - // Standard Error: 1_000 - .saturating_add((166_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy VotingOf (r:3 w:3) // Storage: Democracy ReferendumInfoOf (r:1 w:1) @@ -102,28 +94,16 @@ impl WeightInfo for SubstrateWeight { } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) - fn remove_vote(r: u32, ) -> Weight { + fn remove_vote() -> Weight { (19_970_000 as Weight) - // Standard Error: 1_000 - .saturating_add((153_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) - fn remove_other_vote(r: u32, ) -> Weight { + fn remove_other_vote() -> Weight { (20_094_000 as Weight) - // Standard Error: 1_000 - .saturating_add((157_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - fn unlock(r: u32, ) -> Weight { + fn unlock() -> Weight { (20_094_000 as Weight) - // Standard Error: 1_000 - .saturating_add((157_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } } @@ -132,22 +112,14 @@ impl WeightInfo for () { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - fn vote_new(r: u32, ) -> Weight { + fn vote_new() -> Weight { (46_406_000 as Weight) - // Standard Error: 1_000 - .saturating_add((170_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - fn vote_existing(r: u32, ) -> Weight { + fn vote_existing() -> Weight { (46_071_000 as Weight) - // Standard Error: 1_000 - .saturating_add((166_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy VotingOf (r:3 w:3) // Storage: Democracy ReferendumInfoOf (r:1 w:1) @@ -174,27 +146,15 @@ impl WeightInfo for () { } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) - fn remove_vote(r: u32, ) -> Weight { + fn remove_vote() -> Weight { (19_970_000 as Weight) - // Standard Error: 1_000 - .saturating_add((153_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) - fn remove_other_vote(r: u32, ) -> Weight { + fn remove_other_vote() -> Weight { (20_094_000 as Weight) - // Standard Error: 1_000 - .saturating_add((157_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - fn unlock(r: u32, ) -> Weight { + fn unlock() -> Weight { (20_094_000 as Weight) - // Standard Error: 1_000 - .saturating_add((157_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } } diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 8dd725fa97c1a..df0d5a44632d4 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -581,6 +581,53 @@ impl Polling for Pallet { fn as_ongoing(index: Self::Index) -> Option<(T::Tally, TrackIdOf)> { Self::ensure_ongoing(index).ok().map(|x| (x.tally, x.track)) } + + #[cfg(feature = "runtime-benchmarks")] + fn create_ongoing(period: Self::Moment, class: Self::Class) -> Result { + let index = ReferendumCount::::mutate(|x| { + let r = *x; + *x += 1; + r + }); + let status = ReferendumStatusOf:: { + class, + origin: RawOrigin::Nobody.into(), + proposal_hash: BlakeTwo256::hash_of(index), + enactment: AtOrAfter::After(0), + submitted: frame_system::Pallet::::block_number(), + submission_deposit: None, + decision_deposit: None, + deciding: None, + tally: Default::default(), + in_queue: false, + alarm: Self::ensure_alarm_at(&mut status, index, now + 1_000_000u32.into()), + }; + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + Ok(index) + } + + #[cfg(feature = "runtime-benchmarks")] + fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { + let mut status = Self::ensure_ongoing(index).map_err(|_| ())?; + Self::ensure_no_alarm(&mut status); + Self::note_one_fewer_deciding(status.track, track); + let now = frame_system::Pallet::::block_number(); + let info = if approved { + ReferendumInfo::Approved( + now, + status.submission_deposit, + status.decision_deposit, + ) + } else { + ReferendumInfo::Rejected( + now, + status.submission_deposit, + status.decision_deposit, + ) + }; + ReferendumInfoFor::::insert(index, info); + Ok(()) + } } impl Pallet { diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index c7dce457092ce..80566ce2d5632 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -143,4 +143,17 @@ pub trait Polling { index: Self::Index, f: impl FnOnce(PollStatus<&mut Tally, Self::Moment, Self::Class>) -> Result, ) -> Result; + + /// 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; + + /// 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<(), ()>; } From 0880fdce1ad4b3a94765540180b5ae7158c3448d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 8 Jan 2022 15:44:34 +0100 Subject: [PATCH 48/66] Fixes --- frame/referenda/src/benchmarking.rs | 6 ++--- frame/referenda/src/lib.rs | 37 ++++++++++++++++------------- frame/referenda/src/mock.rs | 4 ++-- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index cfa5cc7705fb4..76a8173f16c9a 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -107,7 +107,7 @@ fn make_passing_after(index: ReferendumIndex, period_portion: Perbill let turnout = info::(index).min_turnout.threshold(period_portion); let approval = info::(index).min_approval.threshold(period_portion); Referenda::::access_poll(index, |status| { - if let PollStatus::Ongoing(tally) = status { + if let PollStatus::Ongoing(tally, ..) = status { *tally = T::Tally::from_requirements(turnout, approval); } }); @@ -115,7 +115,7 @@ fn make_passing_after(index: ReferendumIndex, period_portion: Perbill fn make_passing(index: ReferendumIndex) { Referenda::::access_poll(index, |status| { - if let PollStatus::Ongoing(tally) = status { + if let PollStatus::Ongoing(tally, ..) = status { *tally = T::Tally::unanimity(); } }); @@ -123,7 +123,7 @@ fn make_passing(index: ReferendumIndex) { fn make_failing(index: ReferendumIndex) { Referenda::::access_poll(index, |status| { - if let PollStatus::Ongoing(tally) = status { + if let PollStatus::Ongoing(tally, ..) = status { *tally = T::Tally::default(); } }); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index df0d5a44632d4..4b174efbf159c 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -432,11 +432,10 @@ pub mod pallet { pub fn cancel(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::CancelOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; - let track = Self::track(status.track).ok_or(Error::::BadTrack)?; if let Some((_, last_alarm)) = status.alarm { let _ = T::Scheduler::cancel(last_alarm); } - Self::note_one_fewer_deciding(status.track, track); + Self::note_one_fewer_deciding(status.track); Self::deposit_event(Event::::Cancelled { index, tally: status.tally }); let info = ReferendumInfo::Cancelled( frame_system::Pallet::::block_number(), @@ -457,11 +456,10 @@ pub mod pallet { pub fn kill(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::KillOrigin::ensure_origin(origin)?; let status = Self::ensure_ongoing(index)?; - let track = Self::track(status.track).ok_or(Error::::BadTrack)?; if let Some((_, last_alarm)) = status.alarm { let _ = T::Scheduler::cancel(last_alarm); } - Self::note_one_fewer_deciding(status.track, track); + Self::note_one_fewer_deciding(status.track); Self::deposit_event(Event::::Killed { index, tally: status.tally }); Self::slash_deposit(Some(status.submission_deposit.clone())); Self::slash_deposit(status.decision_deposit.clone()); @@ -583,25 +581,30 @@ impl Polling for Pallet { } #[cfg(feature = "runtime-benchmarks")] - fn create_ongoing(period: Self::Moment, class: Self::Class) -> Result { + fn create_ongoing(class: Self::Class) -> Result { let index = ReferendumCount::::mutate(|x| { let r = *x; *x += 1; r }); - let status = ReferendumStatusOf:: { - class, - origin: RawOrigin::Nobody.into(), - proposal_hash: BlakeTwo256::hash_of(index), - enactment: AtOrAfter::After(0), - submitted: frame_system::Pallet::::block_number(), - submission_deposit: None, + let now = frame_system::Pallet::::block_number(); + let dummy_account_id = + codec::Decode::decode(&mut sp_runtime::traits::TrailingZeroInput::new(&b"dummy"[..])) + .expect("infinite length input; no invalid inputs for type; qed"); + let mut status = ReferendumStatusOf:: { + track: class, + origin: frame_support::dispatch::RawOrigin::Root.into(), + proposal_hash: ::hash_of(&index), + enactment: AtOrAfter::After(Zero::zero()), + submitted: now, + submission_deposit: Deposit { who: dummy_account_id, amount: Zero::zero() }, decision_deposit: None, deciding: None, tally: Default::default(), in_queue: false, - alarm: Self::ensure_alarm_at(&mut status, index, now + 1_000_000u32.into()), + alarm: None, }; + Self::ensure_alarm_at(&mut status, index, sp_runtime::traits::Bounded::max_value()); ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); Ok(index) } @@ -610,7 +613,7 @@ impl Polling for Pallet { fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { let mut status = Self::ensure_ongoing(index).map_err(|_| ())?; Self::ensure_no_alarm(&mut status); - Self::note_one_fewer_deciding(status.track, track); + Self::note_one_fewer_deciding(status.track); let now = frame_system::Pallet::::block_number(); let info = if approved { ReferendumInfo::Approved( @@ -770,7 +773,7 @@ impl Pallet { /// Schedule a call to `one_fewer_deciding` function via the dispatchable /// `defer_one_fewer_deciding`. We could theoretically call it immediately (and it would be /// overall more efficient), however the weights become rather less easy to measure. - fn note_one_fewer_deciding(track: TrackIdOf, _track_info: &TrackInfoOf) { + fn note_one_fewer_deciding(track: TrackIdOf) { // Set an alarm call for the next block to nudge the track along. let now = frame_system::Pallet::::block_number(); let next_block = now + One::one(); @@ -919,7 +922,7 @@ impl Pallet { Some(t) if now >= t => { // Passed! Self::ensure_no_alarm(&mut status); - Self::note_one_fewer_deciding(status.track, track); + Self::note_one_fewer_deciding(status.track); let (desired, call_hash) = (status.enactment, status.proposal_hash); Self::schedule_enactment( index, @@ -955,7 +958,7 @@ impl Pallet { if now >= deciding.since.saturating_add(track.decision_period) { // Failed! Self::ensure_no_alarm(&mut status); - Self::note_one_fewer_deciding(status.track, track); + Self::note_one_fewer_deciding(status.track); Self::deposit_event(Event::::Rejected { index, tally: status.tally }); return ( ReferendumInfo::Rejected( diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index cac0f36d602b1..d2c09b9c39163 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -23,7 +23,7 @@ use codec::{Decode, Encode}; use frame_support::{ assert_ok, ord_parameter_types, parameter_types, traits::{ - ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, + ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, Polling, PreimageRecipient, SortedMembers, }, weights::Weight, @@ -335,7 +335,7 @@ pub fn tally(r: ReferendumIndex) -> Tally { } pub fn set_tally(index: ReferendumIndex, ayes: u32, nays: u32) { - >::access_poll(index, |status| { + >::access_poll(index, |status| { let tally = status.ensure_ongoing().unwrap().0; tally.ayes = ayes; tally.nays = nays; From b7a8ebeab8b614758eac30b421b5d7296d2cc55c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 8 Jan 2022 16:53:11 +0100 Subject: [PATCH 49/66] Test harness --- frame/conviction-voting/src/benchmarking.rs | 33 +++++---------------- frame/conviction-voting/src/tests.rs | 8 ++--- frame/conviction-voting/src/types.rs | 4 +-- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 677911cf9b20a..9242f477c6023 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -44,36 +44,13 @@ fn funded_account(name: &'static str, index: u32) -> T::AccountId { caller } -fn add_referendum(n: u32) -> Result { - let proposal_hash: T::Hash = T::Hashing::hash_of(&n); - let vote_threshold = VoteThreshold::SimpleMajority; - - ConvictionVoting::::inject_referendum( - T::LaunchPeriod::get(), - proposal_hash, - vote_threshold, - 0u32.into(), - ); - let referendum_index: PollIndex = PollCount::::get() - 1; - T::Scheduler::schedule_named( - (DEMOCRACY_ID, referendum_index).encode(), - DispatchTime::At(2u32.into()), - None, - 63, - frame_system::RawOrigin::Root.into(), - Call::enact_proposal { proposal_hash, index: referendum_index }.into(), - ) - .map_err(|_| "failed to schedule named")?; - Ok(referendum_index) -} - fn account_vote(b: BalanceOf) -> AccountVote> { let v = Vote { aye: true, conviction: Conviction::Locked1x }; AccountVote::Standard { vote: v, balance: b } } -benchmarks! { + /* vote_new { let r in 1 .. MAX_REFERENDUMS; @@ -337,7 +314,13 @@ benchmarks! { _ => return Err("Votes are not direct".into()), }; assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); - } + }*/ +benchmarks! { + unlock { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let class = ::Polls::classes().into_iter().next().unwrap(); + }: _(RawOrigin::Signed(caller.clone()), class, caller.clone()) impl_benchmark_test_suite!( ConvictionVoting, diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index f6467d08bc0d2..90a7463849c2c 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -170,10 +170,10 @@ impl Polling> for TestPolls { } #[cfg(feature = "runtime-benchmarks")] - fn create_ongoing(period: Self::Moment, class: Self::Class) -> Result { + fn create_ongoing(class: Self::Class) -> Result { let mut polls = Polls::get(); let i = polls.keys().rev().next().map_or(0, |x| x + 1); - polls.insert(i, (class, Tally::default())); + polls.insert(i, Ongoing(Tally::default(), class)); Polls::set(polls); Ok(i) } @@ -182,11 +182,11 @@ impl Polling> for TestPolls { fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { let mut polls = Polls::get(); match polls.get(&index) { - Some(Ongoing(t, _)) => {}, + Some(Ongoing(..)) => {}, _ => return Err(()), } let now = frame_system::Pallet::::block_number(); - polls.insert(i, Completed(now, approved)); + polls.insert(index, Completed(now, approved)); Polls::set(polls); Ok(()) } diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 94602f61c21ed..06549c41fbceb 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -61,12 +61,12 @@ impl< Perbill::from_rational(self.ayes, self.ayes.saturating_add(self.nays)) } - #[cfg(features = "runtime-benchmarks")] + #[cfg(feature = "runtime-benchmarks")] fn unanimity() -> Self { Self { ayes: Total::get(), nays: Zero::zero(), turnout: Total::get(), dummy: PhantomData } } - #[cfg(features = "runtime-benchmarks")] + #[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); From d9fb5502cbb74177d11c39923e536c3fe5875264 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 9 Jan 2022 12:53:21 +0100 Subject: [PATCH 50/66] Test harness --- frame/conviction-voting/src/benchmarking.rs | 70 +++++++++++++-------- frame/conviction-voting/src/lib.rs | 1 + 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 9242f477c6023..a84a7686d99a5 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -19,6 +19,7 @@ use super::*; +use std::collections::BTreeMap; use frame_benchmarking::{account, benchmarks, whitelist_account}; use frame_support::{ assert_noop, assert_ok, @@ -38,6 +39,24 @@ fn assert_last_event(generic_event: ::Event) { frame_system::Pallet::::assert_last_event(generic_event.into()); } +/// Fill all classes as much as possible up to `MaxVotes` and return the Class with the most votes +/// ongoing. +fn fill_voting() -> (ClassOf, BTreeMap, Vec>>) { + let max: Option<(ClassOf, u32)> = None; + let r: TreeMap, Vec>> = BTreeMap::new(); + for class in T::Polls::classes().into_iter() { + let mut counter = 0; + for i in 0..T::MaxVotes::get() { + match T::Polls::create_ongoing(class) { + Ok(i) => r.entry(class).or_default().push(i), + Err(()) => break, + } + } + } + let c = r.iter().map(|(ref c, ref v)| (v.len(), c.clone())).max(|(l, _)| l).unwrap().1; + (c, r) +} + fn funded_account(name: &'static str, index: u32) -> T::AccountId { let caller: T::AccountId = account(name, index, SEED); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); @@ -50,35 +69,49 @@ fn account_vote(b: BalanceOf) -> AccountVote> { AccountVote::Standard { vote: v, balance: b } } - /* +benchmarks! { vote_new { - let r in 1 .. MAX_REFERENDUMS; - let caller = funded_account::("caller", 0); let account_vote = account_vote::(100u32.into()); - // We need to create existing direct votes - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + let (class, all_polls) = fill_voting::(); + let polls = all_polls[&class]; + let r = polls.len() - 1; + // We need to create existing votes + for i in polls.iter().skip(1) { + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, account_vote.clone())?; } - let votes = match VotingOf::::get(&caller) { + let votes = match VotingFor::::get(&caller, &class) { Voting::Direct { votes, .. } => votes, _ => return Err("Votes are not direct".into()), }; assert_eq!(votes.len(), r as usize, "Votes were not recorded."); - let referendum_index = add_referendum::(r)?; whitelist_account!(caller); - }: vote(RawOrigin::Signed(caller.clone()), referendum_index, account_vote) + let index = polls[0]; + }: vote(RawOrigin::Signed(caller.clone()), *index, account_vote) verify { - let votes = match VotingOf::::get(&caller) { + let votes = match VotingFor::::get(&caller, &class) { Voting::Direct { votes, .. } => votes, _ => return Err("Votes are not direct".into()), }; assert_eq!(votes.len(), (r + 1) as usize, "Vote was not recorded."); } + unlock { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let class = ::Polls::classes().into_iter().next().unwrap(); + }: _(RawOrigin::Signed(caller.clone()), class, caller.clone()) + + impl_benchmark_test_suite!( + ConvictionVoting, + crate::tests::new_test_ext(), + crate::tests::Test + ); +} + + /* vote_existing { let r in 1 .. MAX_REFERENDUMS; @@ -314,17 +347,4 @@ fn account_vote(b: BalanceOf) -> AccountVote> { _ => return Err("Votes are not direct".into()), }; assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); - }*/ -benchmarks! { - unlock { - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - let class = ::Polls::classes().into_iter().next().unwrap(); - }: _(RawOrigin::Signed(caller.clone()), class, caller.clone()) - - impl_benchmark_test_suite!( - ConvictionVoting, - crate::tests::new_test_ext(), - crate::tests::Test - ); -} + }*/ \ No newline at end of file diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 761561d452d5b..155a683cfc754 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -72,6 +72,7 @@ type DelegatingOf = Delegating< >; type TallyOf = Tally, ::MaxTurnout>; type PollIndexOf = <::Polls as Polling>>::Index; +type IndexOf = <::Polls as Polling>>::Index; type ClassOf = <::Polls as Polling>>::Class; #[frame_support::pallet] From 6f9e9d61cdc89bf09eda282eae3682d9ef4a284b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 9 Jan 2022 15:21:20 +0100 Subject: [PATCH 51/66] Benchmarks for Conviction=Voting --- Cargo.lock | 1 + frame/conviction-voting/Cargo.toml | 1 + frame/conviction-voting/src/benchmarking.rs | 381 ++++++++------------ frame/conviction-voting/src/lib.rs | 15 +- frame/conviction-voting/src/tests.rs | 10 + frame/referenda/src/lib.rs | 8 + frame/support/src/traits/voting.rs | 7 + 7 files changed, 185 insertions(+), 238 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba08d0748cc05..d7731b18d04d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5566,6 +5566,7 @@ dependencies = [ name = "pallet-conviction-voting" version = "4.0.0-dev" dependencies = [ + "assert_matches", "frame-benchmarking", "frame-support", "frame-system", diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index e32f524381ecb..99b0e231d48e5 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -24,6 +24,7 @@ sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../pr 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 = "4.1.0-dev", path = "../../primitives/core" } diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index a84a7686d99a5..7d59d59373d77 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -21,39 +21,28 @@ use super::*; use std::collections::BTreeMap; use frame_benchmarking::{account, benchmarks, whitelist_account}; -use frame_support::{ - assert_noop, assert_ok, - codec::Decode, - traits::{ - schedule::DispatchTime, Currency, EnsureOrigin, Get, OnInitialize, UnfilteredDispatchable, - }, -}; -use frame_system::{Pallet as System, RawOrigin}; -use sp_runtime::traits::{BadOrigin, Bounded, One}; +use frame_support::traits::{Currency, Get, fungible}; +use assert_matches::assert_matches; +use frame_support::dispatch::RawOrigin; +use sp_runtime::traits::Bounded; use crate::Pallet as ConvictionVoting; const SEED: u32 = 0; -fn assert_last_event(generic_event: ::Event) { - frame_system::Pallet::::assert_last_event(generic_event.into()); -} - /// Fill all classes as much as possible up to `MaxVotes` and return the Class with the most votes /// ongoing. fn fill_voting() -> (ClassOf, BTreeMap, Vec>>) { - let max: Option<(ClassOf, u32)> = None; - let r: TreeMap, Vec>> = BTreeMap::new(); + let mut r = BTreeMap::, Vec>>::new(); for class in T::Polls::classes().into_iter() { - let mut counter = 0; - for i in 0..T::MaxVotes::get() { - match T::Polls::create_ongoing(class) { - Ok(i) => r.entry(class).or_default().push(i), + 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().map(|(ref c, ref v)| (v.len(), c.clone())).max(|(l, _)| l).unwrap().1; + let c = r.iter().max_by_key(|(_, ref v)| v.len()).unwrap().0.clone(); (c, r) } @@ -72,279 +61,213 @@ fn account_vote(b: BalanceOf) -> AccountVote> { benchmarks! { vote_new { let caller = funded_account::("caller", 0); + whitelist_account!(caller); let account_vote = account_vote::(100u32.into()); let (class, all_polls) = fill_voting::(); - let polls = all_polls[&class]; + let polls = &all_polls[&class]; let r = polls.len() - 1; // We need to create existing votes for i in polls.iter().skip(1) { ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, account_vote.clone())?; } let votes = match VotingFor::::get(&caller, &class) { - Voting::Direct { votes, .. } => votes, + Voting::Casting(Casting { votes, .. }) => votes, _ => return Err("Votes are not direct".into()), }; assert_eq!(votes.len(), r as usize, "Votes were not recorded."); - whitelist_account!(caller); let index = polls[0]; - }: vote(RawOrigin::Signed(caller.clone()), *index, account_vote) + }: vote(RawOrigin::Signed(caller.clone()), index, account_vote) verify { - let votes = match VotingFor::::get(&caller, &class) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r + 1) as usize, "Vote was not recorded."); + assert_matches!( + VotingFor::::get(&caller, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == (r + 1) as usize + ); } - unlock { + vote_existing { let caller = funded_account::("caller", 0); whitelist_account!(caller); - let class = ::Polls::classes().into_iter().next().unwrap(); - }: _(RawOrigin::Signed(caller.clone()), class, caller.clone()) + let old_account_vote = account_vote::(100u32.into()); - impl_benchmark_test_suite!( - ConvictionVoting, - crate::tests::new_test_ext(), - crate::tests::Test - ); -} + let (class, all_polls) = fill_voting::(); + let polls = &all_polls[&class]; + let r = polls.len(); + // We need to create existing votes + for i in polls.iter() { + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote.clone())?; + } + let votes = match VotingFor::::get(&caller, &class) { + Voting::Casting(Casting { votes, .. }) => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r, "Votes were not recorded."); - /* - vote_existing { - let r in 1 .. MAX_REFERENDUMS; + let new_account_vote = account_vote::(200u32.into()); + let index = polls[0]; + }: vote(RawOrigin::Signed(caller.clone()), index, new_account_vote) + verify { + assert_matches!( + VotingFor::::get(&caller, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize + ); + } + remove_vote { let caller = funded_account::("caller", 0); - let account_vote = account_vote::(100u32.into()); + whitelist_account!(caller); + let old_account_vote = account_vote::(100u32.into()); - // We need to create existing direct votes - for i in 0 ..=r { - let ref_idx = add_referendum::(i)?; - ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + let (class, all_polls) = fill_voting::(); + let polls = &all_polls[&class]; + let r = polls.len(); + // We need to create existing votes + for i in polls.iter() { + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote.clone())?; } - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, + let votes = match VotingFor::::get(&caller, &class) { + Voting::Casting(Casting { votes, .. }) => votes, _ => return Err("Votes are not direct".into()), }; - assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded."); + assert_eq!(votes.len(), r, "Votes were not recorded."); - // Change vote from aye to nay - let nay = Vote { aye: false, conviction: Conviction::Locked1x }; - let new_vote = AccountVote::Standard { vote: nay, balance: 1000u32.into() }; - let referendum_index = ConvictionVoting::::referendum_count() - 1; + let index = polls[0]; + }: _(RawOrigin::Signed(caller.clone()), Some(class.clone()), index) + verify { + assert_matches!( + VotingFor::::get(&caller, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == (r - 1) as usize + ); + } - // This tests when a user changes a vote + remove_other_vote { + let caller = funded_account::("caller", 0); + let voter = funded_account::("caller", 0); whitelist_account!(caller); - }: vote(RawOrigin::Signed(caller.clone()), referendum_index, new_vote) - verify { - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, + let old_account_vote = account_vote::(100u32.into()); + + let (class, all_polls) = fill_voting::(); + let polls = &all_polls[&class]; + let r = polls.len(); + // We need to create existing votes + for i in polls.iter() { + ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, old_account_vote.clone())?; + } + let votes = match VotingFor::::get(&caller, &class) { + Voting::Casting(Casting { votes, .. }) => votes, _ => return Err("Votes are not direct".into()), }; - assert_eq!(votes.len(), (r + 1) as usize, "Vote was incorrectly added"); - let referendum_info = ConvictionVoting::::referendum_info(referendum_index) - .ok_or("referendum doesn't exist")?; - let tally = match referendum_info { - PollInfo::Ongoing(r) => r.tally, - _ => return Err("referendum not ongoing".into()), - }; - assert_eq!(tally.nays, 1000u32.into(), "changed vote was not recorded"); + 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::::get(&voter, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == (r - 1) as usize + ); } delegate { - let r in 1 .. MAX_REFERENDUMS; + let r in 0 .. T::MaxVotes::get().min(T::Polls::max_ongoing().1); + + let all_polls = fill_voting::().1; + let class = T::Polls::max_ongoing().0; + let polls = &all_polls[&class]; + let voter = funded_account::("voter", 0); + let caller = funded_account::("caller", 0); + whitelist_account!(caller); - let initial_balance: BalanceOf = 100u32.into(); let delegated_balance: BalanceOf = 1000u32.into(); + let delegate_vote = account_vote::(delegated_balance); - let caller = funded_account::("caller", 0); - // Caller will initially delegate to `old_delegate` - let old_delegate: T::AccountId = funded_account::("old_delegate", r); - ConvictionVoting::::delegate( - RawOrigin::Signed(caller.clone()).into(), - old_delegate.clone(), - Conviction::Locked1x, - delegated_balance, - )?; - let (target, balance) = match VotingOf::::get(&caller) { - Voting::Delegating { target, balance, .. } => (target, balance), - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(target, old_delegate, "delegation target didn't work"); - assert_eq!(balance, delegated_balance, "delegation balance didn't work"); - // Caller will now switch to `new_delegate` - let new_delegate: T::AccountId = funded_account::("new_delegate", r); - let account_vote = account_vote::(initial_balance); - // We need to create existing direct votes for the `new_delegate` - for i in 0..r { - let ref_idx = add_referendum::(i)?; - ConvictionVoting::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote.clone())?; + // We need to create existing delegations + for i in polls.iter().take(r as usize) { + ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote.clone())?; } - let votes = match VotingOf::::get(&new_delegate) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Votes were not recorded."); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone()), new_delegate.clone(), Conviction::Locked1x, delegated_balance) + assert_matches!( + VotingFor::::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 { - let (target, balance) = match VotingOf::::get(&caller) { - Voting::Delegating { target, balance, .. } => (target, balance), - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(target, new_delegate, "delegation target didn't work"); - assert_eq!(balance, delegated_balance, "delegation balance didn't work"); - let delegations = match VotingOf::::get(&new_delegate) { - Voting::Direct { delegations, .. } => delegations, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(delegations.capital, delegated_balance, "delegation was not recorded."); + assert_matches!(VotingFor::::get(&caller, &class), Voting::Delegating(_)); } undelegate { - let r in 1 .. MAX_REFERENDUMS; + let r in 0 .. T::MaxVotes::get().min(T::Polls::max_ongoing().1); + + let all_polls = fill_voting::().1; + let class = T::Polls::max_ongoing().0; + let polls = &all_polls[&class]; + let voter = funded_account::("voter", 0); + let caller = funded_account::("caller", 0); + whitelist_account!(caller); - let initial_balance: BalanceOf = 100u32.into(); let delegated_balance: BalanceOf = 1000u32.into(); + let delegate_vote = account_vote::(delegated_balance); - let caller = funded_account::("caller", 0); - // Caller will delegate - let the_delegate: T::AccountId = funded_account::("delegate", r); ConvictionVoting::::delegate( RawOrigin::Signed(caller.clone()).into(), - the_delegate.clone(), + class.clone(), + voter.clone(), Conviction::Locked1x, delegated_balance, )?; - let (target, balance) = match VotingOf::::get(&caller) { - Voting::Delegating { target, balance, .. } => (target, balance), - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(target, the_delegate, "delegation target didn't work"); - assert_eq!(balance, delegated_balance, "delegation balance didn't work"); - // We need to create votes direct votes for the `delegate` - let account_vote = account_vote::(initial_balance); - for i in 0..r { - let ref_idx = add_referendum::(i)?; - ConvictionVoting::::vote( - RawOrigin::Signed(the_delegate.clone()).into(), - ref_idx, - account_vote.clone() - )?; + + // We need to create delegations + for i in polls.iter().take(r as usize) { + ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote.clone())?; } - let votes = match VotingOf::::get(&the_delegate) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Votes were not recorded."); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone())) + assert_matches!( + VotingFor::::get(&voter, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize + ); + assert_matches!(VotingFor::::get(&caller, &class), Voting::Delegating(_)); + }: _(RawOrigin::Signed(caller.clone()), class.clone()) verify { - // Voting should now be direct - match VotingOf::::get(&caller) { - Voting::Direct { .. } => (), - _ => return Err("undelegation failed".into()), - } + assert_matches!(VotingFor::::get(&caller, &class), Voting::Casting(_)); } - // Test when unlock will set a new value unlock { - let r in 1 .. MAX_REFERENDUMS; - - let locker = funded_account::("locker", 0); - // Populate votes so things are locked - let base_balance: BalanceOf = 100u32.into(); - let small_vote = account_vote::(base_balance); - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - ConvictionVoting::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; - } - - // Create a big vote so lock increases - let big_vote = account_vote::(base_balance * 10u32.into()); - let referendum_index = add_referendum::(r)?; - ConvictionVoting::::vote(RawOrigin::Signed(locker.clone()).into(), referendum_index, big_vote)?; - - let votes = match VotingOf::::get(&locker) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded."); - - let voting = VotingOf::::get(&locker); - assert_eq!(voting.locked_balance(), base_balance * 10u32.into()); - - ConvictionVoting::::remove_vote(RawOrigin::Signed(locker.clone()).into(), referendum_index)?; - let caller = funded_account::("caller", 0); whitelist_account!(caller); - }: unlock(RawOrigin::Signed(caller), locker.clone()) - verify { - let votes = match VotingOf::::get(&locker) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Vote was not removed"); + let normal_account_vote = account_vote::(100u32.into()); + let big_account_vote = account_vote::(200u32.into()); - let voting = VotingOf::::get(&locker); - // Note that we may want to add a `get_lock` api to actually verify - assert_eq!(voting.locked_balance(), base_balance); - } + let orig_usable = >::reducible_balance(&caller, false); - remove_vote { - let r in 1 .. MAX_REFERENDUMS; + // Fill everything up to the max by filling all classes with votes and voting on them all. + let (class, all_polls) = fill_voting::(); + for (class, polls) in all_polls.iter() { + for i in polls.iter() { + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, normal_account_vote.clone())?; + } + } + assert_eq!(orig_usable, >::reducible_balance(&caller, false) + 100u32.into()); - let caller = funded_account::("caller", 0); - let account_vote = account_vote::(100u32.into()); + let polls = &all_polls[&class]; - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; - } + // 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::::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote.clone())?; + assert_eq!(orig_usable, >::reducible_balance(&caller, false) + 200u32.into()); - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Votes not created"); + // Remove the vote + ConvictionVoting::::remove_vote(RawOrigin::Signed(caller.clone()).into(), Some(class.clone()), polls[0])?; - let referendum_index = r - 1; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone()), referendum_index) + // We can now unlock on `class` from 200 to 100... + }: _(RawOrigin::Signed(caller.clone()), class, caller.clone()) verify { - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); + assert_eq!(orig_usable, >::reducible_balance(&caller, false) + 100u32.into()); } - // Worst case is when target == caller and referendum is ongoing - remove_other_vote { - let r in 1 .. MAX_REFERENDUMS; - - let caller = funded_account::("caller", r); - let account_vote = account_vote::(100u32.into()); - - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; - } - - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), r as usize, "Votes not created"); - - let referendum_index = r - 1; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone()), caller.clone(), referendum_index) - verify { - let votes = match VotingOf::::get(&caller) { - Voting::Direct { votes, .. } => votes, - _ => return Err("Votes are not direct".into()), - }; - assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); - }*/ \ No newline at end of file + impl_benchmark_test_suite!( + ConvictionVoting, + crate::tests::new_test_ext(), + crate::tests::Test + ); +} diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 155a683cfc754..00cddf8fff794 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -29,7 +29,7 @@ use frame_support::{ ensure, traits::{ Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling, - ReservableCurrency, WithdrawReasons, + ReservableCurrency, WithdrawReasons, fungible, }, }; use sp_runtime::{ @@ -72,6 +72,7 @@ type DelegatingOf = Delegating< >; type TallyOf = Tally, ::MaxTurnout>; type PollIndexOf = <::Polls as Polling>>::Index; +#[cfg(feature = "runtime-benchmarks")] type IndexOf = <::Polls as Polling>>::Index; type ClassOf = <::Polls as Polling>>::Class; @@ -94,7 +95,8 @@ pub mod pallet { type WeightInfo: WeightInfo; /// Currency type with which voting happens. type Currency: ReservableCurrency - + LockableCurrency; + + LockableCurrency + + fungible::Inspect; /// The implementation of the logic which conducts polls. type Polls: Polling, Votes = BalanceOf, Moment = Self::BlockNumber>; @@ -518,13 +520,8 @@ impl Pallet { prior: Default::default(), })); match old { - Voting::Delegating(Delegating { balance, target, conviction, delegations, mut prior, .. }) => { - // remove any delegation votes to our current target. - Self::reduce_upstream_delegation(&target, &class, conviction.votes(balance)); - let now = frame_system::Pallet::::block_number(); - let lock_periods = conviction.lock_periods().into(); - prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); - voting.set_common(delegations, prior); + Voting::Delegating(Delegating { .. }) => { + Err(Error::::AlreadyDelegating)? }, Voting::Casting(Casting { votes, delegations, prior }) => { // here we just ensure that we're currently idling with no votes recorded. diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 90a7463849c2c..390ff0a8b5778 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -424,6 +424,7 @@ fn classwise_delegation_works() { ].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)), @@ -433,6 +434,9 @@ fn classwise_delegation_works() { ].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)); @@ -451,12 +455,15 @@ fn classwise_delegation_works() { 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); @@ -479,6 +486,7 @@ fn redelegation_after_vote_ending_should_keep_lock() { 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)); @@ -534,7 +542,9 @@ fn lock_amalgamation_valid_with_multiple_removed_votes() { 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); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 4b174efbf159c..5d82a25c61b05 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -631,6 +631,14 @@ impl Polling for Pallet { ReferendumInfoFor::::insert(index, info); Ok(()) } + + #[cfg(feature = "runtime-benchmarks")] + fn max_ongoing() -> (Self::Class, u32) { + let r = T::Tracks::tracks().iter() + .max_by_key(|(id, info)| info.max_deciding) + .expect("Always one class"); + (r.0.clone(), r.1.max_deciding) + } } impl Pallet { diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 80566ce2d5632..53b92c8b420fa 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -156,4 +156,11 @@ pub trait Polling { /// 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()) + } } From 61d22ff788d6de89fd9c60903bf85b9672e02240 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 9 Jan 2022 16:44:33 +0100 Subject: [PATCH 52/66] Benchmarking pipeline complete --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 8 +- bin/node/runtime/src/lib.rs | 57 ++---- frame/conviction-voting/src/benchmarking.rs | 17 +- frame/conviction-voting/src/lib.rs | 3 +- frame/conviction-voting/src/tests.rs | 9 +- frame/conviction-voting/src/types.rs | 11 +- frame/conviction-voting/src/weights.rs | 206 ++++++++++++-------- frame/referenda/src/lib.rs | 2 +- frame/support/src/traits.rs | 2 +- frame/support/src/traits/tokens/currency.rs | 10 + frame/support/src/traits/voting.rs | 1 + 12 files changed, 184 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7731b18d04d4..bc8187219258d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4767,6 +4767,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", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index a453ed4ae5dd9..5d8e2878bef61 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -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 = "4.0.0-dev", 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" } @@ -83,9 +84,7 @@ pallet-proxy = { version = "4.0.0-dev", default-features = false, path = "../../ 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-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 = { 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" } @@ -126,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", @@ -194,6 +194,7 @@ 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", @@ -237,6 +238,7 @@ 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", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 73b2274b3aa66..f5b1b0ca91dda 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -694,6 +694,20 @@ 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; + type Event = Event; + type Currency = Balances; + type VoteLockingPeriod = VoteLockingPeriod; + type MaxVotes = ConstU32<512>; + type MaxTurnout = frame_support::traits::TotalIssuanceOf; + type Polls = Referenda; +} + parameter_types! { pub const AlarmInterval: BlockNumber = 1; pub const SubmissionDeposit: Balance = 100 * DOLLARS; @@ -739,42 +753,8 @@ impl pallet_referenda::TracksInfo for TracksInfo { } } -// TODO: Replace with conviction voting tally. -use scale_info::TypeInfo; -#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, Default)] -pub struct Tally { - pub ayes: u32, - pub nays: u32, -} - -impl frame_support::traits::VoteTally 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 } - } -} - impl pallet_referenda::Config for Runtime { - type WeightInfo = pallet_referenda::weights::SubstrateWeight; + type WeightInfo = pallet_referenda::weights::SubstrateWeight; type Call = Call; type Event = Event; type Scheduler = Scheduler; @@ -782,8 +762,8 @@ impl pallet_referenda::Config for Runtime { type CancelOrigin = EnsureRoot; type KillOrigin = EnsureRoot; type Slash = (); - type Votes = u32; - type Tally = Tally; + type Votes = pallet_conviction_voting::VotesOf; + type Tally = pallet_conviction_voting::TallyOf; type SubmissionDeposit = SubmissionDeposit; type MaxQueued = ConstU32<100>; type UndecidingTimeout = UndecidingTimeout; @@ -1429,6 +1409,7 @@ construct_runtime!( BagsList: pallet_bags_list, ChildBounties: pallet_child_bounties, Referenda: pallet_referenda, + ConvictionVoting: pallet_conviction_voting, } ); @@ -1783,6 +1764,7 @@ impl_runtime_apis! { list_benchmark!(list, extra, pallet_child_bounties, ChildBounties); list_benchmark!(list, extra, pallet_collective, Council); list_benchmark!(list, extra, pallet_contracts, Contracts); + list_benchmark!(list, extra, pallet_conviction_voting, ConvictionVoting); list_benchmark!(list, extra, pallet_democracy, Democracy); list_benchmark!(list, extra, pallet_election_provider_multi_phase, ElectionProviderMultiPhase); list_benchmark!(list, extra, pallet_elections_phragmen, Elections); @@ -1863,6 +1845,7 @@ impl_runtime_apis! { add_benchmark!(params, batches, pallet_child_bounties, ChildBounties); add_benchmark!(params, batches, pallet_collective, Council); add_benchmark!(params, batches, pallet_contracts, Contracts); + add_benchmark!(params, batches, pallet_conviction_voting, ConvictionVoting); add_benchmark!(params, batches, pallet_democracy, Democracy); add_benchmark!(params, batches, pallet_election_provider_multi_phase, ElectionProviderMultiPhase); add_benchmark!(params, batches, pallet_elections_phragmen, Elections); diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 7d59d59373d77..95ebb73737573 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -19,7 +19,7 @@ use super::*; -use std::collections::BTreeMap; +use sp_std::collections::btree_map::BTreeMap; use frame_benchmarking::{account, benchmarks, whitelist_account}; use frame_support::traits::{Currency, Get, fungible}; use assert_matches::assert_matches; @@ -235,26 +235,27 @@ benchmarks! { unlock { let caller = funded_account::("caller", 0); whitelist_account!(caller); - let normal_account_vote = account_vote::(100u32.into()); - let big_account_vote = account_vote::(200u32.into()); - - let orig_usable = >::reducible_balance(&caller, false); + let normal_account_vote = account_vote::(T::Currency::free_balance(&caller) - 100u32.into()); + let big_account_vote = account_vote::(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::(); + assert!(all_polls.len() > 0); for (class, polls) in all_polls.iter() { + assert!(polls.len() > 0); for i in polls.iter() { ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, normal_account_vote.clone())?; } } - assert_eq!(orig_usable, >::reducible_balance(&caller, false) + 100u32.into()); + let orig_usable = >::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::::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote.clone())?; - assert_eq!(orig_usable, >::reducible_balance(&caller, false) + 200u32.into()); + let now_usable = >::reducible_balance(&caller, false); + assert_eq!(orig_usable - now_usable, 100u32.into()); // Remove the vote ConvictionVoting::::remove_vote(RawOrigin::Signed(caller.clone()).into(), Some(class.clone()), polls[0])?; @@ -262,7 +263,7 @@ benchmarks! { // We can now unlock on `class` from 200 to 100... }: _(RawOrigin::Signed(caller.clone()), class, caller.clone()) verify { - assert_eq!(orig_usable, >::reducible_balance(&caller, false) + 100u32.into()); + assert_eq!(orig_usable, >::reducible_balance(&caller, false)); } impl_benchmark_test_suite!( diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 00cddf8fff794..5d757ac5ee52d 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -70,7 +70,8 @@ type DelegatingOf = Delegating< ::AccountId, ::BlockNumber, >; -type TallyOf = Tally, ::MaxTurnout>; +pub type TallyOf = Tally, ::MaxTurnout>; +pub type VotesOf = BalanceOf; type PollIndexOf = <::Polls as Polling>>::Index; #[cfg(feature = "runtime-benchmarks")] type IndexOf = <::Polls as Polling>>::Index; diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 390ff0a8b5778..987b3d65a6c0c 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -95,13 +95,6 @@ impl pallet_balances::Config for Test { type WeightInfo = (); } -pub struct TotalIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); -impl, A> Get for TotalIssuanceOf { - fn get() -> C::Balance { - C::total_issuance() - } -} - #[derive(Clone, PartialEq, Eq, Debug)] pub enum TestPollState { Ongoing(TallyOf, u8), @@ -198,7 +191,7 @@ impl Config for Test { type VoteLockingPeriod = ConstU64<3>; type MaxVotes = ConstU32<3>; type WeightInfo = (); - type MaxTurnout = TotalIssuanceOf; + type MaxTurnout = frame_support::traits::TotalIssuanceOf; type Polls = TestPolls; } diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 06549c41fbceb..a872202028b24 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -17,11 +17,11 @@ //! Miscellaneous additional datatypes. -use std::marker::PhantomData; +use sp_std::marker::PhantomData; use super::*; use crate::{AccountVote, Conviction, Vote}; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, Codec}; use frame_support::{CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use frame_support::traits::VoteTally; use scale_info::TypeInfo; @@ -32,7 +32,8 @@ use sp_runtime::{ /// Info regarding an ongoing referendum. #[derive(CloneNoBound, DefaultNoBound, PartialEqNoBound, EqNoBound, RuntimeDebugNoBound, TypeInfo, Encode, Decode)] -pub struct Tally { +#[scale_info(skip_type_params(Total))] +pub struct Tally { /// 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. @@ -44,7 +45,7 @@ pub struct Tally, > VoteTally for Tally @@ -75,7 +76,7 @@ impl< } impl< - Votes: Clone + Default + PartialEq + Eq + sp_std::fmt::Debug + Copy + AtLeast32BitUnsigned, + Votes: Clone + Default + PartialEq + Eq + sp_std::fmt::Debug + Copy + AtLeast32BitUnsigned + TypeInfo + Codec, Total: Get, > Tally { /// Create a new tally. diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs index c60784eb949fa..0f8fc2d16f5da 100644 --- a/frame/conviction-voting/src/weights.rs +++ b/frame/conviction-voting/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,26 +15,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_democracy +//! Autogenerated weights for pallet_conviction_voting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-09-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-01-09, STEPS: `10`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/substrate +// ../../../target/release/substrate // benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_democracy -// --extrinsic=* +// --chain +// dev +// --steps +// 10 +// --repeat +// 10 +// --pallet +// pallet-conviction-voting +// --extrinsic +// * +// --raw // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/democracy/src/weights.rs -// --template=./.maintain/frame-weight-template.hbs - +// --output +// ../../../frame/conviction-voting/src/weights.rs +// --template +// ../../../.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -43,118 +49,160 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for pallet_democracy. +/// Weight functions needed for pallet_conviction_voting. pub trait WeightInfo { fn vote_new() -> Weight; fn vote_existing() -> Weight; - fn delegate(r: u32, ) -> Weight; - fn undelegate(r: u32, ) -> 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_democracy using the Substrate node and recommended hardware. +/// Weights for pallet_conviction_voting using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) + // 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 { - (46_406_000 as Weight) + (115_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) + // 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 { - (46_071_000 as Weight) + (229_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) } - // Storage: Democracy VotingOf (r:3 w:3) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // 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 { + (208_000_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 { + (38_000_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 { - (57_783_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_623_000 as Weight).saturating_mul(r as Weight)) + (37_000_000 as Weight) + // Standard Error: 1_204_000 + .saturating_add((28_500_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r 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((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(r as Weight))) } - // Storage: Democracy VotingOf (r:2 w:2) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // 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 { - (26_027_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_593_000 as Weight).saturating_mul(r as Weight)) + (23_833_000 as Weight) + // Standard Error: 1_447_000 + .saturating_add((24_167_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r 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((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - fn remove_vote() -> Weight { - (19_970_000 as Weight) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - fn remove_other_vote() -> Weight { - (20_094_000 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 { - (20_094_000 as Weight) + (56_000_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: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) + // 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 { - (46_406_000 as Weight) + (115_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) + // 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 { - (46_071_000 as Weight) + (229_000_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 { + (208_000_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 { + (38_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - // Storage: Democracy VotingOf (r:3 w:3) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // 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 { - (57_783_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_623_000 as Weight).saturating_mul(r as Weight)) + (37_000_000 as Weight) + // Standard Error: 1_204_000 + .saturating_add((28_500_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r 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((1 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(r as Weight))) } - // Storage: Democracy VotingOf (r:2 w:2) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // 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 { - (26_027_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_593_000 as Weight).saturating_mul(r as Weight)) + (23_833_000 as Weight) + // Standard Error: 1_447_000 + .saturating_add((24_167_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r 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((1 as Weight).saturating_mul(r as Weight))) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - fn remove_vote() -> Weight { - (19_970_000 as Weight) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - fn remove_other_vote() -> Weight { - (20_094_000 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 { - (20_094_000 as Weight) + (56_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } } diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 5d82a25c61b05..2a0ad6132709a 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -635,7 +635,7 @@ impl Polling for Pallet { #[cfg(feature = "runtime-benchmarks")] fn max_ongoing() -> (Self::Class, u32) { let r = T::Tracks::tracks().iter() - .max_by_key(|(id, info)| info.max_deciding) + .max_by_key(|(_, info)| info.max_deciding) .expect("Always one class"); (r.0.clone(), r.1.max_deciding) } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index a868d8878d2c9..3c5a61a414c36 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -23,7 +23,7 @@ pub mod tokens; pub use tokens::{ currency::{ Currency, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency, - VestingSchedule, + VestingSchedule, TotalIssuanceOf, }, fungible, fungibles, imbalance::{Imbalance, OnUnbalanced, SignedImbalance}, diff --git a/frame/support/src/traits/tokens/currency.rs b/frame/support/src/traits/tokens/currency.rs index d5756ee84c47a..92a6e3da3b1a5 100644 --- a/frame/support/src/traits/tokens/currency.rs +++ b/frame/support/src/traits/tokens/currency.rs @@ -21,6 +21,7 @@ use super::{ imbalance::{Imbalance, SignedImbalance}, misc::{Balance, ExistenceRequirement, WithdrawReasons}, }; +use crate::traits::Get; use crate::dispatch::{DispatchError, DispatchResult}; use codec::MaxEncodedLen; use sp_runtime::traits::MaybeSerializeDeserialize; @@ -200,6 +201,15 @@ pub trait Currency { ) -> SignedImbalance; } +/// A non-const `Get` implementation parameterised by a `Currency` impl which provides the result +/// of `total_issuance`. +pub struct TotalIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); +impl, A> Get for TotalIssuanceOf { + fn get() -> C::Balance { + C::total_issuance() + } +} + #[cfg(feature = "std")] impl Currency for () { type Balance = u32; diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 53b92c8b420fa..2929df5bdbab9 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -18,6 +18,7 @@ //! Traits and associated data structures concerned with voting, and moving between tokens and //! votes. +use sp_std::prelude::*; use crate::dispatch::{DispatchError, Parameter}; use codec::HasCompact; use sp_arithmetic::{ From 5dcf899e068c30c0e1609f7c0b652878b8647265 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 9 Jan 2022 16:48:11 +0100 Subject: [PATCH 53/66] Docs --- frame/conviction-voting/src/conviction.rs | 2 +- frame/conviction-voting/src/lib.rs | 6 ++-- frame/conviction-voting/src/tests.rs | 2 +- frame/conviction-voting/src/types.rs | 2 +- frame/conviction-voting/src/vote.rs | 2 +- frame/conviction-voting/src/weights.rs | 44 +++++++++++------------ frame/referenda/src/branch.rs | 2 +- frame/referenda/src/lib.rs | 2 +- frame/referenda/src/mock.rs | 2 +- frame/referenda/src/tests.rs | 2 +- frame/referenda/src/types.rs | 2 +- 11 files changed, 35 insertions(+), 33 deletions(-) diff --git a/frame/conviction-voting/src/conviction.rs b/frame/conviction-voting/src/conviction.rs index b4f24c93bb40f..ba79081a3f549 100644 --- a/frame/conviction-voting/src/conviction.rs +++ b/frame/conviction-voting/src/conviction.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 5d757ac5ee52d..56b9e8c968250 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -1,13 +1,15 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// 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.0sp_runtime::{DispatchResult, traits::One}asp_runtime::{DispatchResult, traits::AtLeast32BitUnsigned} in writing, software +// 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 diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 987b3d65a6c0c..8de88dca7d8e9 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index a872202028b24..fc711a01856f8 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index a8b256a0a54cb..e6e993fed4712 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs index 0f8fc2d16f5da..f1612dddbc6d2 100644 --- a/frame/conviction-voting/src/weights.rs +++ b/frame/conviction-voting/src/weights.rs @@ -69,7 +69,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - (115_000_000 as Weight) + (130_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -79,7 +79,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - (229_000_000 as Weight) + (226_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -87,14 +87,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - (208_000_000 as Weight) + (225_000_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 { - (38_000_000 as Weight) + (45_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -104,9 +104,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn delegate(r: u32, ) -> Weight { - (37_000_000 as Weight) - // Standard Error: 1_204_000 - .saturating_add((28_500_000 as Weight).saturating_mul(r as Weight)) + (43_167_000 as Weight) + // Standard Error: 3_423_000 + .saturating_add((32_000_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)) @@ -116,9 +116,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn undelegate(r: u32, ) -> Weight { - (23_833_000 as Weight) - // Standard Error: 1_447_000 - .saturating_add((24_167_000 as Weight).saturating_mul(r as Weight)) + (27_333_000 as Weight) + // Standard Error: 1_885_000 + .saturating_add((24_000_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)) @@ -128,7 +128,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - (56_000_000 as Weight) + (55_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -142,7 +142,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - (115_000_000 as Weight) + (130_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -152,7 +152,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - (229_000_000 as Weight) + (226_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -160,14 +160,14 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - (208_000_000 as Weight) + (225_000_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 { - (38_000_000 as Weight) + (45_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -177,9 +177,9 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn delegate(r: u32, ) -> Weight { - (37_000_000 as Weight) - // Standard Error: 1_204_000 - .saturating_add((28_500_000 as Weight).saturating_mul(r as Weight)) + (43_167_000 as Weight) + // Standard Error: 3_423_000 + .saturating_add((32_000_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)) @@ -189,9 +189,9 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn undelegate(r: u32, ) -> Weight { - (23_833_000 as Weight) - // Standard Error: 1_447_000 - .saturating_add((24_167_000 as Weight).saturating_mul(r as Weight)) + (27_333_000 as Weight) + // Standard Error: 1_885_000 + .saturating_add((24_000_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)) @@ -201,7 +201,7 @@ impl WeightInfo for () { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - (56_000_000 as Weight) + (55_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } diff --git a/frame/referenda/src/branch.rs b/frame/referenda/src/branch.rs index 9f1f9598a1052..4721ef5c5cc80 100644 --- a/frame/referenda/src/branch.rs +++ b/frame/referenda/src/branch.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 2a0ad6132709a..9be68d04c0173 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index d2c09b9c39163..0d20304f8f7db 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 11d7002c31774..385d8839891f0 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 3fb32a4b5825e..00b45c4219bc3 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); From 1df78f598731223daedc0ade663b50fea95d1614 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 9 Jan 2022 16:57:00 +0100 Subject: [PATCH 54/66] Formatting --- frame/conviction-voting/src/benchmarking.rs | 10 +- frame/conviction-voting/src/lib.rs | 158 ++++++------ frame/conviction-voting/src/tests.rs | 267 ++++++++++++-------- frame/conviction-voting/src/types.rs | 54 +++- frame/conviction-voting/src/vote.rs | 6 +- frame/referenda/src/branch.rs | 18 +- frame/referenda/src/lib.rs | 37 ++- frame/support/src/traits.rs | 2 +- frame/support/src/traits/tokens/currency.rs | 6 +- frame/support/src/traits/voting.rs | 2 +- 10 files changed, 331 insertions(+), 229 deletions(-) diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 95ebb73737573..040287f41bdb5 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -19,12 +19,14 @@ use super::*; -use sp_std::collections::btree_map::BTreeMap; -use frame_benchmarking::{account, benchmarks, whitelist_account}; -use frame_support::traits::{Currency, Get, fungible}; use assert_matches::assert_matches; -use frame_support::dispatch::RawOrigin; +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; diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 56b9e8c968250..d7ebd40e206cf 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -30,8 +30,8 @@ use frame_support::{ ensure, traits::{ - Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling, - ReservableCurrency, WithdrawReasons, fungible, + fungible, Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling, + ReservableCurrency, WithdrawReasons, }, }; use sp_runtime::{ @@ -46,8 +46,8 @@ mod vote; pub mod weights; pub use conviction::Conviction; pub use pallet::*; -pub use types::{Delegations, Tally, UnvoteScope,}; -pub use vote::{AccountVote, Vote, Voting, Casting, Delegating}; +pub use types::{Delegations, Tally, UnvoteScope}; +pub use vote::{AccountVote, Casting, Delegating, Vote, Voting}; pub use weights::WeightInfo; #[cfg(test)] @@ -127,7 +127,8 @@ pub mod pallet { /// 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 = StorageDoubleMap<_, + pub type VotingFor = StorageDoubleMap< + _, Twox64Concat, T::AccountId, Twox64Concat, @@ -140,12 +141,8 @@ pub mod pallet { /// require. The actual amount locked on behalf of this pallet should always be the maximum of /// this list. #[pallet::storage] - pub type ClassLocksFor = StorageMap<_, - Twox64Concat, - T::AccountId, - Vec<(ClassOf, BalanceOf)>, - ValueQuery, - >; + pub type ClassLocksFor = + StorageMap<_, Twox64Concat, T::AccountId, Vec<(ClassOf, BalanceOf)>, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -218,8 +215,8 @@ pub mod pallet { /// 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. + /// - `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 @@ -278,7 +275,11 @@ pub mod pallet { /// /// Weight: `O(R)` with R number of vote of target. #[pallet::weight(T::WeightInfo::unlock())] - pub fn unlock(origin: OriginFor, class: ClassOf, target: T::AccountId) -> DispatchResult { + pub fn unlock( + origin: OriginFor, + class: ClassOf, + target: T::AccountId, + ) -> DispatchResult { ensure_signed(origin)?; Self::update_lock(&class, &target); Ok(()) @@ -332,8 +333,8 @@ pub mod pallet { /// /// 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`. + /// - `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. /// @@ -454,7 +455,11 @@ impl Pallet { } /// Return the number of votes for `who` - fn increase_upstream_delegation(who: &T::AccountId, class: &ClassOf, amount: Delegations>) -> u32 { + fn increase_upstream_delegation( + who: &T::AccountId, + class: &ClassOf, + amount: Delegations>, + ) -> u32 { VotingFor::::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. @@ -478,7 +483,11 @@ impl Pallet { } /// Return the number of votes for `who` - fn reduce_upstream_delegation(who: &T::AccountId, class: &ClassOf, amount: Delegations>) -> u32 { + fn reduce_upstream_delegation( + who: &T::AccountId, + class: &ClassOf, + amount: Delegations>, + ) -> u32 { VotingFor::::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. @@ -514,31 +523,34 @@ impl Pallet { ensure!(who != target, Error::::Nonsense); T::Polls::classes().binary_search(&class).map_err(|_| Error::::BadClass)?; ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); - let votes = VotingFor::::try_mutate(&who, &class, |voting| -> Result { - 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::::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::::AlreadyVoting); - voting.set_common(delegations, prior); - }, - } + let votes = + VotingFor::::try_mutate(&who, &class, |voting| -> Result { + 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::::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::::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) - })?; + 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::::Delegated(who, target)); Ok(votes) } @@ -546,43 +558,43 @@ impl Pallet { /// Attempt to end the current delegation. /// /// Return the number of votes of upstream. - fn try_undelegate( - who: T::AccountId, - class: ClassOf, - ) -> Result { - let votes = VotingFor::::try_mutate(&who, &class, |voting| -> Result { - 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::::block_number(); - let lock_periods = conviction.lock_periods().into(); - prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); - voting.set_common(delegations, prior); - - Ok(votes) - }, - Voting::Casting(_) => Err(Error::::NotDelegating.into()), - } - })?; + fn try_undelegate(who: T::AccountId, class: ClassOf) -> Result { + let votes = + VotingFor::::try_mutate(&who, &class, |voting| -> Result { + 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::::block_number(); + let lock_periods = conviction.lock_periods().into(); + prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); + voting.set_common(delegations, prior); + + Ok(votes) + }, + Voting::Casting(_) => Err(Error::::NotDelegating.into()), + } + })?; Self::deposit_event(Event::::Undelegated(who)); Ok(votes) } fn extend_lock(who: &T::AccountId, class: &ClassOf, amount: BalanceOf) { - ClassLocksFor::::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)), - } + ClassLocksFor::::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, - ); + 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 diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 8de88dca7d8e9..cedb23b02a8db 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -21,8 +21,10 @@ use std::collections::BTreeMap; use super::*; use crate as pallet_conviction_voting; -use frame_support::{parameter_types, assert_ok, assert_noop}; -use frame_support::traits::{ConstU32, Contains, ConstU64, Polling}; +use frame_support::{ + assert_noop, assert_ok, parameter_types, + traits::{ConstU32, ConstU64, Contains, Polling}, +}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -116,10 +118,17 @@ impl Polling> for TestPolls { type Votes = u64; type Moment = u64; type Class = u8; - fn classes() -> Vec { vec![0, 1, 2] } + fn classes() -> Vec { + vec![0, 1, 2] + } fn as_ongoing(index: u8) -> Option<(TallyOf, Self::Class)> { - Polls::get().remove(&index) - .and_then(|x| if let TestPollState::Ongoing(t, c) = x { Some((t, c)) } else { None }) + Polls::get().remove(&index).and_then(|x| { + if let TestPollState::Ongoing(t, c) = x { + Some((t, c)) + } else { + None + } + }) } fn access_poll( index: Self::Index, @@ -128,15 +137,10 @@ impl Polling> for TestPolls { 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) - } + 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 @@ -148,15 +152,10 @@ impl Polling> for TestPolls { 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) - } + 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) @@ -237,15 +236,11 @@ fn nay(amount: u64, conviction: u8) -> AccountVote { } fn tally(index: u8) -> TallyOf { - >>::as_ongoing(index) - .expect("No poll") - .0 + >>::as_ongoing(index).expect("No poll").0 } fn class(index: u8) -> u8 { - >>::as_ongoing(index) - .expect("No poll") - .1 + >>::as_ongoing(index).expect("No poll").1 } #[test] @@ -378,12 +373,16 @@ fn successful_conviction_vote_balance_stays_locked_for_correct_time() { #[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()); + 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)); @@ -400,31 +399,46 @@ fn classwise_delegation_works() { 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()); + 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()); + 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()); + 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)); @@ -433,12 +447,17 @@ fn classwise_delegation_works() { 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!( + 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)); @@ -460,21 +479,24 @@ fn classwise_delegation_works() { 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()); + 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()); + 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()); @@ -490,21 +512,25 @@ fn redelegation_after_vote_ending_should_keep_lock() { #[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()); + 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()); + 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); @@ -659,19 +685,23 @@ fn lock_aggregation_over_different_classes_with_delegation_works() { #[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()); + 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()); + 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)); @@ -702,33 +732,55 @@ fn errors_with_vote_work() { assert_noop!(Voting::vote(Origin::signed(1), 0, aye(10, 0)), Error::::NotOngoing); assert_noop!(Voting::vote(Origin::signed(1), 1, aye(10, 0)), Error::::NotOngoing); assert_noop!(Voting::vote(Origin::signed(1), 2, aye(10, 0)), Error::::NotOngoing); - assert_noop!(Voting::vote(Origin::signed(1), 3, aye(11, 0)), Error::::InsufficientFunds); + assert_noop!( + Voting::vote(Origin::signed(1), 3, aye(11, 0)), + Error::::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::::AlreadyDelegating); + assert_noop!( + Voting::vote(Origin::signed(1), 3, aye(10, 0)), + Error::::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()); + 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::::MaxVotesReached); + assert_noop!( + Voting::vote(Origin::signed(1), 3, aye(10, 0)), + Error::::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::::InsufficientFunds); - assert_noop!(Voting::delegate(Origin::signed(1), 3, 2, Conviction::None, 10), Error::::BadClass); + assert_noop!( + Voting::delegate(Origin::signed(1), 0, 2, Conviction::None, 11), + Error::::InsufficientFunds + ); + assert_noop!( + Voting::delegate(Origin::signed(1), 3, 2, Conviction::None, 10), + Error::::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::::AlreadyVoting); + assert_noop!( + Voting::delegate(Origin::signed(1), 0, 2, Conviction::None, 10), + Error::::AlreadyVoting + ); assert_noop!(Voting::undelegate(Origin::signed(1), 0), Error::::NotDelegating); }); @@ -737,12 +789,21 @@ fn errors_with_delegating_work() { #[test] fn remove_other_vote_works() { new_test_ext().execute_with(|| { - assert_noop!(Voting::remove_other_vote(Origin::signed(2), 1, 0, 3), Error::::NotVoter); + assert_noop!( + Voting::remove_other_vote(Origin::signed(2), 1, 0, 3), + Error::::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::::NoPermission); + assert_noop!( + Voting::remove_other_vote(Origin::signed(2), 1, 0, 3), + Error::::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::::NoPermissionYet); + assert_noop!( + Voting::remove_other_vote(Origin::signed(2), 1, 0, 3), + Error::::NoPermissionYet + ); run_to(7); assert_ok!(Voting::remove_other_vote(Origin::signed(2), 1, 0, 3)); }); diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index fc711a01856f8..e0eb5bf0dc5ef 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -21,9 +21,11 @@ use sp_std::marker::PhantomData; use super::*; use crate::{AccountVote, Conviction, Vote}; -use codec::{Decode, Encode, Codec}; -use frame_support::{CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; -use frame_support::traits::VoteTally; +use codec::{Codec, Decode, Encode}; +use frame_support::{ + traits::VoteTally, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, + RuntimeDebugNoBound, +}; use scale_info::TypeInfo; use sp_runtime::{ traits::{Saturating, Zero}, @@ -31,9 +33,21 @@ use sp_runtime::{ }; /// Info regarding an ongoing referendum. -#[derive(CloneNoBound, DefaultNoBound, PartialEqNoBound, EqNoBound, RuntimeDebugNoBound, TypeInfo, Encode, Decode)] +#[derive( + CloneNoBound, + DefaultNoBound, + PartialEqNoBound, + EqNoBound, + RuntimeDebugNoBound, + TypeInfo, + Encode, + Decode, +)] #[scale_info(skip_type_params(Total))] -pub struct Tally { +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. @@ -45,10 +59,17 @@ pub struct Tally, -> VoteTally - for Tally + Votes: Clone + + Default + + PartialEq + + Eq + + sp_std::fmt::Debug + + Copy + + AtLeast32BitUnsigned + + TypeInfo + + Codec, + Total: Get, + > VoteTally for Tally { fn ayes(&self) -> Votes { self.ayes @@ -76,9 +97,18 @@ impl< } impl< - Votes: Clone + Default + PartialEq + Eq + sp_std::fmt::Debug + Copy + AtLeast32BitUnsigned + TypeInfo + Codec, - Total: Get, -> Tally { + Votes: Clone + + Default + + PartialEq + + Eq + + sp_std::fmt::Debug + + Copy + + AtLeast32BitUnsigned + + TypeInfo + + Codec, + Total: Get, + > Tally +{ /// Create a new tally. pub fn new(vote: Vote, balance: Votes) -> Self { let Delegations { votes, capital } = vote.conviction.votes(balance); diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index e6e993fed4712..8a4b5bd2d048f 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -216,8 +216,10 @@ impl< prior: PriorLock, ) { let (d, p) = match self { - Voting::Casting(Casting { ref mut delegations, ref mut prior, .. }) => (delegations, prior), - Voting::Delegating(Delegating { ref mut delegations, ref mut prior, .. }) => (delegations, prior), + 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; diff --git a/frame/referenda/src/branch.rs b/frame/referenda/src/branch.rs index 4721ef5c5cc80..a8185f5efd84b 100644 --- a/frame/referenda/src/branch.rs +++ b/frame/referenda/src/branch.rs @@ -56,8 +56,8 @@ impl From for ServiceBranch { } impl ServiceBranch { - /// Return the weight of the `nudge` function when it takes the branch denoted by `self`. - pub fn weight_of_nudge(self) -> frame_support::weights::Weight { + /// Return the weight of the `nudge` function when it takes the branch denoted by `self`. + pub fn weight_of_nudge(self) -> frame_support::weights::Weight { use ServiceBranch::*; match self { NoDeposit => T::WeightInfo::nudge_referendum_no_deposit(), @@ -78,7 +78,7 @@ impl ServiceBranch { } } - /// Return the maximum possible weight of the `nudge` function. + /// Return the maximum possible weight of the `nudge` function. pub fn max_weight_of_nudge() -> frame_support::weights::Weight { 0.max(T::WeightInfo::nudge_referendum_no_deposit()) .max(T::WeightInfo::nudge_referendum_preparing()) @@ -97,8 +97,8 @@ impl ServiceBranch { .max(T::WeightInfo::nudge_referendum_timed_out()) } - /// Return the weight of the `place_decision_deposit` function when it takes the branch denoted - /// by `self`. + /// Return the weight of the `place_decision_deposit` function when it takes the branch denoted + /// by `self`. pub fn weight_of_deposit(self) -> Option { use ServiceBranch::*; Some(match self { @@ -121,7 +121,7 @@ impl ServiceBranch { }) } - /// Return the maximum possible weight of the `place_decision_deposit` function. + /// Return the maximum possible weight of the `place_decision_deposit` function. pub fn max_weight_of_deposit() -> frame_support::weights::Weight { 0.max(T::WeightInfo::place_decision_deposit_preparing()) .max(T::WeightInfo::place_decision_deposit_queued()) @@ -150,8 +150,8 @@ impl From for OneFewerDecidingBranch { } impl OneFewerDecidingBranch { - /// Return the weight of the `one_fewer_deciding` function when it takes the branch denoted - /// by `self`. + /// Return the weight of the `one_fewer_deciding` function when it takes the branch denoted + /// by `self`. pub fn weight(self) -> frame_support::weights::Weight { use OneFewerDecidingBranch::*; match self { @@ -161,7 +161,7 @@ impl OneFewerDecidingBranch { } } - /// Return the maximum possible weight of the `one_fewer_deciding` function. + /// Return the maximum possible weight of the `one_fewer_deciding` function. pub fn max_weight() -> frame_support::weights::Weight { 0.max(T::WeightInfo::one_fewer_deciding_queue_empty()) .max(T::WeightInfo::one_fewer_deciding_passing()) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 9be68d04c0173..43b2987cf972c 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -384,11 +384,13 @@ pub mod pallet { let mut status = Self::ensure_ongoing(index)?; ensure!(status.decision_deposit.is_none(), Error::::HaveDeposit); let track = Self::track(status.track).ok_or(Error::::NoTrack)?; - status.decision_deposit = Some(Self::take_deposit(who.clone(), track.decision_deposit)?); + status.decision_deposit = + Some(Self::take_deposit(who.clone(), track.decision_deposit)?); let now = frame_system::Pallet::::block_number(); let (info, _, branch) = Self::service_referendum(now, index, status); ReferendumInfoFor::::insert(index, info); - let e = Event::::DecisionDepositPlaced { index, who, amount: track.decision_deposit }; + let e = + Event::::DecisionDepositPlaced { index, who, amount: track.decision_deposit }; Self::deposit_event(e); Ok(branch.weight_of_deposit::().into()) } @@ -548,17 +550,17 @@ impl Polling for Pallet { ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); result }, - Some(ReferendumInfo::Approved(end, ..)) - => f(PollStatus::Completed(end, true)), - Some(ReferendumInfo::Rejected(end, ..)) - => f(PollStatus::Completed(end, false)), + Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)), _ => f(PollStatus::None), } } fn try_access_poll( index: Self::Index, - f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>) -> Result, + f: impl FnOnce( + PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>, + ) -> Result, ) -> Result { match ReferendumInfoFor::::get(index) { Some(ReferendumInfo::Ongoing(mut status)) => { @@ -568,10 +570,8 @@ impl Polling for Pallet { ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); Ok(result) }, - Some(ReferendumInfo::Approved(end, ..)) - => f(PollStatus::Completed(end, true)), - Some(ReferendumInfo::Rejected(end, ..)) - => f(PollStatus::Completed(end, false)), + Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)), _ => f(PollStatus::None), } } @@ -616,17 +616,9 @@ impl Polling for Pallet { Self::note_one_fewer_deciding(status.track); let now = frame_system::Pallet::::block_number(); let info = if approved { - ReferendumInfo::Approved( - now, - status.submission_deposit, - status.decision_deposit, - ) + ReferendumInfo::Approved(now, status.submission_deposit, status.decision_deposit) } else { - ReferendumInfo::Rejected( - now, - status.submission_deposit, - status.decision_deposit, - ) + ReferendumInfo::Rejected(now, status.submission_deposit, status.decision_deposit) }; ReferendumInfoFor::::insert(index, info); Ok(()) @@ -634,7 +626,8 @@ impl Polling for Pallet { #[cfg(feature = "runtime-benchmarks")] fn max_ongoing() -> (Self::Class, u32) { - let r = T::Tracks::tracks().iter() + let r = T::Tracks::tracks() + .iter() .max_by_key(|(_, info)| info.max_deciding) .expect("Always one class"); (r.0.clone(), r.1.max_deciding) diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 3c5a61a414c36..3392fe2ab19e0 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -23,7 +23,7 @@ pub mod tokens; pub use tokens::{ currency::{ Currency, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency, - VestingSchedule, TotalIssuanceOf, + TotalIssuanceOf, VestingSchedule, }, fungible, fungibles, imbalance::{Imbalance, OnUnbalanced, SignedImbalance}, diff --git a/frame/support/src/traits/tokens/currency.rs b/frame/support/src/traits/tokens/currency.rs index 92a6e3da3b1a5..d4b5c0c184f85 100644 --- a/frame/support/src/traits/tokens/currency.rs +++ b/frame/support/src/traits/tokens/currency.rs @@ -21,8 +21,10 @@ use super::{ imbalance::{Imbalance, SignedImbalance}, misc::{Balance, ExistenceRequirement, WithdrawReasons}, }; -use crate::traits::Get; -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; diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 2929df5bdbab9..978c5ce4f6a01 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -18,7 +18,6 @@ //! Traits and associated data structures concerned with voting, and moving between tokens and //! votes. -use sp_std::prelude::*; use crate::dispatch::{DispatchError, Parameter}; use codec::HasCompact; use sp_arithmetic::{ @@ -26,6 +25,7 @@ use sp_arithmetic::{ 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 From b9a8b3866dea890ae63eebd45b3a8b7a9b868f77 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 9 Jan 2022 17:29:18 +0100 Subject: [PATCH 55/66] Remove unneeded warning --- frame/scheduler/src/lib.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 4fd5d50a4de0f..8c1758d6536ef 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -718,13 +718,6 @@ impl Pallet { }); Agenda::::append(when, s); let index = Agenda::::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)) @@ -819,13 +812,6 @@ impl Pallet { }; Agenda::::append(when, Some(s)); let index = Agenda::::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::::insert(&id, &address); Self::deposit_event(Event::Scheduled { when, index }); From 497f404735b7a49b2d4ad6724b18ba855a756035 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 9 Jan 2022 17:55:14 +0100 Subject: [PATCH 56/66] Fix UI tests --- .../no_std_genesis_config.stderr | 20 ++++++++++--------- .../undefined_call_part.stderr | 16 ++++++++------- .../undefined_event_part.stderr | 20 ++++++++++--------- .../undefined_genesis_config_part.stderr | 18 +++++++++-------- .../undefined_inherent_part.stderr | 16 ++++++++------- .../undefined_origin_part.stderr | 20 ++++++++++--------- .../undefined_validate_unsigned_part.stderr | 16 ++++++++------- 7 files changed, 70 insertions(+), 56 deletions(-) diff --git a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr index e458265a07cab..3447ba6577e7e 100644 --- a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr +++ b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr @@ -1,5 +1,5 @@ error: `Pallet` does not have the std feature enabled, this will cause the `test_pallet::GenesisConfig` type to be undefined. - --> $DIR/no_std_genesis_config.rs:13:1 + --> tests/construct_runtime_ui/no_std_genesis_config.rs:13:1 | 13 | / construct_runtime! { 14 | | pub enum Runtime where @@ -13,13 +13,13 @@ error: `Pallet` does not have the std feature enabled, this will cause the `test = note: this error originates in the macro `test_pallet::__substrate_genesis_config_check::is_std_enabled_for_genesis` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/no_std_genesis_config.rs:19:11 + --> tests/construct_runtime_ui/no_std_genesis_config.rs:19:11 | 19 | System: system::{Pallet, Call, Storage, Config, Event}, | ^^^^^^ use of undeclared crate or module `system` error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/no_std_genesis_config.rs:13:1 + --> tests/construct_runtime_ui/no_std_genesis_config.rs:13:1 | 13 | / construct_runtime! { 14 | | pub enum Runtime where @@ -31,13 +31,15 @@ error[E0433]: failed to resolve: use of undeclared crate or module `system` | |_^ not found in `system` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum +help: consider importing one of these items + | +1 | use frame_support::dispatch::RawOrigin; | 1 | use frame_system::RawOrigin; | error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/no_std_genesis_config.rs:13:1 + --> tests/construct_runtime_ui/no_std_genesis_config.rs:13:1 | 13 | / construct_runtime! { 14 | | pub enum Runtime where @@ -59,7 +61,7 @@ help: consider importing one of these items | error[E0412]: cannot find type `GenesisConfig` in crate `test_pallet` - --> $DIR/no_std_genesis_config.rs:13:1 + --> tests/construct_runtime_ui/no_std_genesis_config.rs:13:1 | 13 | / construct_runtime! { 14 | | pub enum Runtime where @@ -77,13 +79,13 @@ help: consider importing this struct | error[E0277]: the trait bound `Runtime: frame_system::pallet::Config` is not satisfied - --> $DIR/no_std_genesis_config.rs:11:6 + --> tests/construct_runtime_ui/no_std_genesis_config.rs:11:6 | 11 | impl test_pallet::Config for Runtime {} | ^^^^^^^^^^^^^^^^^^^ the trait `frame_system::pallet::Config` is not implemented for `Runtime` | note: required by a bound in `Config` - --> $DIR/lib.rs:30:20 + --> pallet/src/lib.rs | -30 | pub trait Config: frame_system::Config {} + | pub trait Config: frame_system::Config {} | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `Config` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr index c4e567102a892..f722c12127935 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr @@ -1,5 +1,5 @@ error: `Pallet` does not have #[pallet::call] defined, perhaps you should remove `Call` from construct_runtime? - --> $DIR/undefined_call_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_call_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,13 +16,13 @@ error: `Pallet` does not have #[pallet::call] defined, perhaps you should remove = note: this error originates in the macro `pallet::__substrate_call_check::is_call_part_defined` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_call_part.rs:28:11 + --> tests/construct_runtime_ui/undefined_call_part.rs:28:11 | 28 | System: system::{Pallet, Call, Storage, Config, Event}, | ^^^^^^ use of undeclared crate or module `system` error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_call_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_call_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -34,13 +34,15 @@ error[E0433]: failed to resolve: use of undeclared crate or module `system` | |_^ not found in `system` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum +help: consider importing one of these items + | +1 | use frame_support::dispatch::RawOrigin; | 1 | use frame_system::RawOrigin; | error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_call_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_call_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -64,13 +66,13 @@ help: consider importing one of these items | error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_call_part.rs:20:6 + --> tests/construct_runtime_ui/undefined_call_part.rs:20:6 | 20 | impl pallet::Config for Runtime {} | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` | note: required by a bound in `pallet::Config` - --> $DIR/undefined_call_part.rs:8:20 + --> tests/construct_runtime_ui/undefined_call_part.rs:8:20 | 8 | pub trait Config: frame_system::Config {} | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `pallet::Config` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr index da972f6f4b2f4..3847a940405ef 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr @@ -1,5 +1,5 @@ error: `Pallet` does not have #[pallet::event] defined, perhaps you should remove `Event` from construct_runtime? - --> $DIR/undefined_event_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_event_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,13 +16,13 @@ error: `Pallet` does not have #[pallet::event] defined, perhaps you should remov = note: this error originates in the macro `pallet::__substrate_event_check::is_event_part_defined` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_event_part.rs:28:11 + --> tests/construct_runtime_ui/undefined_event_part.rs:28:11 | 28 | System: system::{Pallet, Call, Storage, Config, Event}, | ^^^^^^ use of undeclared crate or module `system` error[E0412]: cannot find type `Event` in module `pallet` - --> $DIR/undefined_event_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_event_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -40,7 +40,7 @@ help: consider importing this enum | error[E0412]: cannot find type `Event` in module `pallet` - --> $DIR/undefined_event_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_event_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -60,7 +60,7 @@ help: consider importing one of these items | error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_event_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_event_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -72,13 +72,15 @@ error[E0433]: failed to resolve: use of undeclared crate or module `system` | |_^ not found in `system` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum +help: consider importing one of these items + | +1 | use frame_support::dispatch::RawOrigin; | 1 | use frame_system::RawOrigin; | error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_event_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_event_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -102,13 +104,13 @@ help: consider importing one of these items | error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_event_part.rs:20:6 + --> tests/construct_runtime_ui/undefined_event_part.rs:20:6 | 20 | impl pallet::Config for Runtime {} | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` | note: required by a bound in `pallet::Config` - --> $DIR/undefined_event_part.rs:8:20 + --> tests/construct_runtime_ui/undefined_event_part.rs:8:20 | 8 | pub trait Config: frame_system::Config {} | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `pallet::Config` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr index 8e40773b65736..f4c0ae0f6982e 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr @@ -1,5 +1,5 @@ error: `Pallet` does not have #[pallet::genesis_config] defined, perhaps you should remove `Config` from construct_runtime? - --> $DIR/undefined_genesis_config_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,13 +16,13 @@ error: `Pallet` does not have #[pallet::genesis_config] defined, perhaps you sho = note: this error originates in the macro `pallet::__substrate_genesis_config_check::is_genesis_config_defined` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_genesis_config_part.rs:28:17 + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:28:17 | 28 | System: system::{Pallet, Call, Storage, Config, Event}, | ^^^^^^ use of undeclared crate or module `system` error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_genesis_config_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -34,13 +34,15 @@ error[E0433]: failed to resolve: use of undeclared crate or module `system` | |_^ not found in `system` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum +help: consider importing one of these items + | +1 | use frame_support::dispatch::RawOrigin; | 1 | use frame_system::RawOrigin; | error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_genesis_config_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -64,7 +66,7 @@ help: consider importing one of these items | error[E0412]: cannot find type `GenesisConfig` in module `pallet` - --> $DIR/undefined_genesis_config_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -82,13 +84,13 @@ help: consider importing this struct | error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_genesis_config_part.rs:20:6 + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:20:6 | 20 | impl pallet::Config for Runtime {} | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` | note: required by a bound in `pallet::Config` - --> $DIR/undefined_genesis_config_part.rs:8:20 + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:8:20 | 8 | pub trait Config: frame_system::Config {} | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `pallet::Config` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr index ae461473c3b11..ab67097f75628 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr @@ -1,5 +1,5 @@ error: `Pallet` does not have #[pallet::inherent] defined, perhaps you should remove `Inherent` from construct_runtime? - --> $DIR/undefined_inherent_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,13 +16,13 @@ error: `Pallet` does not have #[pallet::inherent] defined, perhaps you should re = note: this error originates in the macro `pallet::__substrate_inherent_check::is_inherent_part_defined` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_inherent_part.rs:28:11 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:28:11 | 28 | System: system::{Pallet, Call, Storage, Config, Event}, | ^^^^^^ use of undeclared crate or module `system` error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_inherent_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -34,13 +34,15 @@ error[E0433]: failed to resolve: use of undeclared crate or module `system` | |_^ not found in `system` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum +help: consider importing one of these items + | +1 | use frame_support::dispatch::RawOrigin; | 1 | use frame_system::RawOrigin; | error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_inherent_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -64,13 +66,13 @@ help: consider importing one of these items | error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_inherent_part.rs:20:6 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:20:6 | 20 | impl pallet::Config for Runtime {} | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` | note: required by a bound in `pallet::Config` - --> $DIR/undefined_inherent_part.rs:8:20 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:8:20 | 8 | pub trait Config: frame_system::Config {} | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `pallet::Config` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr index dbdd9f869a2e3..53ecb35549118 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr @@ -1,5 +1,5 @@ error: `Pallet` does not have #[pallet::origin] defined, perhaps you should remove `Origin` from construct_runtime? - --> $DIR/undefined_origin_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_origin_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,13 +16,13 @@ error: `Pallet` does not have #[pallet::origin] defined, perhaps you should remo = note: this error originates in the macro `pallet::__substrate_origin_check::is_origin_part_defined` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_origin_part.rs:28:11 + --> tests/construct_runtime_ui/undefined_origin_part.rs:28:11 | 28 | System: system::{Pallet, Call, Storage, Config, Event}, | ^^^^^^ use of undeclared crate or module `system` error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_origin_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_origin_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -34,13 +34,15 @@ error[E0433]: failed to resolve: use of undeclared crate or module `system` | |_^ not found in `system` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum +help: consider importing one of these items + | +1 | use frame_support::dispatch::RawOrigin; | 1 | use frame_system::RawOrigin; | error[E0412]: cannot find type `Origin` in module `pallet` - --> $DIR/undefined_origin_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_origin_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -58,7 +60,7 @@ help: consider importing this type alias | error[E0412]: cannot find type `Origin` in module `pallet` - --> $DIR/undefined_origin_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_origin_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -78,7 +80,7 @@ help: consider importing one of these items | error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_origin_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_origin_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -102,13 +104,13 @@ help: consider importing one of these items | error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_origin_part.rs:20:6 + --> tests/construct_runtime_ui/undefined_origin_part.rs:20:6 | 20 | impl pallet::Config for Runtime {} | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` | note: required by a bound in `pallet::Config` - --> $DIR/undefined_origin_part.rs:8:20 + --> tests/construct_runtime_ui/undefined_origin_part.rs:8:20 | 8 | pub trait Config: frame_system::Config {} | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `pallet::Config` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr index 8126d2f9a3e0f..6547e9280eb4b 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr @@ -1,5 +1,5 @@ error: `Pallet` does not have #[pallet::validate_unsigned] defined, perhaps you should remove `ValidateUnsigned` from construct_runtime? - --> $DIR/undefined_validate_unsigned_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,13 +16,13 @@ error: `Pallet` does not have #[pallet::validate_unsigned] defined, perhaps you = note: this error originates in the macro `pallet::__substrate_validate_unsigned_check::is_validate_unsigned_part_defined` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_validate_unsigned_part.rs:28:11 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:28:11 | 28 | System: system::{Pallet, Call, Storage, Config, Event}, | ^^^^^^ use of undeclared crate or module `system` error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_validate_unsigned_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -34,13 +34,15 @@ error[E0433]: failed to resolve: use of undeclared crate or module `system` | |_^ not found in `system` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum +help: consider importing one of these items + | +1 | use frame_support::dispatch::RawOrigin; | 1 | use frame_system::RawOrigin; | error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_validate_unsigned_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:22:1 | 22 | / construct_runtime! { 23 | | pub enum Runtime where @@ -64,13 +66,13 @@ help: consider importing one of these items | error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_validate_unsigned_part.rs:20:6 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:20:6 | 20 | impl pallet::Config for Runtime {} | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` | note: required by a bound in `pallet::Config` - --> $DIR/undefined_validate_unsigned_part.rs:8:20 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:8:20 | 8 | pub trait Config: frame_system::Config {} | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `pallet::Config` From 28c679f25387f71e1b71f76ce2bb3afae053a88d Mon Sep 17 00:00:00 2001 From: Parity Bot Date: Sun, 9 Jan 2022 16:59:30 +0000 Subject: [PATCH 57/66] 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 --- frame/conviction-voting/src/weights.rs | 71 ++++++++++++-------------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs index f1612dddbc6d2..da15aac0b47ca 100644 --- a/frame/conviction-voting/src/weights.rs +++ b/frame/conviction-voting/src/weights.rs @@ -18,29 +18,22 @@ //! 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: `10`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 +// target/release/substrate // benchmark -// --chain -// dev -// --steps -// 10 -// --repeat -// 10 -// --pallet -// pallet-conviction-voting -// --extrinsic -// * -// --raw +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_conviction_voting +// --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --output -// ../../../frame/conviction-voting/src/weights.rs -// --template -// ../../../.maintain/frame-weight-template.hbs +// --heap-pages=4096 +// --output=./frame/conviction-voting/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -69,7 +62,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - (130_000_000 as Weight) + (159_647_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -79,7 +72,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - (226_000_000 as Weight) + (339_851_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -87,14 +80,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - (225_000_000 as 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 { - (45_000_000 as Weight) + (52_222_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -104,9 +97,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn delegate(r: u32, ) -> Weight { - (43_167_000 as Weight) - // Standard Error: 3_423_000 - .saturating_add((32_000_000 as Weight).saturating_mul(r as 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)) @@ -116,9 +109,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn undelegate(r: u32, ) -> Weight { - (27_333_000 as Weight) - // Standard Error: 1_885_000 - .saturating_add((24_000_000 as Weight).saturating_mul(r as 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)) @@ -128,7 +121,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - (55_000_000 as Weight) + (69_017_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -142,7 +135,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - (130_000_000 as Weight) + (159_647_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -152,7 +145,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - (226_000_000 as Weight) + (339_851_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -160,14 +153,14 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - (225_000_000 as 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 { - (45_000_000 as Weight) + (52_222_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -177,9 +170,9 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn delegate(r: u32, ) -> Weight { - (43_167_000 as Weight) - // Standard Error: 3_423_000 - .saturating_add((32_000_000 as Weight).saturating_mul(r as 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)) @@ -189,9 +182,9 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn undelegate(r: u32, ) -> Weight { - (27_333_000 as Weight) - // Standard Error: 1_885_000 - .saturating_add((24_000_000 as Weight).saturating_mul(r as 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)) @@ -201,7 +194,7 @@ impl WeightInfo for () { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - (55_000_000 as Weight) + (69_017_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } From 6102564030555faee9ea170d18b1f82f8a0d9f59 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 18 Jan 2022 14:44:51 +0100 Subject: [PATCH 58/66] Docs --- frame/balances/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 5235dc97ccb4f..9e570dc02a3d8 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -1497,7 +1497,7 @@ where .map_err(|_| Error::::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::::can_dec_provider(transactor); From c6c9a29b45b0583f5962e50814952d9684796638 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Thu, 20 Jan 2022 14:02:06 +0100 Subject: [PATCH 59/66] Update frame/conviction-voting/src/vote.rs Co-authored-by: Shawn Tabrizi --- frame/conviction-voting/src/vote.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index 8a4b5bd2d048f..a31536a2d5f9d 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -138,7 +138,7 @@ pub struct Delegating { pub balance: Balance, /// The account to which the voting power is delegated. pub target: AccountId, - /// Th conviction with which the voting power is delegated. When this gets undelegated, the + /// 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. From 98a72f187d6f6ad075f6a21140dd5151c42f7f35 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Mon, 24 Jan 2022 10:34:11 +0000 Subject: [PATCH 60/66] update sp-runtime version --- frame/conviction-voting/Cargo.toml | 2 +- frame/referenda/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index 99b0e231d48e5..994cc8e851170 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "2.0.0", default-features = 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 = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "4.1.0-dev", 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" } diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index 224438435a6a3..37152234d6f4c 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "2.0.0", default-features = 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 = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "4.1.0-dev", 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" } From 5e5f314ddd67e4499c86fb1d75abe077d4ffbdf1 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Fri, 28 Jan 2022 17:54:44 +0000 Subject: [PATCH 61/66] MEL Fixes for Referenda and Conviction Voting (#10725) * free maxencodedlen * more maxencodedlen * more MEL * more mel * disable storage info --- frame/conviction-voting/src/conviction.rs | 4 +- frame/conviction-voting/src/lib.rs | 9 ++-- frame/conviction-voting/src/types.rs | 5 ++- frame/conviction-voting/src/vote.rs | 50 +++++++++++++++-------- frame/referenda/src/lib.rs | 1 + frame/referenda/src/mock.rs | 4 +- frame/referenda/src/types.rs | 16 ++++---- 7 files changed, 53 insertions(+), 36 deletions(-) diff --git a/frame/conviction-voting/src/conviction.rs b/frame/conviction-voting/src/conviction.rs index ba79081a3f549..a273a0a8273fe 100644 --- a/frame/conviction-voting/src/conviction.rs +++ b/frame/conviction-voting/src/conviction.rs @@ -18,7 +18,7 @@ //! The conviction datatype. use crate::types::Delegations; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ traits::{Bounded, CheckedDiv, CheckedMul, Zero}, @@ -27,7 +27,7 @@ use sp_runtime::{ 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)] +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum Conviction { /// 0.1x votes, unlocked. None, diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index d7ebd40e206cf..16c31efd7772d 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -65,6 +65,7 @@ type VotingOf = Voting< ::AccountId, ::BlockNumber, PollIndexOf, + ::MaxVotes, >; #[allow(dead_code)] type DelegatingOf = Delegating< @@ -88,6 +89,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::config] @@ -377,11 +379,8 @@ impl Pallet { votes[i].1 = vote; }, Err(i) => { - ensure!( - (votes.len() as u32) < T::MaxVotes::get(), - Error::::MaxVotesReached - ); - votes.insert(i, (poll_index, vote)); + votes.try_insert(i, (poll_index, vote)) + .map_err(|()| Error::::MaxVotesReached)?; }, } // Shouldn't be possible to fail, but we handle it gracefully. diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index e0eb5bf0dc5ef..432bd7b97e6d4 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -21,7 +21,7 @@ use sp_std::marker::PhantomData; use super::*; use crate::{AccountVote, Conviction, Vote}; -use codec::{Codec, Decode, Encode}; +use codec::{Codec, Decode, Encode, MaxEncodedLen}; use frame_support::{ traits::VoteTally, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, @@ -42,6 +42,7 @@ use sp_runtime::{ TypeInfo, Encode, Decode, + MaxEncodedLen, )] #[scale_info(skip_type_params(Total))] pub struct Tally< @@ -190,7 +191,7 @@ impl< } /// Amount of votes and capital placed in delegation for an account. -#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct Delegations { /// The number of votes (this is post-conviction). pub votes: Balance, diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index a31536a2d5f9d..38785ac78d60f 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -18,16 +18,17 @@ //! The vote datatype. use crate::{Conviction, Delegations}; -use codec::{Decode, Encode, EncodeLike, Input, Output}; +use codec::{Decode, Encode, EncodeLike, Input, Output, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ traits::{Saturating, Zero}, RuntimeDebug, }; use sp_std::{convert::TryFrom, prelude::*, result::Result}; +use frame_support::{BoundedVec, pallet_prelude::Get}; /// A number of lock periods, plus a vote, one way or the other. -#[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)] +#[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen)] pub struct Vote { pub aye: bool, pub conviction: Conviction, @@ -66,7 +67,7 @@ impl TypeInfo for Vote { } /// A vote for a referendum of a particular account. -#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum AccountVote { /// A standard vote, one-way (approve or reject) with a given amount of conviction. Standard { vote: Vote, balance: Balance }, @@ -108,7 +109,7 @@ impl AccountVote { /// A "prior" lock, i.e. a lock for some now-forgotten reason. #[derive( - Encode, Decode, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, + Encode, Decode, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen, )] pub struct PriorLock(BlockNumber, Balance); @@ -132,7 +133,7 @@ impl PriorLock { /// The amount of balance delegated. pub balance: Balance, @@ -148,10 +149,14 @@ pub struct Delegating { } /// Information concerning the direct vote-casting of some voting power. -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct Casting { +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxVotes))] +pub struct Casting + where + MaxVotes: Get, +{ /// The current votes of the account. - pub votes: Vec<(PollIndex, AccountVote)>, + pub votes: BoundedVec<(PollIndex, AccountVote), MaxVotes>, /// The total amount of delegations that this account has received, post-conviction-weighting. pub delegations: Delegations, /// Any pre-existing locks from past voting/delegating activity. @@ -159,28 +164,36 @@ pub struct Casting { } /// An indicator for what an account is doing; it can either be delegating or voting. -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub enum Voting { +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxVotes))] +pub enum Voting + where + MaxVotes: Get, +{ /// The account is voting directly. - Casting(Casting), + Casting(Casting), /// The account is delegating `balance` of its balance to a `target` account with `conviction`. Delegating(Delegating), } -impl Default - for Voting +impl Default + for Voting + where + MaxVotes: Get, { fn default() -> Self { Voting::Casting(Casting { - votes: Vec::new(), + votes: Default::default(), delegations: Default::default(), prior: PriorLock(Zero::zero(), Default::default()), }) } } -impl AsMut> - for Voting +impl AsMut> + for Voting + where + MaxVotes: Get, { fn as_mut(&mut self) -> &mut PriorLock { match self { @@ -195,7 +208,10 @@ impl< BlockNumber: Ord + Copy + Zero, AccountId, PollIndex, - > Voting + MaxVotes, + > Voting + where + MaxVotes: Get, { pub fn rejig(&mut self, now: BlockNumber) { AsMut::>::as_mut(self).rejig(now); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 43b2987cf972c..734bd9c0da709 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -110,6 +110,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::config] diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 0d20304f8f7db..eecbd6a0573d6 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -19,7 +19,7 @@ use super::*; use crate as pallet_referenda; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ assert_ok, ord_parameter_types, parameter_types, traits::{ @@ -253,7 +253,7 @@ pub fn new_test_ext_execute_with_cond(execute: impl FnOnce(bool) -> () + Clone) new_test_ext().execute_with(|| execute(true)); } -#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, Default)] +#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, Default, MaxEncodedLen)] pub struct Tally { pub ayes: u32, pub nays: u32, diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 00b45c4219bc3..d4d81b6a7d513 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -18,7 +18,7 @@ //! Miscellaneous additional datatypes. use super::*; -use codec::{Decode, Encode, EncodeLike}; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use frame_support::{traits::schedule::Anon, Parameter}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; @@ -124,7 +124,7 @@ mod tests { } } -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct DecidingStatus { /// When this referendum began being "decided". If confirming, then the /// end will actually be delayed until the end of the confirmation period. @@ -134,7 +134,7 @@ pub struct DecidingStatus { pub(crate) confirming: Option, } -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct Deposit { pub(crate) who: AccountId, pub(crate) amount: Balance, @@ -142,7 +142,7 @@ pub struct Deposit { #[derive(Clone, Encode, TypeInfo)] pub struct TrackInfo { - /// Name of this track. + /// 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. @@ -186,7 +186,7 @@ pub trait TracksInfo { } /// Indication of either a specific moment or a delay from a implicitly defined moment. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum AtOrAfter { /// Indiciates that the event should occur at the moment given. At(Moment), @@ -206,7 +206,7 @@ impl AtOrAfter { } /// Info regarding an ongoing referendum. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[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, @@ -243,7 +243,7 @@ pub struct ReferendumStatus< } /// Info regarding a referendum, present or past. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[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, @@ -298,7 +298,7 @@ impl< /// 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)] +#[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)`. From 6f4acf3baaaa5d32b1c4cec60d930711222c08db Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 1 Feb 2022 23:40:30 +0100 Subject: [PATCH 62/66] More Referenda Patches (#10760) * basic fixes * fix benchmarking * fix license --- frame/conviction-voting/src/benchmarking.rs | 2 ++ frame/conviction-voting/src/conviction.rs | 14 +++++++- frame/conviction-voting/src/lib.rs | 14 ++++++-- frame/conviction-voting/src/types.rs | 10 +++--- frame/conviction-voting/src/vote.rs | 37 +++++++++++++-------- frame/referenda/src/branch.rs | 4 ++- frame/referenda/src/lib.rs | 6 ++-- 7 files changed, 62 insertions(+), 25 deletions(-) diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 040287f41bdb5..2beee4f3b49d2 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -61,6 +61,8 @@ fn account_vote(b: BalanceOf) -> AccountVote> { } benchmarks! { + where_clause { where T::MaxVotes: core::fmt::Debug } + vote_new { let caller = funded_account::("caller", 0); whitelist_account!(caller); diff --git a/frame/conviction-voting/src/conviction.rs b/frame/conviction-voting/src/conviction.rs index a273a0a8273fe..129f2771124b5 100644 --- a/frame/conviction-voting/src/conviction.rs +++ b/frame/conviction-voting/src/conviction.rs @@ -27,7 +27,19 @@ use sp_runtime::{ 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)] +#[derive( + Encode, + Decode, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + RuntimeDebug, + TypeInfo, + MaxEncodedLen, +)] pub enum Conviction { /// 0.1x votes, unlocked. None, diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 16c31efd7772d..8e7e0d91b1cf4 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -379,7 +379,8 @@ impl Pallet { votes[i].1 = vote; }, Err(i) => { - votes.try_insert(i, (poll_index, vote)) + votes + .try_insert(i, (poll_index, vote)) .map_err(|()| Error::::MaxVotesReached)?; }, } @@ -433,7 +434,9 @@ impl Pallet { }, PollStatus::Completed(end, approved) => { if let Some((lock_periods, balance)) = v.1.locked_if(approved) { - let unlock_at = end + T::VoteLockingPeriod::get() * lock_periods.into(); + let unlock_at = end.saturating_add( + T::VoteLockingPeriod::get().saturating_mul(lock_periods.into()), + ); let now = frame_system::Pallet::::block_number(); if now < unlock_at { ensure!( @@ -576,7 +579,12 @@ impl Pallet { ); let now = frame_system::Pallet::::block_number(); let lock_periods = conviction.lock_periods().into(); - prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); + prior.accumulate( + now.saturating_add( + T::VoteLockingPeriod::get().saturating_mul(lock_periods), + ), + balance, + ); voting.set_common(delegations, prior); Ok(votes) diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 432bd7b97e6d4..2ad1a164dd143 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -170,28 +170,28 @@ impl< } /// Increment some amount of votes. - pub fn increase(&mut self, approve: bool, delegations: Delegations) -> Option<()> { + pub fn increase(&mut self, approve: bool, delegations: Delegations) { 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), } - Some(()) } /// Decrement some amount of votes. - pub fn reduce(&mut self, approve: bool, delegations: Delegations) -> Option<()> { + pub fn reduce(&mut self, approve: bool, delegations: Delegations) { 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), } - Some(()) } } /// Amount of votes and capital placed in delegation for an account. -#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[derive( + Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] pub struct Delegations { /// The number of votes (this is post-conviction). pub votes: Balance, diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index 38785ac78d60f..d7ca931de35a1 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -18,14 +18,14 @@ //! The vote datatype. use crate::{Conviction, Delegations}; -use codec::{Decode, Encode, EncodeLike, Input, Output, MaxEncodedLen}; +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}; -use frame_support::{BoundedVec, pallet_prelude::Get}; /// A number of lock periods, plus a vote, one way or the other. #[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen)] @@ -109,7 +109,18 @@ impl AccountVote { /// 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, + Encode, + Decode, + Default, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + RuntimeDebug, + TypeInfo, + MaxEncodedLen, )] pub struct PriorLock(BlockNumber, Balance); @@ -152,8 +163,8 @@ pub struct Delegating { #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(MaxVotes))] pub struct Casting - where - MaxVotes: Get, +where + MaxVotes: Get, { /// The current votes of the account. pub votes: BoundedVec<(PollIndex, AccountVote), MaxVotes>, @@ -167,8 +178,8 @@ pub struct Casting #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(MaxVotes))] pub enum Voting - where - MaxVotes: Get, +where + MaxVotes: Get, { /// The account is voting directly. Casting(Casting), @@ -178,8 +189,8 @@ pub enum Voting impl Default for Voting - where - MaxVotes: Get, +where + MaxVotes: Get, { fn default() -> Self { Voting::Casting(Casting { @@ -192,8 +203,8 @@ impl Defaul impl AsMut> for Voting - where - MaxVotes: Get, +where + MaxVotes: Get, { fn as_mut(&mut self) -> &mut PriorLock { match self { @@ -210,8 +221,8 @@ impl< PollIndex, MaxVotes, > Voting - where - MaxVotes: Get, +where + MaxVotes: Get, { pub fn rejig(&mut self, now: BlockNumber) { AsMut::>::as_mut(self).rejig(now); diff --git a/frame/referenda/src/branch.rs b/frame/referenda/src/branch.rs index a8185f5efd84b..6a4efa31e15e2 100644 --- a/frame/referenda/src/branch.rs +++ b/frame/referenda/src/branch.rs @@ -7,7 +7,9 @@ // 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.0sp_runtime::{DispatchResult, traits::One}asp_runtime::{DispatchResult, traits::AtLeast32BitUnsigned} in writing, software +// 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 diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 734bd9c0da709..fb8e9aa6a6db7 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -7,7 +7,9 @@ // 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.0sp_runtime::{DispatchResult, traits::One}asp_runtime::{DispatchResult, traits::AtLeast32BitUnsigned} in writing, software +// 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 @@ -360,7 +362,7 @@ pub mod pallet { deciding: None, tally: Default::default(), in_queue: false, - alarm: Self::set_alarm(nudge_call, now + T::UndecidingTimeout::get()), + alarm: Self::set_alarm(nudge_call, now.saturating_add(T::UndecidingTimeout::get())), }; ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); From 0c4df94a82b01264b2eaa631f5fd6d44bf51f167 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sat, 5 Feb 2022 18:12:15 +0100 Subject: [PATCH 63/66] prevent panic in curve math --- frame/referenda/src/tests.rs | 19 +++++++++++++++++++ frame/referenda/src/types.rs | 8 ++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 385d8839891f0..6cc34eb974837 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -496,3 +496,22 @@ fn set_balance_proposal_is_correctly_filtered_out() { assert!(!::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()); +} diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index d4d81b6a7d513..b6c218c235055 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -309,7 +309,7 @@ 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, + Self::LinearDecreasing { begin, delta } => *begin - (*delta * x).min(*begin), } } @@ -327,7 +327,11 @@ impl Curve { pub fn delay(&self, y: Perbill) -> Perbill { match self { Self::LinearDecreasing { begin, delta } => - (*begin - y.min(*begin)).min(*delta) / *delta, + if delta.is_zero() { + return *delta + } else { + return (*begin - y.min(*begin)).min(*delta) / *delta + } } } From dfc6faf835f4fce3bf553c794455edef307db465 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sat, 5 Feb 2022 18:42:44 +0100 Subject: [PATCH 64/66] fmt --- frame/referenda/src/tests.rs | 10 ++-------- frame/referenda/src/types.rs | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 6cc34eb974837..cea071ced12fe 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -499,18 +499,12 @@ fn set_balance_proposal_is_correctly_filtered_out() { #[test] fn curve_handles_all_inputs() { - let test_curve = Curve::LinearDecreasing { - begin: Perbill::zero(), - delta: Perbill::zero(), - }; + 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 test_curve = Curve::LinearDecreasing { begin: Perbill::zero(), delta: Perbill::one() }; let threshold = test_curve.threshold(Perbill::one()); assert_eq!(threshold, Perbill::zero()); diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index b6c218c235055..1b028fdfe9f79 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -331,7 +331,7 @@ impl Curve { return *delta } else { return (*begin - y.min(*begin)).min(*delta) / *delta - } + }, } } From 4db9db5e840bd5eba06238c62061234cae09d703 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sat, 5 Feb 2022 18:46:20 +0100 Subject: [PATCH 65/66] bump crate versions --- frame/conviction-voting/Cargo.toml | 6 +++--- frame/referenda/Cargo.toml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index 994cc8e851170..ab62065c1c546 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -19,15 +19,15 @@ codec = { package = "parity-scale-codec", version = "2.0.0", default-features = ] } 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 = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.1.0-dev", default-features = false, path = "../../primitives/runtime" } +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 = "4.1.0-dev", path = "../../primitives/core" } +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" } diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index 37152234d6f4c..e979f2b0c4a37 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -19,15 +19,15 @@ codec = { package = "parity-scale-codec", version = "2.0.0", default-features = ] } 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 = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.1.0-dev", default-features = false, path = "../../primitives/runtime" } +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 = "4.1.0-dev", path = "../../primitives/core" } +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" } From 6a1a45de158449672e841a944d74d6484f31831e Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sat, 5 Feb 2022 19:00:47 +0100 Subject: [PATCH 66/66] Update mock.rs --- frame/referenda/src/mock.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index eecbd6a0573d6..063b124f2b71f 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -45,11 +45,11 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + System: frame_system, + Balances: pallet_balances, Preimage: pallet_preimage, - Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, - Referenda: pallet_referenda::{Pallet, Call, Storage, Event}, + Scheduler: pallet_scheduler, + Referenda: pallet_referenda, } );