feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
[package]
|
||||
name = "pezpallet-democracy"
|
||||
version = "28.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME pallet for democracy"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = [
|
||||
"derive",
|
||||
], optional = true, workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-preimage = { workspace = true, default-features = true }
|
||||
pezpallet-scheduler = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-preimage/std",
|
||||
"pezpallet-scheduler/std",
|
||||
"scale-info/std",
|
||||
"serde",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-preimage/runtime-benchmarks",
|
||||
"pezpallet-scheduler/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-preimage/try-runtime",
|
||||
"pezpallet-scheduler/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,135 @@
|
||||
# Democracy Pallet
|
||||
|
||||
- [`democracy::Config`](https://docs.rs/pezpallet-democracy/latest/pallet_democracy/trait.Config.html)
|
||||
- [`Call`](https://docs.rs/pezpallet-democracy/latest/pallet_democracy/enum.Call.html)
|
||||
|
||||
## Overview
|
||||
|
||||
The Democracy pallet handles the administration of general stakeholder voting.
|
||||
|
||||
There are two different queues that a proposal can be added to before it
|
||||
becomes a referendum, 1) the proposal queue consisting of all public proposals
|
||||
and 2) the external queue consisting of a single proposal that originates
|
||||
from one of the _external_ origins (such as a collective group).
|
||||
|
||||
Every launch period - a length defined in the runtime - the Democracy pallet
|
||||
launches a referendum from a proposal that it takes from either the proposal
|
||||
queue or the external queue in turn. Any token holder in the system can vote
|
||||
on referenda. The voting system
|
||||
uses time-lock voting by allowing the token holder to set their _conviction_
|
||||
behind a vote. The conviction will dictate the length of time the tokens
|
||||
will be locked, as well as the multiplier that scales the vote power.
|
||||
|
||||
### Terminology
|
||||
|
||||
- **Enactment Period:** The minimum period of locking and the period between a proposal being
|
||||
approved and enacted.
|
||||
- **Lock Period:** A period of time after proposal enactment that the tokens of _winning_ voters
|
||||
will be locked.
|
||||
- **Conviction:** An indication of a voter's strength of belief in their vote. An increase
|
||||
of one in conviction indicates that a token holder is willing to lock their tokens for twice
|
||||
as many lock periods after enactment.
|
||||
- **Vote:** A value that can either be in approval ("Aye") or rejection ("Nay")
|
||||
of a particular referendum.
|
||||
- **Proposal:** A submission to the chain that represents an action that a proposer (either an
|
||||
account or an external origin) suggests that the system adopt.
|
||||
- **Referendum:** A proposal that is in the process of being voted on for
|
||||
either acceptance or rejection as a change to the system.
|
||||
- **Delegation:** The act of granting your voting power to the decisions of another account for
|
||||
up to a certain conviction.
|
||||
|
||||
### Adaptive Quorum Biasing
|
||||
|
||||
A _referendum_ can be either simple majority-carries in which 50%+1 of the
|
||||
votes decide the outcome or _adaptive quorum biased_. Adaptive quorum biasing
|
||||
makes the threshold for passing or rejecting a referendum higher or lower
|
||||
depending on how the referendum was originally proposed. There are two types of
|
||||
adaptive quorum biasing: 1) _positive turnout bias_ makes a referendum
|
||||
require a super-majority to pass that decreases as turnout increases and
|
||||
2) _negative turnout bias_ makes a referendum require a super-majority to
|
||||
reject that decreases as turnout increases. Another way to think about the
|
||||
quorum biasing is that _positive bias_ referendums will be rejected by
|
||||
default and _negative bias_ referendums get passed by default.
|
||||
|
||||
## Interface
|
||||
|
||||
### Dispatchable Functions
|
||||
|
||||
#### Public
|
||||
|
||||
These calls can be made from any externally held account capable of creating
|
||||
a signed extrinsic.
|
||||
|
||||
Basic actions:
|
||||
- `propose` - Submits a sensitive action, represented as a hash. Requires a deposit.
|
||||
- `second` - Signals agreement with a proposal, moves it higher on the proposal queue, and
|
||||
requires a matching deposit to the original.
|
||||
- `vote` - Votes in a referendum, either the vote is "Aye" to enact the proposal or "Nay" to
|
||||
keep the status quo.
|
||||
- `unvote` - Cancel a previous vote, this must be done by the voter before the vote ends.
|
||||
- `delegate` - Delegates the voting power (tokens * conviction) to another account.
|
||||
- `undelegate` - Stops the delegation of voting power to another account.
|
||||
|
||||
Administration actions that can be done to any account:
|
||||
- `reap_vote` - Remove some account's expired votes.
|
||||
- `unlock` - Redetermine the account's balance lock, potentially making tokens available.
|
||||
|
||||
Preimage actions:
|
||||
- `note_preimage` - Registers the preimage for an upcoming proposal, requires
|
||||
a deposit that is returned once the proposal is enacted.
|
||||
- `note_preimage_operational` - same but provided by `T::OperationalPreimageOrigin`.
|
||||
- `note_imminent_preimage` - Registers the preimage for an upcoming proposal.
|
||||
Does not require a deposit, but the proposal must be in the dispatch queue.
|
||||
- `note_imminent_preimage_operational` - same but provided by `T::OperationalPreimageOrigin`.
|
||||
- `reap_preimage` - Removes the preimage for an expired proposal. Will only
|
||||
work under the condition that it's the same account that noted it and
|
||||
after the voting period, OR it's a different account after the enactment period.
|
||||
|
||||
#### Cancellation Origin
|
||||
|
||||
This call can only be made by the `CancellationOrigin`.
|
||||
|
||||
- `emergency_cancel` - Schedules an emergency cancellation of a referendum.
|
||||
Can only happen once to a specific referendum.
|
||||
|
||||
#### ExternalOrigin
|
||||
|
||||
This call can only be made by the `ExternalOrigin`.
|
||||
|
||||
- `external_propose` - Schedules a proposal to become a referendum once it is legal
|
||||
for an externally proposed referendum.
|
||||
|
||||
#### External Majority Origin
|
||||
|
||||
This call can only be made by the `ExternalMajorityOrigin`.
|
||||
|
||||
- `external_propose_majority` - Schedules a proposal to become a majority-carries
|
||||
referendum once it is legal for an externally proposed referendum.
|
||||
|
||||
#### External Default Origin
|
||||
|
||||
This call can only be made by the `ExternalDefaultOrigin`.
|
||||
|
||||
- `external_propose_default` - Schedules a proposal to become a negative-turnout-bias
|
||||
referendum once it is legal for an externally proposed referendum.
|
||||
|
||||
#### Fast Track Origin
|
||||
|
||||
This call can only be made by the `FastTrackOrigin`.
|
||||
|
||||
- `fast_track` - Schedules the current externally proposed proposal that
|
||||
is "majority-carries" to become a referendum immediately.
|
||||
|
||||
#### Veto Origin
|
||||
|
||||
This call can only be made by the `VetoOrigin`.
|
||||
|
||||
- `veto_external` - Vetoes and blacklists the external proposal hash.
|
||||
|
||||
#### Root
|
||||
|
||||
- `cancel_referendum` - Removes a referendum.
|
||||
- `cancel_queued` - Cancels a proposal that is queued for enactment.
|
||||
- `clear_public_proposal` - Removes all public proposals.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,976 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Democracy pallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_support::{
|
||||
assert_noop, assert_ok,
|
||||
traits::{Currency, EnsureOrigin, Get, OnInitialize, UnfilteredDispatchable},
|
||||
};
|
||||
use pezframe_system::{pezpallet_prelude::BlockNumberFor, RawOrigin};
|
||||
use pezsp_runtime::{traits::Bounded, BoundedVec};
|
||||
|
||||
use crate::Pallet as Democracy;
|
||||
|
||||
const REFERENDUM_COUNT_HINT: u32 = 10;
|
||||
const SEED: u32 = 0;
|
||||
|
||||
fn funded_account<T: Config>(name: &'static str, index: u32) -> T::AccountId {
|
||||
let caller: T::AccountId = account(name, index, SEED);
|
||||
// Give the account half of the maximum value of the `Balance` type.
|
||||
// Otherwise some transfers will fail with an overflow error.
|
||||
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value() / 2u32.into());
|
||||
caller
|
||||
}
|
||||
|
||||
fn make_proposal<T: Config>(n: u32) -> BoundedCallOf<T> {
|
||||
let call: CallOf<T> = pezframe_system::Call::remark { remark: n.encode() }.into();
|
||||
<T as Config>::Preimages::bound(call).unwrap()
|
||||
}
|
||||
|
||||
fn add_proposal<T: Config>(n: u32) -> Result<T::Hash, &'static str> {
|
||||
let other = funded_account::<T>("proposer", n);
|
||||
let value = T::MinimumDeposit::get();
|
||||
let proposal = make_proposal::<T>(n);
|
||||
Democracy::<T>::propose(RawOrigin::Signed(other).into(), proposal.clone(), value)?;
|
||||
Ok(proposal.hash())
|
||||
}
|
||||
|
||||
// add a referendum with a metadata.
|
||||
fn add_referendum<T: Config>(n: u32) -> (ReferendumIndex, T::Hash, T::Hash) {
|
||||
let vote_threshold = VoteThreshold::SimpleMajority;
|
||||
let proposal = make_proposal::<T>(n);
|
||||
let hash = proposal.hash();
|
||||
let index = Democracy::<T>::inject_referendum(
|
||||
T::LaunchPeriod::get(),
|
||||
proposal,
|
||||
vote_threshold,
|
||||
0u32.into(),
|
||||
);
|
||||
let preimage_hash = note_preimage::<T>();
|
||||
MetadataOf::<T>::insert(crate::MetadataOwner::Referendum(index), preimage_hash);
|
||||
(index, hash, preimage_hash)
|
||||
}
|
||||
|
||||
fn account_vote<T: Config>(b: BalanceOf<T>) -> AccountVote<BalanceOf<T>> {
|
||||
let v = Vote { aye: true, conviction: Conviction::Locked1x };
|
||||
|
||||
AccountVote::Standard { vote: v, balance: b }
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
pezframe_system::Pallet::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
pezframe_system::Pallet::<T>::assert_has_event(generic_event.into());
|
||||
}
|
||||
|
||||
// note a new preimage.
|
||||
fn note_preimage<T: Config>() -> T::Hash {
|
||||
use alloc::borrow::Cow;
|
||||
use core::sync::atomic::{AtomicU8, Ordering};
|
||||
// note a new preimage on every function invoke.
|
||||
static COUNTER: AtomicU8 = AtomicU8::new(0);
|
||||
let data = Cow::from(vec![COUNTER.fetch_add(1, Ordering::Relaxed)]);
|
||||
let hash = <T as Config>::Preimages::note(data).unwrap();
|
||||
hash
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn propose() -> Result<(), BenchmarkError> {
|
||||
let p = T::MaxProposals::get();
|
||||
|
||||
for i in 0..(p - 1) {
|
||||
add_proposal::<T>(i)?;
|
||||
}
|
||||
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
let proposal = make_proposal::<T>(0);
|
||||
let value = T::MinimumDeposit::get();
|
||||
whitelist_account!(caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller), proposal, value);
|
||||
|
||||
assert_eq!(PublicProps::<T>::get().len(), p as usize, "Proposals not created.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn second() -> Result<(), BenchmarkError> {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
add_proposal::<T>(0)?;
|
||||
|
||||
// Create s existing "seconds"
|
||||
// we must reserve one deposit for the `proposal` and one for our benchmarked `second` call.
|
||||
for i in 0..T::MaxDeposits::get() - 2 {
|
||||
let seconder = funded_account::<T>("seconder", i);
|
||||
Democracy::<T>::second(RawOrigin::Signed(seconder).into(), 0)?;
|
||||
}
|
||||
|
||||
let deposits = DepositOf::<T>::get(0).ok_or("Proposal not created")?;
|
||||
assert_eq!(deposits.0.len(), (T::MaxDeposits::get() - 1) as usize, "Seconds not recorded");
|
||||
whitelist_account!(caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller), 0);
|
||||
|
||||
let deposits = DepositOf::<T>::get(0).ok_or("Proposal not created")?;
|
||||
assert_eq!(
|
||||
deposits.0.len(),
|
||||
(T::MaxDeposits::get()) as usize,
|
||||
"`second` benchmark did not work"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn vote_new() -> Result<(), BenchmarkError> {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
let account_vote = account_vote::<T>(100u32.into());
|
||||
|
||||
// We need to create existing direct votes
|
||||
for i in 0..T::MaxVotes::get() - 1 {
|
||||
let ref_index = add_referendum::<T>(i).0;
|
||||
Democracy::<T>::vote(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
ref_index,
|
||||
account_vote,
|
||||
)?;
|
||||
}
|
||||
let votes = match VotingOf::<T>::get(&caller) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(votes.len(), (T::MaxVotes::get() - 1) as usize, "Votes were not recorded.");
|
||||
|
||||
let ref_index = add_referendum::<T>(T::MaxVotes::get() - 1).0;
|
||||
whitelist_account!(caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
vote(RawOrigin::Signed(caller.clone()), ref_index, account_vote);
|
||||
|
||||
let votes = match VotingOf::<T>::get(&caller) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
|
||||
assert_eq!(votes.len(), T::MaxVotes::get() as usize, "Vote was not recorded.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn vote_existing() -> Result<(), BenchmarkError> {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
let account_vote = account_vote::<T>(100u32.into());
|
||||
|
||||
// We need to create existing direct votes
|
||||
for i in 0..T::MaxVotes::get() {
|
||||
let ref_index = add_referendum::<T>(i).0;
|
||||
Democracy::<T>::vote(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
ref_index,
|
||||
account_vote,
|
||||
)?;
|
||||
}
|
||||
let votes = match VotingOf::<T>::get(&caller) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(votes.len(), T::MaxVotes::get() 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 ref_index = ReferendumCount::<T>::get() - 1;
|
||||
|
||||
// This tests when a user changes a vote
|
||||
whitelist_account!(caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
vote(RawOrigin::Signed(caller.clone()), ref_index, new_vote);
|
||||
|
||||
let votes = match VotingOf::<T>::get(&caller) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(votes.len(), T::MaxVotes::get() as usize, "Vote was incorrectly added");
|
||||
let referendum_info =
|
||||
ReferendumInfoOf::<T>::get(ref_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");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn emergency_cancel() -> Result<(), BenchmarkError> {
|
||||
let origin = T::CancellationOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let (ref_index, _, preimage_hash) = add_referendum::<T>(0);
|
||||
assert_ok!(Democracy::<T>::referendum_status(ref_index));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, ref_index);
|
||||
// Referendum has been canceled
|
||||
assert_noop!(Democracy::<T>::referendum_status(ref_index), Error::<T>::ReferendumInvalid,);
|
||||
assert_last_event::<T>(
|
||||
crate::Event::MetadataCleared {
|
||||
owner: MetadataOwner::Referendum(ref_index),
|
||||
hash: preimage_hash,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn blacklist() -> Result<(), BenchmarkError> {
|
||||
// Place our proposal at the end to make sure it's worst case.
|
||||
for i in 0..T::MaxProposals::get() - 1 {
|
||||
add_proposal::<T>(i)?;
|
||||
}
|
||||
// We should really add a lot of seconds here, but we're not doing it elsewhere.
|
||||
|
||||
// Add a referendum of our proposal.
|
||||
let (ref_index, hash, preimage_hash) = add_referendum::<T>(0);
|
||||
assert_ok!(Democracy::<T>::referendum_status(ref_index));
|
||||
// Place our proposal in the external queue, too.
|
||||
assert_ok!(Democracy::<T>::external_propose(
|
||||
T::ExternalOrigin::try_successful_origin()
|
||||
.expect("ExternalOrigin has no successful origin required for the benchmark"),
|
||||
make_proposal::<T>(0)
|
||||
));
|
||||
let origin =
|
||||
T::BlacklistOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, hash, Some(ref_index));
|
||||
|
||||
// Referendum has been canceled
|
||||
assert_noop!(Democracy::<T>::referendum_status(ref_index), Error::<T>::ReferendumInvalid);
|
||||
assert_has_event::<T>(
|
||||
crate::Event::MetadataCleared {
|
||||
owner: MetadataOwner::Referendum(ref_index),
|
||||
hash: preimage_hash,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Worst case scenario, we external propose a previously blacklisted proposal
|
||||
#[benchmark]
|
||||
fn external_propose() -> Result<(), BenchmarkError> {
|
||||
let origin =
|
||||
T::ExternalOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let proposal = make_proposal::<T>(0);
|
||||
// Add proposal to blacklist with block number 0
|
||||
|
||||
let addresses: BoundedVec<_, _> = (0..(T::MaxBlacklisted::get() - 1))
|
||||
.into_iter()
|
||||
.map(|i| account::<T::AccountId>("blacklist", i, SEED))
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
Blacklist::<T>::insert(proposal.hash(), (BlockNumberFor::<T>::zero(), addresses));
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, proposal);
|
||||
|
||||
// External proposal created
|
||||
ensure!(NextExternal::<T>::exists(), "External proposal didn't work");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn external_propose_majority() -> Result<(), BenchmarkError> {
|
||||
let origin = T::ExternalMajorityOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let proposal = make_proposal::<T>(0);
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, proposal);
|
||||
|
||||
// External proposal created
|
||||
ensure!(NextExternal::<T>::exists(), "External proposal didn't work");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn external_propose_default() -> Result<(), BenchmarkError> {
|
||||
let origin = T::ExternalDefaultOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let proposal = make_proposal::<T>(0);
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, proposal);
|
||||
|
||||
// External proposal created
|
||||
ensure!(NextExternal::<T>::exists(), "External proposal didn't work");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn fast_track() -> Result<(), BenchmarkError> {
|
||||
let origin_propose = T::ExternalDefaultOrigin::try_successful_origin()
|
||||
.expect("ExternalDefaultOrigin has no successful origin required for the benchmark");
|
||||
let proposal = make_proposal::<T>(0);
|
||||
let proposal_hash = proposal.hash();
|
||||
Democracy::<T>::external_propose_default(origin_propose.clone(), proposal)?;
|
||||
// Set metadata to the external proposal.
|
||||
let preimage_hash = note_preimage::<T>();
|
||||
assert_ok!(Democracy::<T>::set_metadata(
|
||||
origin_propose,
|
||||
MetadataOwner::External,
|
||||
Some(preimage_hash)
|
||||
));
|
||||
// NOTE: Instant origin may invoke a little bit more logic, but may not always succeed.
|
||||
let origin_fast_track =
|
||||
T::FastTrackOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let voting_period = T::FastTrackVotingPeriod::get();
|
||||
let delay = 0u32;
|
||||
#[extrinsic_call]
|
||||
_(origin_fast_track as T::RuntimeOrigin, proposal_hash, voting_period, delay.into());
|
||||
|
||||
assert_eq!(ReferendumCount::<T>::get(), 1, "referendum not created");
|
||||
assert_last_event::<T>(
|
||||
crate::Event::MetadataTransferred {
|
||||
prev_owner: MetadataOwner::External,
|
||||
owner: MetadataOwner::Referendum(0),
|
||||
hash: preimage_hash,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn veto_external() -> Result<(), BenchmarkError> {
|
||||
let proposal = make_proposal::<T>(0);
|
||||
let proposal_hash = proposal.hash();
|
||||
|
||||
let origin_propose = T::ExternalDefaultOrigin::try_successful_origin()
|
||||
.expect("ExternalDefaultOrigin has no successful origin required for the benchmark");
|
||||
Democracy::<T>::external_propose_default(origin_propose.clone(), proposal)?;
|
||||
|
||||
let preimage_hash = note_preimage::<T>();
|
||||
assert_ok!(Democracy::<T>::set_metadata(
|
||||
origin_propose,
|
||||
MetadataOwner::External,
|
||||
Some(preimage_hash)
|
||||
));
|
||||
|
||||
let mut vetoers: BoundedVec<T::AccountId, _> = Default::default();
|
||||
for i in 0..(T::MaxBlacklisted::get() - 1) {
|
||||
vetoers.try_push(account::<T::AccountId>("vetoer", i, SEED)).unwrap();
|
||||
}
|
||||
vetoers.sort();
|
||||
Blacklist::<T>::insert(proposal_hash, (BlockNumberFor::<T>::zero(), vetoers));
|
||||
|
||||
let origin =
|
||||
T::VetoOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
ensure!(NextExternal::<T>::get().is_some(), "no external proposal");
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, proposal_hash);
|
||||
|
||||
assert!(NextExternal::<T>::get().is_none());
|
||||
let (_, new_vetoers) = Blacklist::<T>::get(&proposal_hash).ok_or("no blacklist")?;
|
||||
assert_eq!(new_vetoers.len(), T::MaxBlacklisted::get() as usize, "vetoers not added");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn cancel_proposal() -> Result<(), BenchmarkError> {
|
||||
// Place our proposal at the end to make sure it's worst case.
|
||||
for i in 0..T::MaxProposals::get() {
|
||||
add_proposal::<T>(i)?;
|
||||
}
|
||||
// Add metadata to the first proposal.
|
||||
let proposer = funded_account::<T>("proposer", 0);
|
||||
let preimage_hash = note_preimage::<T>();
|
||||
assert_ok!(Democracy::<T>::set_metadata(
|
||||
RawOrigin::Signed(proposer).into(),
|
||||
MetadataOwner::Proposal(0),
|
||||
Some(preimage_hash)
|
||||
));
|
||||
let cancel_origin = T::CancelProposalOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
#[extrinsic_call]
|
||||
_(cancel_origin as T::RuntimeOrigin, 0);
|
||||
|
||||
assert_last_event::<T>(
|
||||
crate::Event::MetadataCleared {
|
||||
owner: MetadataOwner::Proposal(0),
|
||||
hash: preimage_hash,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn cancel_referendum() -> Result<(), BenchmarkError> {
|
||||
let (ref_index, _, preimage_hash) = add_referendum::<T>(0);
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, ref_index);
|
||||
|
||||
assert_last_event::<T>(
|
||||
crate::Event::MetadataCleared {
|
||||
owner: MetadataOwner::Referendum(0),
|
||||
hash: preimage_hash,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(extra)]
|
||||
fn on_initialize_external(r: Linear<0, REFERENDUM_COUNT_HINT>) -> Result<(), BenchmarkError> {
|
||||
for i in 0..r {
|
||||
add_referendum::<T>(i);
|
||||
}
|
||||
|
||||
assert_eq!(ReferendumCount::<T>::get(), r, "referenda not created");
|
||||
|
||||
// Launch external
|
||||
LastTabledWasExternal::<T>::put(false);
|
||||
|
||||
let origin = T::ExternalMajorityOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let proposal = make_proposal::<T>(r);
|
||||
let call = Call::<T>::external_propose_majority { proposal };
|
||||
call.dispatch_bypass_filter(origin)?;
|
||||
// External proposal created
|
||||
ensure!(NextExternal::<T>::exists(), "External proposal didn't work");
|
||||
|
||||
let block_number = T::LaunchPeriod::get();
|
||||
|
||||
#[block]
|
||||
{
|
||||
Democracy::<T>::on_initialize(block_number);
|
||||
}
|
||||
|
||||
// One extra because of next external
|
||||
assert_eq!(ReferendumCount::<T>::get(), r + 1, "referenda not created");
|
||||
ensure!(!NextExternal::<T>::exists(), "External wasn't taken");
|
||||
|
||||
// All but the new next external should be finished
|
||||
for i in 0..r {
|
||||
if let Some(value) = ReferendumInfoOf::<T>::get(i) {
|
||||
match value {
|
||||
ReferendumInfo::Finished { .. } => (),
|
||||
ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(extra)]
|
||||
fn on_initialize_public(
|
||||
r: Linear<0, { T::MaxVotes::get() - 1 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
for i in 0..r {
|
||||
add_referendum::<T>(i);
|
||||
}
|
||||
|
||||
assert_eq!(ReferendumCount::<T>::get(), r, "referenda not created");
|
||||
|
||||
// Launch public
|
||||
assert!(add_proposal::<T>(r).is_ok(), "proposal not created");
|
||||
LastTabledWasExternal::<T>::put(true);
|
||||
|
||||
let block_number = T::LaunchPeriod::get();
|
||||
|
||||
#[block]
|
||||
{
|
||||
Democracy::<T>::on_initialize(block_number);
|
||||
}
|
||||
|
||||
// One extra because of next public
|
||||
assert_eq!(ReferendumCount::<T>::get(), r + 1, "proposal not accepted");
|
||||
|
||||
// All should be finished
|
||||
for i in 0..r {
|
||||
if let Some(value) = ReferendumInfoOf::<T>::get(i) {
|
||||
match value {
|
||||
ReferendumInfo::Finished { .. } => (),
|
||||
ReferendumInfo::Ongoing(_) => return Err("Referendum was not finished".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// No launch no maturing referenda.
|
||||
#[benchmark]
|
||||
fn on_initialize_base(r: Linear<0, { T::MaxVotes::get() - 1 }>) -> Result<(), BenchmarkError> {
|
||||
for i in 0..r {
|
||||
add_referendum::<T>(i);
|
||||
}
|
||||
|
||||
for (key, mut info) in ReferendumInfoOf::<T>::iter() {
|
||||
if let ReferendumInfo::Ongoing(ref mut status) = info {
|
||||
status.end += 100u32.into();
|
||||
}
|
||||
ReferendumInfoOf::<T>::insert(key, info);
|
||||
}
|
||||
|
||||
assert_eq!(ReferendumCount::<T>::get(), r, "referenda not created");
|
||||
assert_eq!(LowestUnbaked::<T>::get(), 0, "invalid referenda init");
|
||||
|
||||
#[block]
|
||||
{
|
||||
Democracy::<T>::on_initialize(1u32.into());
|
||||
}
|
||||
|
||||
// All should be on going
|
||||
for i in 0..r {
|
||||
if let Some(value) = ReferendumInfoOf::<T>::get(i) {
|
||||
match value {
|
||||
ReferendumInfo::Finished { .. } =>
|
||||
return Err("Referendum has been finished".into()),
|
||||
ReferendumInfo::Ongoing(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn on_initialize_base_with_launch_period(
|
||||
r: Linear<0, { T::MaxVotes::get() - 1 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
for i in 0..r {
|
||||
add_referendum::<T>(i);
|
||||
}
|
||||
|
||||
for (key, mut info) in ReferendumInfoOf::<T>::iter() {
|
||||
if let ReferendumInfo::Ongoing(ref mut status) = info {
|
||||
status.end += 100u32.into();
|
||||
}
|
||||
ReferendumInfoOf::<T>::insert(key, info);
|
||||
}
|
||||
|
||||
assert_eq!(ReferendumCount::<T>::get(), r, "referenda not created");
|
||||
assert_eq!(LowestUnbaked::<T>::get(), 0, "invalid referenda init");
|
||||
|
||||
let block_number = T::LaunchPeriod::get();
|
||||
|
||||
#[block]
|
||||
{
|
||||
Democracy::<T>::on_initialize(block_number);
|
||||
}
|
||||
|
||||
// All should be on going
|
||||
for i in 0..r {
|
||||
if let Some(value) = ReferendumInfoOf::<T>::get(i) {
|
||||
match value {
|
||||
ReferendumInfo::Finished { .. } =>
|
||||
return Err("Referendum has been finished".into()),
|
||||
ReferendumInfo::Ongoing(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn delegate(r: Linear<0, { T::MaxVotes::get() - 1 }>) -> Result<(), BenchmarkError> {
|
||||
let initial_balance: BalanceOf<T> = 100u32.into();
|
||||
let delegated_balance: BalanceOf<T> = 1000u32.into();
|
||||
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
// Caller will initially delegate to `old_delegate`
|
||||
let old_delegate: T::AccountId = funded_account::<T>("old_delegate", r);
|
||||
let old_delegate_lookup = T::Lookup::unlookup(old_delegate.clone());
|
||||
Democracy::<T>::delegate(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
old_delegate_lookup,
|
||||
Conviction::Locked1x,
|
||||
delegated_balance,
|
||||
)?;
|
||||
let (target, balance) = match VotingOf::<T>::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::<T>("new_delegate", r);
|
||||
let new_delegate_lookup = T::Lookup::unlookup(new_delegate.clone());
|
||||
let account_vote = account_vote::<T>(initial_balance);
|
||||
// We need to create existing direct votes for the `new_delegate`
|
||||
for i in 0..r {
|
||||
let ref_index = add_referendum::<T>(i).0;
|
||||
Democracy::<T>::vote(
|
||||
RawOrigin::Signed(new_delegate.clone()).into(),
|
||||
ref_index,
|
||||
account_vote,
|
||||
)?;
|
||||
}
|
||||
let votes = match VotingOf::<T>::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);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(
|
||||
RawOrigin::Signed(caller.clone()),
|
||||
new_delegate_lookup,
|
||||
Conviction::Locked1x,
|
||||
delegated_balance,
|
||||
);
|
||||
|
||||
let (target, balance) = match VotingOf::<T>::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::<T>::get(&new_delegate) {
|
||||
Voting::Direct { delegations, .. } => delegations,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(delegations.capital, delegated_balance, "delegation was not recorded.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn undelegate(r: Linear<0, { T::MaxVotes::get() - 1 }>) -> Result<(), BenchmarkError> {
|
||||
let initial_balance: BalanceOf<T> = 100u32.into();
|
||||
let delegated_balance: BalanceOf<T> = 1000u32.into();
|
||||
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
// Caller will delegate
|
||||
let the_delegate: T::AccountId = funded_account::<T>("delegate", r);
|
||||
let the_delegate_lookup = T::Lookup::unlookup(the_delegate.clone());
|
||||
Democracy::<T>::delegate(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
the_delegate_lookup,
|
||||
Conviction::Locked1x,
|
||||
delegated_balance,
|
||||
)?;
|
||||
let (target, balance) = match VotingOf::<T>::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::<T>(initial_balance);
|
||||
for i in 0..r {
|
||||
let ref_index = add_referendum::<T>(i).0;
|
||||
Democracy::<T>::vote(
|
||||
RawOrigin::Signed(the_delegate.clone()).into(),
|
||||
ref_index,
|
||||
account_vote,
|
||||
)?;
|
||||
}
|
||||
let votes = match VotingOf::<T>::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);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()));
|
||||
|
||||
// Voting should now be direct
|
||||
match VotingOf::<T>::get(&caller) {
|
||||
Voting::Direct { .. } => (),
|
||||
_ => return Err("undelegation failed".into()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn clear_public_proposals() -> Result<(), BenchmarkError> {
|
||||
add_proposal::<T>(0)?;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test when unlock will remove locks
|
||||
#[benchmark]
|
||||
fn unlock_remove(r: Linear<0, { T::MaxVotes::get() - 1 }>) -> Result<(), BenchmarkError> {
|
||||
let locker = funded_account::<T>("locker", 0);
|
||||
let locker_lookup = T::Lookup::unlookup(locker.clone());
|
||||
// Populate votes so things are locked
|
||||
let base_balance: BalanceOf<T> = 100u32.into();
|
||||
let small_vote = account_vote::<T>(base_balance);
|
||||
// Vote and immediately unvote
|
||||
for i in 0..r {
|
||||
let ref_index = add_referendum::<T>(i).0;
|
||||
Democracy::<T>::vote(RawOrigin::Signed(locker.clone()).into(), ref_index, small_vote)?;
|
||||
Democracy::<T>::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_index)?;
|
||||
}
|
||||
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
unlock(RawOrigin::Signed(caller), locker_lookup);
|
||||
|
||||
// Note that we may want to add a `get_lock` api to actually verify
|
||||
let voting = VotingOf::<T>::get(&locker);
|
||||
assert_eq!(voting.locked_balance(), BalanceOf::<T>::zero());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test when unlock will set a new value
|
||||
#[benchmark]
|
||||
fn unlock_set(r: Linear<0, { T::MaxVotes::get() - 1 }>) -> Result<(), BenchmarkError> {
|
||||
let locker = funded_account::<T>("locker", 0);
|
||||
let locker_lookup = T::Lookup::unlookup(locker.clone());
|
||||
// Populate votes so things are locked
|
||||
let base_balance: BalanceOf<T> = 100u32.into();
|
||||
let small_vote = account_vote::<T>(base_balance);
|
||||
for i in 0..r {
|
||||
let ref_index = add_referendum::<T>(i).0;
|
||||
Democracy::<T>::vote(RawOrigin::Signed(locker.clone()).into(), ref_index, small_vote)?;
|
||||
}
|
||||
|
||||
// Create a big vote so lock increases
|
||||
let big_vote = account_vote::<T>(base_balance * 10u32.into());
|
||||
let ref_index = add_referendum::<T>(r).0;
|
||||
Democracy::<T>::vote(RawOrigin::Signed(locker.clone()).into(), ref_index, big_vote)?;
|
||||
|
||||
let votes = match VotingOf::<T>::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::<T>::get(&locker);
|
||||
assert_eq!(voting.locked_balance(), base_balance * 10u32.into());
|
||||
|
||||
Democracy::<T>::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_index)?;
|
||||
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
whitelist_account!(caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
unlock(RawOrigin::Signed(caller), locker_lookup);
|
||||
|
||||
let votes = match VotingOf::<T>::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::<T>::get(&locker);
|
||||
// Note that we may want to add a `get_lock` api to actually verify
|
||||
assert_eq!(voting.locked_balance(), if r > 0 { base_balance } else { 0u32.into() });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove_vote(r: Linear<1, { T::MaxVotes::get() }>) -> Result<(), BenchmarkError> {
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
let account_vote = account_vote::<T>(100u32.into());
|
||||
|
||||
for i in 0..r {
|
||||
let ref_index = add_referendum::<T>(i).0;
|
||||
Democracy::<T>::vote(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
ref_index,
|
||||
account_vote,
|
||||
)?;
|
||||
}
|
||||
|
||||
let votes = match VotingOf::<T>::get(&caller) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(votes.len(), r as usize, "Votes not created");
|
||||
|
||||
let ref_index = r - 1;
|
||||
whitelist_account!(caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), ref_index);
|
||||
|
||||
let votes = match VotingOf::<T>::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");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Worst case is when target == caller and referendum is ongoing
|
||||
#[benchmark]
|
||||
fn remove_other_vote(r: Linear<1, { T::MaxVotes::get() }>) -> Result<(), BenchmarkError> {
|
||||
let caller = funded_account::<T>("caller", r);
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
let account_vote = account_vote::<T>(100u32.into());
|
||||
|
||||
for i in 0..r {
|
||||
let ref_index = add_referendum::<T>(i).0;
|
||||
Democracy::<T>::vote(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
ref_index,
|
||||
account_vote,
|
||||
)?;
|
||||
}
|
||||
|
||||
let votes = match VotingOf::<T>::get(&caller) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct".into()),
|
||||
};
|
||||
assert_eq!(votes.len(), r as usize, "Votes not created");
|
||||
|
||||
let ref_index = r - 1;
|
||||
whitelist_account!(caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), caller_lookup, ref_index);
|
||||
|
||||
let votes = match VotingOf::<T>::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");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_external_metadata() -> Result<(), BenchmarkError> {
|
||||
let origin = T::ExternalOrigin::try_successful_origin()
|
||||
.expect("ExternalOrigin has no successful origin required for the benchmark");
|
||||
assert_ok!(Democracy::<T>::external_propose(origin.clone(), make_proposal::<T>(0)));
|
||||
let owner = MetadataOwner::External;
|
||||
let hash = note_preimage::<T>();
|
||||
|
||||
#[extrinsic_call]
|
||||
set_metadata(origin as T::RuntimeOrigin, owner.clone(), Some(hash));
|
||||
|
||||
assert_last_event::<T>(crate::Event::MetadataSet { owner, hash }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn clear_external_metadata() -> Result<(), BenchmarkError> {
|
||||
let origin = T::ExternalOrigin::try_successful_origin()
|
||||
.expect("ExternalOrigin has no successful origin required for the benchmark");
|
||||
assert_ok!(Democracy::<T>::external_propose(origin.clone(), make_proposal::<T>(0)));
|
||||
let owner = MetadataOwner::External;
|
||||
let _proposer = funded_account::<T>("proposer", 0);
|
||||
let hash = note_preimage::<T>();
|
||||
assert_ok!(Democracy::<T>::set_metadata(origin.clone(), owner.clone(), Some(hash)));
|
||||
|
||||
#[extrinsic_call]
|
||||
set_metadata(origin as T::RuntimeOrigin, owner.clone(), None);
|
||||
|
||||
assert_last_event::<T>(crate::Event::MetadataCleared { owner, hash }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_proposal_metadata() -> Result<(), BenchmarkError> {
|
||||
// Place our proposal at the end to make sure it's worst case.
|
||||
for i in 0..T::MaxProposals::get() {
|
||||
add_proposal::<T>(i)?;
|
||||
}
|
||||
let owner = MetadataOwner::Proposal(0);
|
||||
let proposer = funded_account::<T>("proposer", 0);
|
||||
let hash = note_preimage::<T>();
|
||||
|
||||
#[extrinsic_call]
|
||||
set_metadata(RawOrigin::Signed(proposer), owner.clone(), Some(hash));
|
||||
|
||||
assert_last_event::<T>(crate::Event::MetadataSet { owner, hash }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn clear_proposal_metadata() -> Result<(), BenchmarkError> {
|
||||
// Place our proposal at the end to make sure it's worst case.
|
||||
for i in 0..T::MaxProposals::get() {
|
||||
add_proposal::<T>(i)?;
|
||||
}
|
||||
let proposer = funded_account::<T>("proposer", 0);
|
||||
let owner = MetadataOwner::Proposal(0);
|
||||
let hash = note_preimage::<T>();
|
||||
assert_ok!(Democracy::<T>::set_metadata(
|
||||
RawOrigin::Signed(proposer.clone()).into(),
|
||||
owner.clone(),
|
||||
Some(hash)
|
||||
));
|
||||
|
||||
#[extrinsic_call]
|
||||
set_metadata::<T::RuntimeOrigin>(RawOrigin::Signed(proposer), owner.clone(), None);
|
||||
|
||||
assert_last_event::<T>(crate::Event::MetadataCleared { owner, hash }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_referendum_metadata() -> Result<(), BenchmarkError> {
|
||||
// create not ongoing referendum.
|
||||
ReferendumInfoOf::<T>::insert(
|
||||
0,
|
||||
ReferendumInfo::Finished { end: BlockNumberFor::<T>::zero(), approved: true },
|
||||
);
|
||||
let owner = MetadataOwner::Referendum(0);
|
||||
let _caller = funded_account::<T>("caller", 0);
|
||||
let hash = note_preimage::<T>();
|
||||
|
||||
#[extrinsic_call]
|
||||
set_metadata::<T::RuntimeOrigin>(RawOrigin::Root, owner.clone(), Some(hash));
|
||||
|
||||
assert_last_event::<T>(crate::Event::MetadataSet { owner, hash }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn clear_referendum_metadata() -> Result<(), BenchmarkError> {
|
||||
// create not ongoing referendum.
|
||||
ReferendumInfoOf::<T>::insert(
|
||||
0,
|
||||
ReferendumInfo::Finished { end: BlockNumberFor::<T>::zero(), approved: true },
|
||||
);
|
||||
let owner = MetadataOwner::Referendum(0);
|
||||
let hash = note_preimage::<T>();
|
||||
MetadataOf::<T>::insert(owner.clone(), hash);
|
||||
let caller = funded_account::<T>("caller", 0);
|
||||
|
||||
#[extrinsic_call]
|
||||
set_metadata::<T::RuntimeOrigin>(RawOrigin::Signed(caller), owner.clone(), None);
|
||||
|
||||
assert_last_event::<T>(crate::Event::MetadataCleared { owner, hash }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Democracy, crate::tests::new_test_ext(), crate::tests::Test);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The conviction datatype.
|
||||
|
||||
use crate::types::Delegations;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use core::result::Result;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::{
|
||||
traits::{Bounded, CheckedDiv, CheckedMul, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
|
||||
/// A value denoting the strength of conviction of a vote.
|
||||
#[derive(
|
||||
Encode,
|
||||
MaxEncodedLen,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
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<Conviction> for u8 {
|
||||
fn from(c: Conviction) -> u8 {
|
||||
match c {
|
||||
Conviction::None => 0,
|
||||
Conviction::Locked1x => 1,
|
||||
Conviction::Locked2x => 2,
|
||||
Conviction::Locked3x => 3,
|
||||
Conviction::Locked4x => 4,
|
||||
Conviction::Locked5x => 5,
|
||||
Conviction::Locked6x => 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Conviction {
|
||||
type Error = ();
|
||||
fn try_from(i: u8) -> Result<Conviction, ()> {
|
||||
Ok(match i {
|
||||
0 => Conviction::None,
|
||||
1 => Conviction::Locked1x,
|
||||
2 => Conviction::Locked2x,
|
||||
3 => Conviction::Locked3x,
|
||||
4 => Conviction::Locked4x,
|
||||
5 => Conviction::Locked5x,
|
||||
6 => Conviction::Locked6x,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Conviction {
|
||||
/// The amount of time (in number of periods) that our conviction implies a successful voter's
|
||||
/// balance should be locked for.
|
||||
pub fn lock_periods(self) -> u32 {
|
||||
match self {
|
||||
Conviction::None => 0,
|
||||
Conviction::Locked1x => 1,
|
||||
Conviction::Locked2x => 2,
|
||||
Conviction::Locked3x => 4,
|
||||
Conviction::Locked4x => 8,
|
||||
Conviction::Locked5x => 16,
|
||||
Conviction::Locked6x => 32,
|
||||
}
|
||||
}
|
||||
|
||||
/// The votes of a voter of the given `balance` with our conviction.
|
||||
pub fn votes<B: From<u8> + Zero + Copy + CheckedMul + CheckedDiv + Bounded>(
|
||||
self,
|
||||
capital: B,
|
||||
) -> Delegations<B> {
|
||||
let votes = match self {
|
||||
Conviction::None => capital.checked_div(&10u8.into()).unwrap_or_else(Zero::zero),
|
||||
x => capital.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value),
|
||||
};
|
||||
Delegations { votes, capital }
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded for Conviction {
|
||||
fn min_value() -> Self {
|
||||
Conviction::None
|
||||
}
|
||||
fn max_value() -> Self {
|
||||
Conviction::Locked6x
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! All migrations of this pallet.
|
||||
|
||||
/// Migration to unlock and unreserve all pallet funds.
|
||||
pub mod unlock_and_unreserve_all_funds;
|
||||
|
||||
/// V1 storage migrations for the preimage pallet.
|
||||
pub mod v1;
|
||||
@@ -0,0 +1,430 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A migration that unreserves all deposit and unlocks all stake held in the context of this
|
||||
//! pallet.
|
||||
|
||||
use crate::{PropIndex, Voting, DEMOCRACY_ID};
|
||||
use alloc::{collections::btree_map::BTreeMap, vec::Vec};
|
||||
use core::iter::Sum;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::ValueQuery,
|
||||
storage_alias,
|
||||
traits::{Currency, LockableCurrency, OnRuntimeUpgrade, ReservableCurrency},
|
||||
weights::RuntimeDbWeight,
|
||||
Parameter, Twox64Concat,
|
||||
};
|
||||
use pezsp_core::Get;
|
||||
use pezsp_runtime::{traits::Zero, BoundedVec, Saturating};
|
||||
|
||||
const LOG_TARGET: &str = "runtime::democracy::migrations::unlock_and_unreserve_all_funds";
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as UnlockConfig>::Currency as Currency<<T as UnlockConfig>::AccountId>>::Balance;
|
||||
|
||||
/// The configuration for [`UnlockAndUnreserveAllFunds`].
|
||||
pub trait UnlockConfig: 'static {
|
||||
/// The account ID used in the runtime.
|
||||
type AccountId: Parameter + Ord;
|
||||
/// The currency type used in the runtime.
|
||||
///
|
||||
/// Should match the currency type previously used for the pallet, if applicable.
|
||||
type Currency: LockableCurrency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
|
||||
/// The name of the pallet as previously configured in
|
||||
/// [`construct_runtime!`](pezframe_support::construct_runtime).
|
||||
type PalletName: Get<&'static str>;
|
||||
/// The maximum number of votes as configured previously in the runtime.
|
||||
type MaxVotes: Get<u32>;
|
||||
/// The maximum deposit as configured previously in the runtime.
|
||||
type MaxDeposits: Get<u32>;
|
||||
/// The DB weight as configured in the runtime to calculate the correct weight.
|
||||
type DbWeight: Get<RuntimeDbWeight>;
|
||||
/// The block number as configured in the runtime.
|
||||
type BlockNumber: Parameter + Zero + Copy + Ord;
|
||||
}
|
||||
|
||||
#[storage_alias(dynamic)]
|
||||
type DepositOf<T: UnlockConfig> = StorageMap<
|
||||
<T as UnlockConfig>::PalletName,
|
||||
Twox64Concat,
|
||||
PropIndex,
|
||||
(BoundedVec<<T as UnlockConfig>::AccountId, <T as UnlockConfig>::MaxDeposits>, BalanceOf<T>),
|
||||
>;
|
||||
|
||||
#[storage_alias(dynamic)]
|
||||
type VotingOf<T: UnlockConfig> = StorageMap<
|
||||
<T as UnlockConfig>::PalletName,
|
||||
Twox64Concat,
|
||||
<T as UnlockConfig>::AccountId,
|
||||
Voting<
|
||||
BalanceOf<T>,
|
||||
<T as UnlockConfig>::AccountId,
|
||||
<T as UnlockConfig>::BlockNumber,
|
||||
<T as UnlockConfig>::MaxVotes,
|
||||
>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
/// A migration that unreserves all deposit and unlocks all stake held in the context of this
|
||||
/// pallet.
|
||||
///
|
||||
/// Useful to prevent funds from being locked up when the pallet is being deprecated.
|
||||
///
|
||||
/// The pallet should be made inoperable before this migration is run.
|
||||
///
|
||||
/// (See also [`RemovePallet`][pezframe_support::migrations::RemovePallet])
|
||||
pub struct UnlockAndUnreserveAllFunds<T: UnlockConfig>(core::marker::PhantomData<T>);
|
||||
|
||||
impl<T: UnlockConfig> UnlockAndUnreserveAllFunds<T> {
|
||||
/// Calculates and returns the total amounts reserved by each account by this pallet, and all
|
||||
/// accounts with locks in the context of this pallet.
|
||||
///
|
||||
/// There is no need to return the amount locked, because the entire lock is removed (always
|
||||
/// should be zero post-migration). We need to return the amounts reserved to check that the
|
||||
/// reserved amount is deducted correctly.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// This function returns a tuple of two `BTreeMap` collections and the weight of the reads:
|
||||
///
|
||||
/// * `BTreeMap<T::AccountId, BalanceOf<T>>`: Map of account IDs to their respective total
|
||||
/// reserved balance by this pallet
|
||||
/// * `BTreeMap<T::AccountId, BalanceOf<T>>`: Map of account IDs to their respective total
|
||||
/// locked balance by this pallet
|
||||
/// * `pezframe_support::weights::Weight`: the weight consumed by this call.
|
||||
fn get_account_deposits_and_locks() -> (
|
||||
BTreeMap<T::AccountId, BalanceOf<T>>,
|
||||
BTreeMap<T::AccountId, BalanceOf<T>>,
|
||||
pezframe_support::weights::Weight,
|
||||
) {
|
||||
let mut deposit_of_len = 0;
|
||||
|
||||
// Get all deposits (reserved).
|
||||
let mut total_voting_vec_entries: u64 = 0;
|
||||
let account_deposits: BTreeMap<T::AccountId, BalanceOf<T>> = DepositOf::<T>::iter()
|
||||
.flat_map(|(_prop_index, (accounts, balance))| {
|
||||
// Count the number of deposits
|
||||
deposit_of_len.saturating_inc();
|
||||
|
||||
// Track the total number of vec entries to calculate the weight of the reads.
|
||||
total_voting_vec_entries.saturating_accrue(accounts.len() as u64);
|
||||
|
||||
// Create a vec of tuples where each account is associated with the given balance
|
||||
accounts.into_iter().map(|account| (account, balance)).collect::<Vec<_>>()
|
||||
})
|
||||
.fold(BTreeMap::new(), |mut acc, (account, balance)| {
|
||||
// Add the balance to the account's existing balance in the accumulator
|
||||
acc.entry(account.clone()).or_insert(Zero::zero()).saturating_accrue(balance);
|
||||
acc
|
||||
});
|
||||
|
||||
// Voter accounts have amounts locked.
|
||||
let account_stakes: BTreeMap<T::AccountId, BalanceOf<T>> = VotingOf::<T>::iter()
|
||||
.map(|(account_id, voting)| (account_id, voting.locked_balance()))
|
||||
.collect();
|
||||
let voting_of_len = account_stakes.len() as u64;
|
||||
|
||||
(
|
||||
account_deposits,
|
||||
account_stakes,
|
||||
T::DbWeight::get().reads(
|
||||
deposit_of_len.saturating_add(voting_of_len).saturating_add(
|
||||
// Max items in a Voting enum is MaxVotes + 5
|
||||
total_voting_vec_entries
|
||||
.saturating_mul(T::MaxVotes::get().saturating_add(5) as u64),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UnlockConfig> OnRuntimeUpgrade for UnlockAndUnreserveAllFunds<T>
|
||||
where
|
||||
BalanceOf<T>: Sum,
|
||||
{
|
||||
/// Collects pre-migration data useful for validating the migration was successful, and also
|
||||
/// checks the integrity of deposited and reserved balances.
|
||||
///
|
||||
/// Steps:
|
||||
/// 1. Gets the deposited balances for each account stored in this pallet.
|
||||
/// 2. Collects actual pre-migration reserved balances for each account.
|
||||
/// 3. Checks the integrity of the deposited balances.
|
||||
/// 4. Prints summary statistics about the state to be migrated.
|
||||
/// 5. Encodes and returns pre-migration data to be used in post_upgrade.
|
||||
///
|
||||
/// Fails with a `TryRuntimeError` if somehow the amount reserved by this pallet is greater than
|
||||
/// the actual total reserved amount for any accounts.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::TryRuntimeError> {
|
||||
use alloc::collections::btree_set::BTreeSet;
|
||||
use codec::Encode;
|
||||
|
||||
// Get staked and deposited balances as reported by this pallet.
|
||||
let (account_deposits, account_locks, _) = Self::get_account_deposits_and_locks();
|
||||
|
||||
let all_accounts = account_deposits
|
||||
.keys()
|
||||
.chain(account_locks.keys())
|
||||
.cloned()
|
||||
.collect::<BTreeSet<_>>();
|
||||
let account_reserved_before: BTreeMap<T::AccountId, BalanceOf<T>> = account_deposits
|
||||
.keys()
|
||||
.map(|account| (account.clone(), T::Currency::reserved_balance(&account)))
|
||||
.collect();
|
||||
|
||||
// Total deposited for each account *should* be less than or equal to the total reserved,
|
||||
// however this does not hold for all cases due to bugs in the reserve logic of this pallet.
|
||||
let bugged_deposits = all_accounts
|
||||
.iter()
|
||||
.filter(|account| {
|
||||
account_deposits.get(&account).unwrap_or(&Zero::zero()) >
|
||||
account_reserved_before.get(&account).unwrap_or(&Zero::zero())
|
||||
})
|
||||
.count();
|
||||
|
||||
let total_deposits_to_unreserve =
|
||||
account_deposits.clone().into_values().sum::<BalanceOf<T>>();
|
||||
let total_stake_to_unlock = account_locks.clone().into_values().sum::<BalanceOf<T>>();
|
||||
|
||||
log::info!(target: LOG_TARGET, "Total accounts: {:?}", all_accounts.len());
|
||||
log::info!(target: LOG_TARGET, "Total stake to unlock: {:?}", total_stake_to_unlock);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Total deposit to unreserve: {:?}",
|
||||
total_deposits_to_unreserve
|
||||
);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Bugged deposits: {}/{}",
|
||||
bugged_deposits,
|
||||
account_deposits.len()
|
||||
);
|
||||
|
||||
Ok(account_reserved_before.encode())
|
||||
}
|
||||
|
||||
/// Executes the migration.
|
||||
///
|
||||
/// Steps:
|
||||
/// 1. Retrieves the deposit and accounts with locks for the pallet.
|
||||
/// 2. Unreserves the deposited funds for each account.
|
||||
/// 3. Unlocks the staked funds for each account.
|
||||
fn on_runtime_upgrade() -> pezframe_support::weights::Weight {
|
||||
// Get staked and deposited balances as reported by this pallet.
|
||||
let (account_deposits, account_stakes, initial_reads) =
|
||||
Self::get_account_deposits_and_locks();
|
||||
|
||||
// Deposited funds need to be unreserved.
|
||||
for (account, unreserve_amount) in account_deposits.iter() {
|
||||
if unreserve_amount.is_zero() {
|
||||
log::warn!(target: LOG_TARGET, "Unexpected zero amount to unreserve!");
|
||||
continue;
|
||||
}
|
||||
T::Currency::unreserve(&account, *unreserve_amount);
|
||||
}
|
||||
|
||||
// Staked funds need to be unlocked.
|
||||
for account in account_stakes.keys() {
|
||||
T::Currency::remove_lock(DEMOCRACY_ID, account);
|
||||
}
|
||||
|
||||
T::DbWeight::get()
|
||||
.reads_writes(
|
||||
account_stakes.len().saturating_add(account_deposits.len()) as u64,
|
||||
account_stakes.len().saturating_add(account_deposits.len()) as u64,
|
||||
)
|
||||
.saturating_add(initial_reads)
|
||||
}
|
||||
|
||||
/// Performs post-upgrade sanity checks:
|
||||
///
|
||||
/// 1. No locks remain for this pallet in Balances.
|
||||
/// 2. The reserved balance for each account has been reduced by the expected amount.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(
|
||||
account_reserved_before_bytes: Vec<u8>,
|
||||
) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
use codec::Decode;
|
||||
|
||||
let account_reserved_before =
|
||||
BTreeMap::<T::AccountId, BalanceOf<T>>::decode(&mut &account_reserved_before_bytes[..])
|
||||
.map_err(|_| "Failed to decode account_reserved_before_bytes")?;
|
||||
|
||||
// Get staked and deposited balances as reported by this pallet.
|
||||
let (account_deposits, _, _) = Self::get_account_deposits_and_locks();
|
||||
|
||||
// Check that the reserved balance is reduced by the expected deposited amount.
|
||||
for (account, actual_reserved_before) in account_reserved_before {
|
||||
let actual_reserved_after = T::Currency::reserved_balance(&account);
|
||||
let expected_amount_deducted = *account_deposits
|
||||
.get(&account)
|
||||
.expect("account deposit must exist to be in pre_migration_data, qed");
|
||||
let expected_reserved_after =
|
||||
actual_reserved_before.saturating_sub(expected_amount_deducted);
|
||||
assert!(
|
||||
actual_reserved_after == expected_reserved_after,
|
||||
"Reserved balance for {:?} is incorrect. actual before: {:?}, actual after, {:?}, expected deducted: {:?}",
|
||||
account,
|
||||
actual_reserved_before,
|
||||
actual_reserved_after,
|
||||
expected_amount_deducted,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "try-runtime", test))]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
tests::{new_test_ext, Balances, Test},
|
||||
DepositOf, Voting, VotingOf,
|
||||
};
|
||||
use pezframe_support::{
|
||||
assert_ok, parameter_types,
|
||||
traits::{Currency, OnRuntimeUpgrade, ReservableCurrency, WithdrawReasons},
|
||||
BoundedVec,
|
||||
};
|
||||
use pezframe_system::pezpallet_prelude::BlockNumberFor;
|
||||
use pezsp_core::ConstU32;
|
||||
|
||||
parameter_types! {
|
||||
const PalletName: &'static str = "Democracy";
|
||||
}
|
||||
|
||||
struct UnlockConfigImpl;
|
||||
|
||||
impl super::UnlockConfig for UnlockConfigImpl {
|
||||
type Currency = Balances;
|
||||
type MaxVotes = ConstU32<100>;
|
||||
type MaxDeposits = ConstU32<1000>;
|
||||
type AccountId = u64;
|
||||
type BlockNumber = BlockNumberFor<Test>;
|
||||
type DbWeight = ();
|
||||
type PalletName = PalletName;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unreserve_works_for_depositor() {
|
||||
let depositor_0 = 10;
|
||||
let depositor_1 = 11;
|
||||
let deposit = 25;
|
||||
let depositor_0_initial_reserved = 0;
|
||||
let depositor_1_initial_reserved = 15;
|
||||
let initial_balance = 100_000;
|
||||
new_test_ext().execute_with(|| {
|
||||
// Set up initial state.
|
||||
<Test as crate::Config>::Currency::make_free_balance_be(&depositor_0, initial_balance);
|
||||
<Test as crate::Config>::Currency::make_free_balance_be(&depositor_1, initial_balance);
|
||||
assert_ok!(<Test as crate::Config>::Currency::reserve(
|
||||
&depositor_0,
|
||||
depositor_0_initial_reserved + deposit
|
||||
));
|
||||
assert_ok!(<Test as crate::Config>::Currency::reserve(
|
||||
&depositor_1,
|
||||
depositor_1_initial_reserved + deposit
|
||||
));
|
||||
let depositors =
|
||||
BoundedVec::<_, <Test as crate::Config>::MaxDeposits>::truncate_from(vec![
|
||||
depositor_0,
|
||||
depositor_1,
|
||||
]);
|
||||
DepositOf::<Test>::insert(0, (depositors, deposit));
|
||||
|
||||
// Sanity check: ensure initial reserved balance was set correctly.
|
||||
assert_eq!(
|
||||
<Test as crate::Config>::Currency::reserved_balance(&depositor_0),
|
||||
depositor_0_initial_reserved + deposit
|
||||
);
|
||||
assert_eq!(
|
||||
<Test as crate::Config>::Currency::reserved_balance(&depositor_1),
|
||||
depositor_1_initial_reserved + deposit
|
||||
);
|
||||
|
||||
// Run the migration.
|
||||
let bytes = UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::pre_upgrade()
|
||||
.unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e));
|
||||
UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::on_runtime_upgrade();
|
||||
assert_ok!(UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::post_upgrade(bytes));
|
||||
|
||||
// Assert the reserved balance was reduced by the expected amount.
|
||||
assert_eq!(
|
||||
<Test as crate::Config>::Currency::reserved_balance(&depositor_0),
|
||||
depositor_0_initial_reserved
|
||||
);
|
||||
assert_eq!(
|
||||
<Test as crate::Config>::Currency::reserved_balance(&depositor_1),
|
||||
depositor_1_initial_reserved
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unlock_works_for_voter() {
|
||||
let voter = 10;
|
||||
let stake = 25;
|
||||
let initial_locks = vec![(b"somethin", 10)];
|
||||
let initial_balance = 100_000;
|
||||
new_test_ext().execute_with(|| {
|
||||
// Set up initial state.
|
||||
<Test as crate::Config>::Currency::make_free_balance_be(&voter, initial_balance);
|
||||
for lock in initial_locks.clone() {
|
||||
<Test as crate::Config>::Currency::set_lock(
|
||||
*lock.0,
|
||||
&voter,
|
||||
lock.1,
|
||||
WithdrawReasons::all(),
|
||||
);
|
||||
}
|
||||
VotingOf::<Test>::insert(voter, Voting::default());
|
||||
<Test as crate::Config>::Currency::set_lock(
|
||||
DEMOCRACY_ID,
|
||||
&voter,
|
||||
stake,
|
||||
WithdrawReasons::all(),
|
||||
);
|
||||
|
||||
// Sanity check: ensure initial Balance state was set up correctly.
|
||||
let mut voter_all_locks = initial_locks.clone();
|
||||
voter_all_locks.push((&DEMOCRACY_ID, stake));
|
||||
assert_eq!(
|
||||
<Test as crate::Config>::Currency::locks(&voter)
|
||||
.iter()
|
||||
.map(|lock| (&lock.id, lock.amount))
|
||||
.collect::<Vec<_>>(),
|
||||
voter_all_locks
|
||||
);
|
||||
|
||||
// Run the migration.
|
||||
let bytes = UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::pre_upgrade()
|
||||
.unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e));
|
||||
UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::on_runtime_upgrade();
|
||||
assert_ok!(UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::post_upgrade(bytes));
|
||||
|
||||
// Assert the voter lock was removed
|
||||
assert_eq!(
|
||||
<Test as crate::Config>::Currency::locks(&voter)
|
||||
.iter()
|
||||
.map(|lock| (&lock.id, lock.amount))
|
||||
.collect::<Vec<_>>(),
|
||||
initial_locks
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Storage migrations for the preimage pallet.
|
||||
|
||||
use crate::*;
|
||||
use pezframe_support::{pezpallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade, BoundedVec};
|
||||
use pezframe_system::pezpallet_prelude::BlockNumberFor;
|
||||
use pezsp_core::H256;
|
||||
|
||||
/// The log target.
|
||||
const TARGET: &'static str = "runtime::democracy::migration::v1";
|
||||
|
||||
/// The original data layout of the democracy pallet without a specific version number.
|
||||
mod v0 {
|
||||
use super::*;
|
||||
|
||||
#[storage_alias]
|
||||
pub type PublicProps<T: Config> = StorageValue<
|
||||
Pallet<T>,
|
||||
Vec<(PropIndex, <T as pezframe_system::Config>::Hash, <T as pezframe_system::Config>::AccountId)>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[storage_alias]
|
||||
pub type NextExternal<T: Config> =
|
||||
StorageValue<Pallet<T>, (<T as pezframe_system::Config>::Hash, VoteThreshold)>;
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
#[storage_alias]
|
||||
pub type ReferendumInfoOf<T: Config> = StorageMap<
|
||||
Pallet<T>,
|
||||
pezframe_support::Twox64Concat,
|
||||
ReferendumIndex,
|
||||
ReferendumInfo<BlockNumberFor<T>, <T as pezframe_system::Config>::Hash, BalanceOf<T>>,
|
||||
>;
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
use super::*;
|
||||
|
||||
/// Migration for translating bare `Hash`es into `Bounded<Call>`s.
|
||||
pub struct Migration<T>(core::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Config + pezframe_system::Config<Hash = H256>> OnRuntimeUpgrade for Migration<T> {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::TryRuntimeError> {
|
||||
ensure!(StorageVersion::get::<Pallet<T>>() == 0, "can only upgrade from version 0");
|
||||
|
||||
let props_count = v0::PublicProps::<T>::get().len();
|
||||
log::info!(target: TARGET, "{} public proposals will be migrated.", props_count,);
|
||||
ensure!(props_count <= T::MaxProposals::get() as usize, Error::<T>::TooMany);
|
||||
|
||||
let referenda_count = v0::ReferendumInfoOf::<T>::iter().count();
|
||||
log::info!(target: TARGET, "{} referenda will be migrated.", referenda_count);
|
||||
|
||||
Ok((props_count as u32, referenda_count as u32).encode())
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
if StorageVersion::get::<Pallet<T>>() != 0 {
|
||||
log::warn!(
|
||||
target: TARGET,
|
||||
"skipping on_runtime_upgrade: executed on wrong storage version.\
|
||||
Expected version 0"
|
||||
);
|
||||
return weight;
|
||||
}
|
||||
|
||||
ReferendumInfoOf::<T>::translate(
|
||||
|index, old: ReferendumInfo<BlockNumberFor<T>, T::Hash, BalanceOf<T>>| {
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
log::info!(target: TARGET, "migrating referendum #{:?}", &index);
|
||||
Some(match old {
|
||||
ReferendumInfo::Ongoing(status) =>
|
||||
ReferendumInfo::Ongoing(ReferendumStatus {
|
||||
end: status.end,
|
||||
proposal: Bounded::from_legacy_hash(status.proposal),
|
||||
threshold: status.threshold,
|
||||
delay: status.delay,
|
||||
tally: status.tally,
|
||||
}),
|
||||
ReferendumInfo::Finished { approved, end } =>
|
||||
ReferendumInfo::Finished { approved, end },
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
let props = v0::PublicProps::<T>::take()
|
||||
.into_iter()
|
||||
.map(|(i, hash, a)| (i, Bounded::from_legacy_hash(hash), a))
|
||||
.collect::<Vec<_>>();
|
||||
let bounded = BoundedVec::<_, T::MaxProposals>::truncate_from(props.clone());
|
||||
PublicProps::<T>::put(bounded);
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
|
||||
|
||||
if props.len() as u32 > T::MaxProposals::get() {
|
||||
log::error!(
|
||||
target: TARGET,
|
||||
"truncated {} public proposals to {}; continuing",
|
||||
props.len(),
|
||||
T::MaxProposals::get()
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((hash, threshold)) = v0::NextExternal::<T>::take() {
|
||||
log::info!(target: TARGET, "migrating next external proposal");
|
||||
NextExternal::<T>::put((Bounded::from_legacy_hash(hash), threshold));
|
||||
}
|
||||
|
||||
StorageVersion::new(1).put::<Pallet<T>>();
|
||||
|
||||
weight.saturating_add(T::DbWeight::get().reads_writes(1, 3))
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
ensure!(StorageVersion::get::<Pallet<T>>() == 1, "must upgrade");
|
||||
|
||||
let (old_props_count, old_ref_count): (u32, u32) =
|
||||
Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed");
|
||||
let new_props_count = crate::PublicProps::<T>::get().len() as u32;
|
||||
ensure!(new_props_count == old_props_count, "must migrate all public proposals");
|
||||
let new_ref_count = crate::ReferendumInfoOf::<T>::iter().count() as u32;
|
||||
ensure!(new_ref_count == old_ref_count, "must migrate all referenda");
|
||||
|
||||
log::info!(
|
||||
target: TARGET,
|
||||
"{} public proposals migrated, {} referenda migrated",
|
||||
new_props_count,
|
||||
new_ref_count,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "try-runtime")]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
tests::{Test as T, *},
|
||||
types::*,
|
||||
};
|
||||
use pezsp_runtime::bounded_vec;
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[test]
|
||||
fn migration_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(StorageVersion::get::<Pallet<T>>(), 0);
|
||||
// Insert some values into the v0 storage:
|
||||
|
||||
// Case 1: Ongoing referendum
|
||||
let hash = H256::repeat_byte(1);
|
||||
let status = ReferendumStatus {
|
||||
end: 1u32.into(),
|
||||
proposal: hash,
|
||||
threshold: VoteThreshold::SuperMajorityApprove,
|
||||
delay: 1u32.into(),
|
||||
tally: Tally { ayes: 1u32.into(), nays: 1u32.into(), turnout: 1u32.into() },
|
||||
};
|
||||
v0::ReferendumInfoOf::<T>::insert(1u32, ReferendumInfo::Ongoing(status));
|
||||
|
||||
// Case 2: Finished referendum
|
||||
v0::ReferendumInfoOf::<T>::insert(
|
||||
2u32,
|
||||
ReferendumInfo::Finished { approved: true, end: 123u32.into() },
|
||||
);
|
||||
|
||||
// Case 3: Public proposals
|
||||
let hash2 = H256::repeat_byte(2);
|
||||
v0::PublicProps::<T>::put(vec![(3u32, hash, 123u64), (4u32, hash2, 123u64)]);
|
||||
|
||||
// Case 4: Next external
|
||||
v0::NextExternal::<T>::put((hash, VoteThreshold::SuperMajorityApprove));
|
||||
|
||||
// Migrate.
|
||||
let state = v1::Migration::<T>::pre_upgrade().unwrap();
|
||||
let _weight = v1::Migration::<T>::on_runtime_upgrade();
|
||||
v1::Migration::<T>::post_upgrade(state).unwrap();
|
||||
// Check that all values got migrated.
|
||||
|
||||
// Case 1: Ongoing referendum
|
||||
assert_eq!(
|
||||
ReferendumInfoOf::<T>::get(1u32),
|
||||
Some(ReferendumInfo::Ongoing(ReferendumStatus {
|
||||
end: 1u32.into(),
|
||||
proposal: Bounded::from_legacy_hash(hash),
|
||||
threshold: VoteThreshold::SuperMajorityApprove,
|
||||
delay: 1u32.into(),
|
||||
tally: Tally { ayes: 1u32.into(), nays: 1u32.into(), turnout: 1u32.into() },
|
||||
}))
|
||||
);
|
||||
// Case 2: Finished referendum
|
||||
assert_eq!(
|
||||
ReferendumInfoOf::<T>::get(2u32),
|
||||
Some(ReferendumInfo::Finished { approved: true, end: 123u32.into() })
|
||||
);
|
||||
// Case 3: Public proposals
|
||||
let props: BoundedVec<_, <Test as Config>::MaxProposals> = bounded_vec![
|
||||
(3u32, Bounded::from_legacy_hash(hash), 123u64),
|
||||
(4u32, Bounded::from_legacy_hash(hash2), 123u64)
|
||||
];
|
||||
assert_eq!(PublicProps::<T>::get(), props);
|
||||
// Case 4: Next external
|
||||
assert_eq!(
|
||||
NextExternal::<T>::get(),
|
||||
Some((Bounded::from_legacy_hash(hash), VoteThreshold::SuperMajorityApprove))
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The crate's tests.
|
||||
|
||||
use super::*;
|
||||
use crate as pezpallet_democracy;
|
||||
use pezframe_support::{
|
||||
assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types,
|
||||
traits::{
|
||||
ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, SortedMembers,
|
||||
StorePreimage,
|
||||
},
|
||||
weights::Weight,
|
||||
};
|
||||
use pezframe_system::{EnsureRoot, EnsureSigned, EnsureSignedBy};
|
||||
use pezpallet_balances::{BalanceLock, Error as BalancesError};
|
||||
use pezsp_runtime::{
|
||||
traits::{BadOrigin, BlakeTwo256, Hash},
|
||||
BuildStorage, Perbill,
|
||||
};
|
||||
mod cancellation;
|
||||
mod decoders;
|
||||
mod delegation;
|
||||
mod external_proposing;
|
||||
mod fast_tracking;
|
||||
mod lock_voting;
|
||||
mod metadata;
|
||||
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 };
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
Preimage: pezpallet_preimage,
|
||||
Scheduler: pezpallet_scheduler,
|
||||
Democracy: pezpallet_democracy,
|
||||
}
|
||||
);
|
||||
|
||||
// Test that a filtered call can be dispatched.
|
||||
pub struct BaseFilter;
|
||||
impl Contains<RuntimeCall> for BaseFilter {
|
||||
fn contains(call: &RuntimeCall) -> bool {
|
||||
!matches!(call, &RuntimeCall::Balances(pezpallet_balances::Call::force_set_balance { .. }))
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: pezframe_system::limits::BlockWeights =
|
||||
pezframe_system::limits::BlockWeights::simple_max(
|
||||
Weight::from_parts(pezframe_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type BaseCallFilter = BaseFilter;
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
}
|
||||
parameter_types! {
|
||||
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block;
|
||||
}
|
||||
|
||||
impl pezpallet_preimage::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type ManagerOrigin = EnsureRoot<u64>;
|
||||
type Consideration = ();
|
||||
}
|
||||
|
||||
impl pezpallet_scheduler::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type PalletsOrigin = OriginCaller;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type MaximumWeight = MaximumSchedulerWeight;
|
||||
type ScheduleOrigin = EnsureRoot<u64>;
|
||||
type MaxScheduledPerBlock = ConstU32<100>;
|
||||
type WeightInfo = ();
|
||||
type OriginPrivilegeCmp = EqualPrivilegeOnly;
|
||||
type Preimages = ();
|
||||
type BlockNumberProvider = pezframe_system::Pallet<Test>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
parameter_types! {
|
||||
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<u64> for OneToFive {
|
||||
fn sorted_members() -> Vec<u64> {
|
||||
vec![1, 2, 3, 4, 5]
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn add(_m: &u64) {}
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = pezpallet_balances::Pallet<Self>;
|
||||
type EnactmentPeriod = ConstU64<2>;
|
||||
type LaunchPeriod = ConstU64<2>;
|
||||
type VotingPeriod = ConstU64<2>;
|
||||
type VoteLockingPeriod = ConstU64<3>;
|
||||
type FastTrackVotingPeriod = ConstU64<2>;
|
||||
type MinimumDeposit = ConstU64<1>;
|
||||
type MaxDeposits = ConstU32<1000>;
|
||||
type MaxBlacklisted = ConstU32<5>;
|
||||
type SubmitOrigin = EnsureSigned<Self::AccountId>;
|
||||
type ExternalOrigin = EnsureSignedBy<Two, u64>;
|
||||
type ExternalMajorityOrigin = EnsureSignedBy<Three, u64>;
|
||||
type ExternalDefaultOrigin = EnsureSignedBy<One, u64>;
|
||||
type FastTrackOrigin = EnsureSignedBy<Five, u64>;
|
||||
type CancellationOrigin = EnsureSignedBy<Four, u64>;
|
||||
type BlacklistOrigin = EnsureRoot<u64>;
|
||||
type CancelProposalOrigin = EnsureRoot<u64>;
|
||||
type VetoOrigin = EnsureSignedBy<OneToFive, u64>;
|
||||
type CooloffPeriod = ConstU64<2>;
|
||||
type Slash = ();
|
||||
type InstantOrigin = EnsureSignedBy<Six, u64>;
|
||||
type InstantAllowed = InstantAllowed;
|
||||
type Scheduler = Scheduler;
|
||||
type MaxVotes = ConstU32<100>;
|
||||
type PalletsOrigin = OriginCaller;
|
||||
type WeightInfo = ();
|
||||
type MaxProposals = ConstU32<100>;
|
||||
type Preimages = Preimage;
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
pezpallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
pezpallet_democracy::GenesisConfig::<Test>::default()
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn params_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(ReferendumCount::<Test>::get(), 0);
|
||||
assert_eq!(Balances::free_balance(42), 0);
|
||||
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), 210);
|
||||
});
|
||||
}
|
||||
|
||||
fn set_balance_proposal(value: u64) -> BoundedCallOf<Test> {
|
||||
let inner = pezpallet_balances::Call::force_set_balance { who: 42, new_free: value };
|
||||
let outer = RuntimeCall::Balances(inner);
|
||||
Preimage::bound(outer).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_balance_proposal_is_correctly_filtered_out() {
|
||||
for i in 0..10 {
|
||||
let call = Preimage::realize(&set_balance_proposal(i)).unwrap().0;
|
||||
assert!(!<Test as pezframe_system::Config>::BaseCallFilter::contains(&call));
|
||||
}
|
||||
}
|
||||
|
||||
fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult {
|
||||
Democracy::propose(RuntimeOrigin::signed(who), set_balance_proposal(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(1, 2, 1));
|
||||
fast_forward_to(2);
|
||||
0
|
||||
}
|
||||
|
||||
fn aye(who: u64) -> AccountVote<u64> {
|
||||
AccountVote::Standard { vote: AYE, balance: Balances::free_balance(&who) }
|
||||
}
|
||||
|
||||
fn nay(who: u64) -> AccountVote<u64> {
|
||||
AccountVote::Standard { vote: NAY, balance: Balances::free_balance(&who) }
|
||||
}
|
||||
|
||||
fn big_aye(who: u64) -> AccountVote<u64> {
|
||||
AccountVote::Standard { vote: BIG_AYE, balance: Balances::free_balance(&who) }
|
||||
}
|
||||
|
||||
fn big_nay(who: u64) -> AccountVote<u64> {
|
||||
AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) }
|
||||
}
|
||||
|
||||
fn tally(r: ReferendumIndex) -> Tally<u64> {
|
||||
Democracy::referendum_status(r).unwrap().tally
|
||||
}
|
||||
|
||||
/// note a new preimage without registering.
|
||||
fn note_preimage(who: u64) -> <Test as pezframe_system::Config>::Hash {
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
// note a new preimage on every function invoke.
|
||||
static COUNTER: AtomicU8 = AtomicU8::new(0);
|
||||
let data = vec![COUNTER.fetch_add(1, Ordering::Relaxed)];
|
||||
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(who), data.clone()));
|
||||
let hash = BlakeTwo256::hash(&data);
|
||||
assert!(!Preimage::is_requested(&hash));
|
||||
hash
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The tests for cancellation functionality.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cancel_referendum_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = Democracy::inject_referendum(
|
||||
2,
|
||||
set_balance_proposal(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1)));
|
||||
assert_ok!(Democracy::cancel_referendum(RuntimeOrigin::root(), r.into()));
|
||||
assert_eq!(LowestUnbaked::<Test>::get(), 0);
|
||||
|
||||
next_block();
|
||||
|
||||
next_block();
|
||||
|
||||
assert_eq!(LowestUnbaked::<Test>::get(), 1);
|
||||
assert_eq!(LowestUnbaked::<Test>::get(), ReferendumCount::<Test>::get());
|
||||
assert_eq!(Balances::free_balance(42), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[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(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
2,
|
||||
);
|
||||
assert!(Democracy::referendum_status(r).is_ok());
|
||||
|
||||
assert_noop!(Democracy::emergency_cancel(RuntimeOrigin::signed(3), r), BadOrigin);
|
||||
assert_ok!(Democracy::emergency_cancel(RuntimeOrigin::signed(4), r));
|
||||
assert!(ReferendumInfoOf::<Test>::get(r).is_none());
|
||||
|
||||
// some time later...
|
||||
|
||||
let r = Democracy::inject_referendum(
|
||||
2,
|
||||
set_balance_proposal(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
2,
|
||||
);
|
||||
assert!(Democracy::referendum_status(r).is_ok());
|
||||
assert_noop!(
|
||||
Democracy::emergency_cancel(RuntimeOrigin::signed(4), r),
|
||||
Error::<Test>::AlreadyCanceled,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The for various partial storage decoders
|
||||
|
||||
use super::*;
|
||||
use pezframe_support::{
|
||||
storage::{migration, unhashed},
|
||||
BoundedVec,
|
||||
};
|
||||
|
||||
#[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: (BoundedVec<u64, _>, u64) =
|
||||
((0..l).map(|_| Default::default()).collect::<Vec<_>>().try_into().unwrap(), 3u64);
|
||||
DepositOf::<Test>::insert(2, value);
|
||||
assert_eq!(Democracy::len_of_deposit_of(2), Some(l));
|
||||
}
|
||||
|
||||
DepositOf::<Test>::remove(2);
|
||||
assert_eq!(Democracy::len_of_deposit_of(2), None);
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! 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(1, 2, 1));
|
||||
|
||||
fast_forward_to(2);
|
||||
|
||||
// Delegate first vote.
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 20));
|
||||
let r = 0;
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1)));
|
||||
assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 });
|
||||
|
||||
// Delegate a second vote.
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(3), 1, Conviction::None, 30));
|
||||
assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 60 });
|
||||
|
||||
// Reduce first vote.
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::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(RuntimeOrigin::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(RuntimeOrigin::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(RuntimeOrigin::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(RuntimeOrigin::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(RuntimeOrigin::signed(1), 1, Conviction::None, 10),
|
||||
Error::<Test>::Nonsense,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cyclic_delegation_should_unwind() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
|
||||
assert_ok!(propose_set_balance(1, 2, 1));
|
||||
|
||||
fast_forward_to(2);
|
||||
|
||||
// Check behavior with cycle.
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 20));
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(3), 2, Conviction::None, 30));
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(1), 3, Conviction::None, 10));
|
||||
let r = 0;
|
||||
assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(3)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, aye(3)));
|
||||
assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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(1, 2, 1));
|
||||
|
||||
fast_forward_to(2);
|
||||
|
||||
let r = 0;
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, nay(2)));
|
||||
assert_eq!(tally(r), Tally { ayes: 1, nays: 2, turnout: 30 });
|
||||
|
||||
// Delegate vote.
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(2), r));
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::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(1, 2, 1));
|
||||
|
||||
// Delegate and undelegate vote.
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 20));
|
||||
assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(2)));
|
||||
|
||||
fast_forward_to(2);
|
||||
let r = 0;
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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(RuntimeOrigin::signed(1), r, aye(1)));
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 20));
|
||||
assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 });
|
||||
assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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 and vote.
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::Locked6x, 20));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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(RuntimeOrigin::signed(2), 1, Conviction::Locked6x, 20));
|
||||
assert_ok!(Democracy::vote(
|
||||
RuntimeOrigin::signed(1),
|
||||
r,
|
||||
AccountVote::Split { aye: 10, nay: 0 }
|
||||
));
|
||||
// Delegated vote is huge.
|
||||
assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 });
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redelegation_keeps_lock() {
|
||||
// If transactor voted, delegated vote is overwritten.
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = begin_referendum();
|
||||
// Delegate and vote.
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::Locked6x, 20));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1)));
|
||||
// Delegated vote is huge.
|
||||
assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 });
|
||||
|
||||
let mut prior_lock = vote::PriorLock::default();
|
||||
|
||||
// Locked balance of delegator exists
|
||||
assert_eq!(VotingOf::<Test>::get(2).locked_balance(), 20);
|
||||
assert_eq!(VotingOf::<Test>::get(2).prior(), &prior_lock);
|
||||
|
||||
// Delegate someone else at a lower conviction and amount
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 3, Conviction::None, 10));
|
||||
|
||||
// 6x prior should appear w/ locked balance.
|
||||
prior_lock.accumulate(98, 20);
|
||||
assert_eq!(VotingOf::<Test>::get(2).prior(), &prior_lock);
|
||||
assert_eq!(VotingOf::<Test>::get(2).locked_balance(), 20);
|
||||
// Unlock shouldn't work
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(2), 2));
|
||||
assert_eq!(VotingOf::<Test>::get(2).prior(), &prior_lock);
|
||||
assert_eq!(VotingOf::<Test>::get(2).locked_balance(), 20);
|
||||
|
||||
fast_forward_to(100);
|
||||
|
||||
// Now unlock can remove the prior lock and reduce the locked amount.
|
||||
assert_eq!(VotingOf::<Test>::get(2).prior(), &prior_lock);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(2), 2));
|
||||
assert_eq!(VotingOf::<Test>::get(2).prior(), &vote::PriorLock::default());
|
||||
assert_eq!(VotingOf::<Test>::get(2).locked_balance(), 10);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! 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(RuntimeOrigin::signed(2), set_balance_proposal(2),));
|
||||
assert!(NextExternal::<Test>::exists());
|
||||
|
||||
let h = set_balance_proposal(2).hash();
|
||||
assert_ok!(Democracy::veto_external(RuntimeOrigin::signed(3), h));
|
||||
// cancelled.
|
||||
assert!(!NextExternal::<Test>::exists());
|
||||
// fails - same proposal can't be resubmitted.
|
||||
assert_noop!(
|
||||
Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),),
|
||||
Error::<Test>::ProposalBlacklisted
|
||||
);
|
||||
|
||||
fast_forward_to(1);
|
||||
// fails as we're still in cooloff period.
|
||||
assert_noop!(
|
||||
Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),),
|
||||
Error::<Test>::ProposalBlacklisted
|
||||
);
|
||||
|
||||
fast_forward_to(2);
|
||||
// works; as we're out of the cooloff period.
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),));
|
||||
assert!(NextExternal::<Test>::exists());
|
||||
|
||||
// 3 can't veto the same thing twice.
|
||||
assert_noop!(
|
||||
Democracy::veto_external(RuntimeOrigin::signed(3), h),
|
||||
Error::<Test>::AlreadyVetoed
|
||||
);
|
||||
|
||||
// 4 vetoes.
|
||||
assert_ok!(Democracy::veto_external(RuntimeOrigin::signed(4), h));
|
||||
// cancelled again.
|
||||
assert!(!NextExternal::<Test>::exists());
|
||||
|
||||
fast_forward_to(3);
|
||||
// same proposal fails as we're still in cooloff
|
||||
assert_noop!(
|
||||
Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2)),
|
||||
Error::<Test>::ProposalBlacklisted
|
||||
);
|
||||
// different proposal works fine.
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(3),));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_blacklisting_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),));
|
||||
|
||||
let hash = set_balance_proposal(2).hash();
|
||||
assert_ok!(Democracy::blacklist(RuntimeOrigin::root(), hash, None));
|
||||
|
||||
fast_forward_to(2);
|
||||
assert_noop!(Democracy::referendum_status(0), Error::<Test>::ReferendumInvalid);
|
||||
|
||||
assert_noop!(
|
||||
Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2)),
|
||||
Error::<Test>::ProposalBlacklisted,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_referendum_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
assert_noop!(
|
||||
Democracy::external_propose(RuntimeOrigin::signed(1), set_balance_proposal(2),),
|
||||
BadOrigin,
|
||||
);
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),));
|
||||
assert_noop!(
|
||||
Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(1),),
|
||||
Error::<Test>::DuplicateProposal
|
||||
);
|
||||
fast_forward_to(2);
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(0),
|
||||
Ok(ReferendumStatus {
|
||||
end: 4,
|
||||
proposal: set_balance_proposal(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(RuntimeOrigin::signed(1), set_balance_proposal(2)),
|
||||
BadOrigin,
|
||||
);
|
||||
assert_ok!(Democracy::external_propose_majority(
|
||||
RuntimeOrigin::signed(3),
|
||||
set_balance_proposal(2)
|
||||
));
|
||||
fast_forward_to(2);
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(0),
|
||||
Ok(ReferendumStatus {
|
||||
end: 4,
|
||||
proposal: set_balance_proposal(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(RuntimeOrigin::signed(3), set_balance_proposal(2)),
|
||||
BadOrigin,
|
||||
);
|
||||
assert_ok!(Democracy::external_propose_default(
|
||||
RuntimeOrigin::signed(1),
|
||||
set_balance_proposal(2)
|
||||
));
|
||||
fast_forward_to(2);
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(0),
|
||||
Ok(ReferendumStatus {
|
||||
end: 4,
|
||||
proposal: set_balance_proposal(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(RuntimeOrigin::signed(2), set_balance_proposal(1),));
|
||||
assert_ok!(propose_set_balance(6, 2, 2));
|
||||
|
||||
fast_forward_to(2);
|
||||
|
||||
// both waiting: external goes first.
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(0),
|
||||
Ok(ReferendumStatus {
|
||||
end: 4,
|
||||
proposal: set_balance_proposal(1),
|
||||
threshold: VoteThreshold::SuperMajorityApprove,
|
||||
delay: 2,
|
||||
tally: Tally { ayes: 0, nays: 0, turnout: 0 },
|
||||
})
|
||||
);
|
||||
// replenish external
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(3),));
|
||||
|
||||
fast_forward_to(4);
|
||||
|
||||
// both waiting: public goes next.
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(1),
|
||||
Ok(ReferendumStatus {
|
||||
end: 6,
|
||||
proposal: set_balance_proposal(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: set_balance_proposal(3),
|
||||
threshold: VoteThreshold::SuperMajorityApprove,
|
||||
delay: 2,
|
||||
tally: Tally { ayes: 0, nays: 0, turnout: 0 },
|
||||
})
|
||||
);
|
||||
// replenish external
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(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: set_balance_proposal(5),
|
||||
threshold: VoteThreshold::SuperMajorityApprove,
|
||||
delay: 2,
|
||||
tally: Tally { ayes: 0, nays: 0, turnout: 0 },
|
||||
})
|
||||
);
|
||||
// replenish both
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(7),));
|
||||
assert_ok!(propose_set_balance(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: set_balance_proposal(4),
|
||||
threshold: VoteThreshold::SuperMajorityApprove,
|
||||
delay: 2,
|
||||
tally: Tally { ayes: 0, nays: 0, turnout: 0 },
|
||||
})
|
||||
);
|
||||
// replenish public again
|
||||
assert_ok!(propose_set_balance(6, 6, 2));
|
||||
// cancel external
|
||||
let h = set_balance_proposal(7).hash();
|
||||
assert_ok!(Democracy::veto_external(RuntimeOrigin::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: set_balance_proposal(6),
|
||||
threshold: VoteThreshold::SuperMajorityApprove,
|
||||
delay: 2,
|
||||
tally: Tally { ayes: 0, nays: 0, turnout: 0 },
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! 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(2).hash();
|
||||
assert_noop!(
|
||||
Democracy::fast_track(RuntimeOrigin::signed(5), h, 3, 2),
|
||||
Error::<Test>::ProposalMissing
|
||||
);
|
||||
assert_ok!(Democracy::external_propose_majority(
|
||||
RuntimeOrigin::signed(3),
|
||||
set_balance_proposal(2)
|
||||
));
|
||||
let hash = note_preimage(1);
|
||||
assert!(MetadataOf::<Test>::get(MetadataOwner::External).is_none());
|
||||
assert_ok!(Democracy::set_metadata(
|
||||
RuntimeOrigin::signed(3),
|
||||
MetadataOwner::External,
|
||||
Some(hash),
|
||||
),);
|
||||
assert!(MetadataOf::<Test>::get(MetadataOwner::External).is_some());
|
||||
assert_noop!(Democracy::fast_track(RuntimeOrigin::signed(1), h, 3, 2), BadOrigin);
|
||||
assert_ok!(Democracy::fast_track(RuntimeOrigin::signed(5), h, 2, 0));
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(0),
|
||||
Ok(ReferendumStatus {
|
||||
end: 2,
|
||||
proposal: set_balance_proposal(2),
|
||||
threshold: VoteThreshold::SimpleMajority,
|
||||
delay: 0,
|
||||
tally: Tally { ayes: 0, nays: 0, turnout: 0 },
|
||||
})
|
||||
);
|
||||
// metadata reset from the external proposal to the referendum.
|
||||
assert!(MetadataOf::<Test>::get(MetadataOwner::External).is_none());
|
||||
assert!(MetadataOf::<Test>::get(MetadataOwner::Referendum(0)).is_some());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instant_referendum_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
let h = set_balance_proposal(2).hash();
|
||||
assert_noop!(
|
||||
Democracy::fast_track(RuntimeOrigin::signed(5), h, 3, 2),
|
||||
Error::<Test>::ProposalMissing
|
||||
);
|
||||
assert_ok!(Democracy::external_propose_majority(
|
||||
RuntimeOrigin::signed(3),
|
||||
set_balance_proposal(2)
|
||||
));
|
||||
assert_noop!(Democracy::fast_track(RuntimeOrigin::signed(1), h, 3, 2), BadOrigin);
|
||||
assert_noop!(Democracy::fast_track(RuntimeOrigin::signed(5), h, 1, 0), BadOrigin);
|
||||
assert_noop!(
|
||||
Democracy::fast_track(RuntimeOrigin::signed(6), h, 1, 0),
|
||||
Error::<Test>::InstantNotAllowed
|
||||
);
|
||||
INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true);
|
||||
assert_noop!(
|
||||
Democracy::fast_track(RuntimeOrigin::signed(6), h, 0, 0),
|
||||
Error::<Test>::VotingPeriodLow
|
||||
);
|
||||
assert_ok!(Democracy::fast_track(RuntimeOrigin::signed(6), h, 1, 0));
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(0),
|
||||
Ok(ReferendumStatus {
|
||||
end: 1,
|
||||
proposal: set_balance_proposal(2),
|
||||
threshold: VoteThreshold::SimpleMajority,
|
||||
delay: 0,
|
||||
tally: Tally { ayes: 0, nays: 0, turnout: 0 },
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instant_next_block_referendum_backed() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// arrange
|
||||
let start_block_number = 10;
|
||||
let majority_origin_id = 3;
|
||||
let instant_origin_id = 6;
|
||||
let voting_period = 1;
|
||||
let proposal = set_balance_proposal(2);
|
||||
let delay = 2; // has no effect on test
|
||||
|
||||
// init
|
||||
System::set_block_number(start_block_number);
|
||||
InstantAllowed::set(true);
|
||||
|
||||
// propose with majority origin
|
||||
assert_ok!(Democracy::external_propose_majority(
|
||||
RuntimeOrigin::signed(majority_origin_id),
|
||||
proposal.clone()
|
||||
));
|
||||
|
||||
// fast track with instant origin and voting period pointing to the next block
|
||||
assert_ok!(Democracy::fast_track(
|
||||
RuntimeOrigin::signed(instant_origin_id),
|
||||
proposal.hash(),
|
||||
voting_period,
|
||||
delay
|
||||
));
|
||||
|
||||
// fetch the status of the only referendum at index 0
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(0),
|
||||
Ok(ReferendumStatus {
|
||||
end: start_block_number + voting_period,
|
||||
proposal,
|
||||
threshold: VoteThreshold::SimpleMajority,
|
||||
delay,
|
||||
tally: Tally { ayes: 0, nays: 0, turnout: 0 },
|
||||
})
|
||||
);
|
||||
|
||||
// referendum expected to be baked with the start of the next block
|
||||
next_block();
|
||||
|
||||
// assert no active referendums
|
||||
assert_noop!(Democracy::referendum_status(0), Error::<Test>::ReferendumInvalid);
|
||||
// the only referendum in the storage is finished and not approved
|
||||
assert_eq!(
|
||||
ReferendumInfoOf::<Test>::get(0).unwrap(),
|
||||
ReferendumInfo::Finished { approved: false, end: start_block_number + voting_period }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[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(2).hash();
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2)));
|
||||
assert_noop!(
|
||||
Democracy::fast_track(RuntimeOrigin::signed(5), h, 3, 2),
|
||||
Error::<Test>::NotSimpleMajority
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The tests for functionality concerning locking and lock-voting.
|
||||
|
||||
use super::*;
|
||||
|
||||
fn aye(x: u8, balance: u64) -> AccountVote<u64> {
|
||||
AccountVote::Standard {
|
||||
vote: Vote { aye: true, conviction: Conviction::try_from(x).unwrap() },
|
||||
balance,
|
||||
}
|
||||
}
|
||||
|
||||
fn nay(x: u8, balance: u64) -> AccountVote<u64> {
|
||||
AccountVote::Standard {
|
||||
vote: Vote { aye: false, conviction: Conviction::try_from(x).unwrap() },
|
||||
balance,
|
||||
}
|
||||
}
|
||||
|
||||
fn the_lock(amount: u64) -> BalanceLock<u64> {
|
||||
BalanceLock { id: DEMOCRACY_ID, amount, reasons: pezpallet_balances::Reasons::All }
|
||||
}
|
||||
|
||||
#[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(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, nay(5, 10)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, aye(4, 20)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, aye(3, 30)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(4), r, aye(2, 40)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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!(pezpallet_balances::Locks::<Test>::get(&i), vec![the_lock(i * 10)]);
|
||||
}
|
||||
|
||||
fast_forward_to(3);
|
||||
|
||||
// Referendum passed; 1 and 5 didn't get their way and can now reap and unlock.
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(1), r));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 1));
|
||||
// Anyone can reap and unlock anyone else's in this context.
|
||||
assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(2), 5, r));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::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(RuntimeOrigin::signed(1), 2, r),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
// However, they can be unvoted by the owner, though it will make no difference to the lock.
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(2), r));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(2), 2));
|
||||
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&1), vec![]);
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&2), vec![the_lock(20)]);
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&3), vec![the_lock(30)]);
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&4), vec![the_lock(40)]);
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![]);
|
||||
assert_eq!(Balances::free_balance(42), 2);
|
||||
|
||||
fast_forward_to(7);
|
||||
// No change yet...
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(RuntimeOrigin::signed(1), 4, r),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 4));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&4), vec![the_lock(40)]);
|
||||
fast_forward_to(8);
|
||||
// 4 should now be able to reap and unlock
|
||||
assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 4, r));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 4));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&4), vec![]);
|
||||
|
||||
fast_forward_to(13);
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(RuntimeOrigin::signed(1), 3, r),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 3));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&3), vec![the_lock(30)]);
|
||||
fast_forward_to(14);
|
||||
assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 3, r));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 3));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&3), vec![]);
|
||||
|
||||
// 2 doesn't need to reap_vote here because it was already done before.
|
||||
fast_forward_to(25);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 2));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&2), vec![the_lock(20)]);
|
||||
fast_forward_to(26);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 2));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&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(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(0, 10)));
|
||||
|
||||
fast_forward_to(3);
|
||||
|
||||
assert_eq!(Balances::free_balance(42), 2);
|
||||
assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(2), 1, r));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(2), 1));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&1), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_voting_should_work_with_delegation() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = Democracy::inject_referendum(
|
||||
2,
|
||||
set_balance_proposal(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, nay(5, 10)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, aye(4, 20)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, aye(3, 30)));
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(4), 2, Conviction::Locked2x, 40));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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(2), VoteThreshold::SimpleMajority, 0);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r1, aye(4, 10)));
|
||||
|
||||
let r2 =
|
||||
Democracy::inject_referendum(2, set_balance_proposal(2), VoteThreshold::SimpleMajority, 0);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r2, aye(3, 20)));
|
||||
|
||||
let r3 =
|
||||
Democracy::inject_referendum(2, set_balance_proposal(2), VoteThreshold::SimpleMajority, 0);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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(RuntimeOrigin::signed(1), 5, r.2),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(50)]);
|
||||
fast_forward_to(8);
|
||||
assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.2));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(20)]);
|
||||
fast_forward_to(13);
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.1),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(20)]);
|
||||
fast_forward_to(14);
|
||||
assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.1));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(10)]);
|
||||
fast_forward_to(25);
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.0),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(10)]);
|
||||
fast_forward_to(26);
|
||||
assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.0));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&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(RuntimeOrigin::signed(5), r.2));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(50)]);
|
||||
fast_forward_to(8);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(20)]);
|
||||
|
||||
fast_forward_to(13);
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.1));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(20)]);
|
||||
fast_forward_to(14);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(10)]);
|
||||
|
||||
fast_forward_to(25);
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.0));
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![the_lock(10)]);
|
||||
fast_forward_to(26);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&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(RuntimeOrigin::signed(5), r.2));
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.1));
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.0));
|
||||
|
||||
fast_forward_to(8);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert!(pezpallet_balances::Locks::<Test>::get(&5)[0].amount >= 20);
|
||||
|
||||
fast_forward_to(14);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert!(pezpallet_balances::Locks::<Test>::get(&5)[0].amount >= 10);
|
||||
|
||||
fast_forward_to(26);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&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(2),
|
||||
VoteThreshold::SimpleMajority,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, aye(4, 10)));
|
||||
fast_forward_to(2);
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r));
|
||||
// locked 10 until #26.
|
||||
|
||||
assert_ok!(Democracy::delegate(RuntimeOrigin::signed(5), 1, Conviction::Locked3x, 20));
|
||||
// locked 20.
|
||||
assert!(pezpallet_balances::Locks::<Test>::get(&5)[0].amount == 20);
|
||||
|
||||
assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(5)));
|
||||
// locked 20 until #14
|
||||
|
||||
fast_forward_to(13);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert!(pezpallet_balances::Locks::<Test>::get(&5)[0].amount == 20);
|
||||
|
||||
fast_forward_to(14);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert!(pezpallet_balances::Locks::<Test>::get(&5)[0].amount >= 10);
|
||||
|
||||
fast_forward_to(25);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert!(pezpallet_balances::Locks::<Test>::get(&5)[0].amount >= 10);
|
||||
|
||||
fast_forward_to(26);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&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(RuntimeOrigin::signed(5), 1, Conviction::Locked5x, 5));
|
||||
assert_ok!(Democracy::undelegate(RuntimeOrigin::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(RuntimeOrigin::signed(5), r.2));
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.1));
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.0));
|
||||
|
||||
fast_forward_to(8);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert!(pezpallet_balances::Locks::<Test>::get(&5)[0].amount >= 20);
|
||||
|
||||
fast_forward_to(14);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert!(pezpallet_balances::Locks::<Test>::get(&5)[0].amount >= 10);
|
||||
|
||||
fast_forward_to(26);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert!(pezpallet_balances::Locks::<Test>::get(&5)[0].amount >= 5);
|
||||
|
||||
fast_forward_to(48);
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![]);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The tests for functionality concerning the metadata.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn set_external_metadata_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// invalid preimage hash.
|
||||
let invalid_hash: <Test as pezframe_system::Config>::Hash = [1u8; 32].into();
|
||||
// metadata owner is an external proposal.
|
||||
let owner = MetadataOwner::External;
|
||||
// fails to set metadata if an external proposal does not exist.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), Some(invalid_hash)),
|
||||
Error::<Test>::NoProposal,
|
||||
);
|
||||
// create an external proposal.
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2)));
|
||||
assert!(NextExternal::<Test>::exists());
|
||||
// fails to set metadata with non external origin.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), Some(invalid_hash)),
|
||||
BadOrigin,
|
||||
);
|
||||
// fails to set non-existing preimage.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), Some(invalid_hash)),
|
||||
Error::<Test>::PreimageNotExist,
|
||||
);
|
||||
// set metadata successful.
|
||||
let hash = note_preimage(1);
|
||||
assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), Some(hash)));
|
||||
System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataSet {
|
||||
owner,
|
||||
hash,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_metadata_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// metadata owner is an external proposal.
|
||||
let owner = MetadataOwner::External;
|
||||
// create an external proposal.
|
||||
assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2)));
|
||||
assert!(NextExternal::<Test>::exists());
|
||||
// set metadata.
|
||||
let hash = note_preimage(1);
|
||||
assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), Some(hash)));
|
||||
// fails to clear metadata with a wrong origin.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), None),
|
||||
BadOrigin,
|
||||
);
|
||||
// clear metadata successful.
|
||||
assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), None));
|
||||
System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataCleared {
|
||||
owner,
|
||||
hash,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_proposal_metadata_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// invalid preimage hash.
|
||||
let invalid_hash: <Test as pezframe_system::Config>::Hash = [1u8; 32].into();
|
||||
// create an external proposal.
|
||||
assert_ok!(propose_set_balance(1, 2, 5));
|
||||
// metadata owner is a public proposal.
|
||||
let owner = MetadataOwner::Proposal(PublicPropCount::<Test>::get() - 1);
|
||||
// fails to set non-existing preimage.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), Some(invalid_hash)),
|
||||
Error::<Test>::PreimageNotExist,
|
||||
);
|
||||
// note preimage.
|
||||
let hash = note_preimage(1);
|
||||
// fails to set a preimage if an origin is not a proposer.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(3), owner.clone(), Some(hash)),
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
// set metadata successful.
|
||||
assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), Some(hash)));
|
||||
System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataSet {
|
||||
owner,
|
||||
hash,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_proposal_metadata_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// create an external proposal.
|
||||
assert_ok!(propose_set_balance(1, 2, 5));
|
||||
// metadata owner is a public proposal.
|
||||
let owner = MetadataOwner::Proposal(PublicPropCount::<Test>::get() - 1);
|
||||
// set metadata.
|
||||
let hash = note_preimage(1);
|
||||
assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), Some(hash)));
|
||||
// fails to clear metadata with a wrong origin.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(3), owner.clone(), None),
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
// clear metadata successful.
|
||||
assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), None));
|
||||
System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataCleared {
|
||||
owner,
|
||||
hash,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_referendum_metadata_by_root() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let index = Democracy::inject_referendum(
|
||||
2,
|
||||
set_balance_proposal(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
// metadata owner is a referendum.
|
||||
let owner = MetadataOwner::Referendum(index);
|
||||
// note preimage.
|
||||
let hash = note_preimage(1);
|
||||
// fails to set if not a root.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(3), owner.clone(), Some(hash)),
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
// fails to clear if not a root.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(3), owner.clone(), None),
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
// succeed to set metadata by a root for an ongoing referendum.
|
||||
assert_ok!(Democracy::set_metadata(RuntimeOrigin::root(), owner.clone(), Some(hash)));
|
||||
System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataSet {
|
||||
owner: owner.clone(),
|
||||
hash,
|
||||
}));
|
||||
// succeed to clear metadata by a root for an ongoing referendum.
|
||||
assert_ok!(Democracy::set_metadata(RuntimeOrigin::root(), owner.clone(), None));
|
||||
System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataCleared {
|
||||
owner,
|
||||
hash,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_referendum_metadata_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// create a referendum.
|
||||
let index = Democracy::inject_referendum(
|
||||
2,
|
||||
set_balance_proposal(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
// metadata owner is a referendum.
|
||||
let owner = MetadataOwner::Referendum(index);
|
||||
// set metadata.
|
||||
let hash = note_preimage(1);
|
||||
// referendum finished.
|
||||
MetadataOf::<Test>::insert(owner.clone(), hash);
|
||||
// no permission to clear metadata of an ongoing referendum.
|
||||
assert_noop!(
|
||||
Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), None),
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
// referendum finished.
|
||||
ReferendumInfoOf::<Test>::insert(
|
||||
index,
|
||||
ReferendumInfo::Finished { end: 1, approved: true },
|
||||
);
|
||||
// clear metadata successful.
|
||||
assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), None));
|
||||
System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataCleared {
|
||||
owner,
|
||||
hash,
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! 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(1, 2, 2));
|
||||
assert_ok!(propose_set_balance(1, 4, 4));
|
||||
assert_ok!(propose_set_balance(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(1, 2, 5));
|
||||
assert_ok!(Democracy::second(RuntimeOrigin::signed(2), 0));
|
||||
assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0));
|
||||
assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0));
|
||||
assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0));
|
||||
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(1, 2, 5));
|
||||
assert_ok!(Democracy::second(RuntimeOrigin::signed(2), 0));
|
||||
assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0));
|
||||
assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0));
|
||||
assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0));
|
||||
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::<Test>::ValueLow);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poor_proposer_should_not_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(propose_set_balance(1, 2, 11), BalancesError::<Test, _>::InsufficientBalance);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poor_seconder_should_not_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(propose_set_balance(2, 2, 11));
|
||||
assert_noop!(
|
||||
Democracy::second(RuntimeOrigin::signed(1), 0),
|
||||
BalancesError::<Test, _>::InsufficientBalance
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_proposal_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(propose_set_balance(1, 2, 2));
|
||||
assert_ok!(propose_set_balance(1, 4, 4));
|
||||
assert_noop!(Democracy::cancel_proposal(RuntimeOrigin::signed(1), 0), BadOrigin);
|
||||
let hash = note_preimage(1);
|
||||
assert_ok!(Democracy::set_metadata(
|
||||
RuntimeOrigin::signed(1),
|
||||
MetadataOwner::Proposal(0),
|
||||
Some(hash)
|
||||
));
|
||||
assert!(MetadataOf::<Test>::get(MetadataOwner::Proposal(0)).is_some());
|
||||
assert_ok!(Democracy::cancel_proposal(RuntimeOrigin::root(), 0));
|
||||
// metadata cleared, preimage unrequested.
|
||||
assert!(MetadataOf::<Test>::get(MetadataOwner::Proposal(0)).is_none());
|
||||
System::assert_has_event(crate::Event::ProposalCanceled { prop_index: 0 }.into());
|
||||
System::assert_last_event(
|
||||
crate::Event::MetadataCleared { owner: MetadataOwner::Proposal(0), hash }.into(),
|
||||
);
|
||||
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(2).hash();
|
||||
|
||||
assert_ok!(propose_set_balance(1, 2, 2));
|
||||
assert_ok!(propose_set_balance(1, 4, 4));
|
||||
|
||||
assert_noop!(Democracy::blacklist(RuntimeOrigin::signed(1), hash, None), BadOrigin);
|
||||
assert_ok!(Democracy::blacklist(RuntimeOrigin::root(), hash, None));
|
||||
|
||||
assert_eq!(Democracy::backing_for(0), None);
|
||||
assert_eq!(Democracy::backing_for(1), Some(4));
|
||||
|
||||
assert_noop!(propose_set_balance(1, 2, 2), Error::<Test>::ProposalBlacklisted);
|
||||
|
||||
fast_forward_to(2);
|
||||
|
||||
let hash = set_balance_proposal(4).hash();
|
||||
assert_ok!(Democracy::referendum_status(0));
|
||||
assert_ok!(Democracy::blacklist(RuntimeOrigin::root(), hash, Some(0)));
|
||||
assert_noop!(Democracy::referendum_status(0), Error::<Test>::ReferendumInvalid);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runners_up_should_come_after() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
assert_ok!(propose_set_balance(1, 2, 2));
|
||||
assert_ok!(propose_set_balance(1, 4, 4));
|
||||
assert_ok!(propose_set_balance(1, 3, 3));
|
||||
fast_forward_to(2);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), 0, aye(1)));
|
||||
fast_forward_to(4);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), 1, aye(1)));
|
||||
fast_forward_to(6);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), 2, aye(1)));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! 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(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1)));
|
||||
assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 });
|
||||
assert_eq!(LowestUnbaked::<Test>::get(), 0);
|
||||
next_block();
|
||||
next_block();
|
||||
assert_eq!(LowestUnbaked::<Test>::get(), 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(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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(3),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
let r2 = Democracy::inject_referendum(
|
||||
2,
|
||||
set_balance_proposal(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r2, aye(1)));
|
||||
assert_eq!(tally(r2), Tally { ayes: 1, nays: 0, turnout: 10 });
|
||||
|
||||
next_block();
|
||||
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r1, aye(1)));
|
||||
assert_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 10 });
|
||||
|
||||
next_block();
|
||||
assert_eq!(Balances::free_balance(42), 2);
|
||||
|
||||
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(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
1,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, aye(2)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, aye(3)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(4), r, aye(4)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, aye(5)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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(1),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
let r2 = Democracy::inject_referendum(
|
||||
2,
|
||||
set_balance_proposal(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
let r3 = Democracy::inject_referendum(
|
||||
10,
|
||||
set_balance_proposal(3),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r1, aye(1)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r2, aye(1)));
|
||||
// r3 is canceled
|
||||
assert_ok!(Democracy::cancel_referendum(RuntimeOrigin::root(), r3.into()));
|
||||
assert_eq!(LowestUnbaked::<Test>::get(), 0);
|
||||
|
||||
next_block();
|
||||
// r2 ends with approval
|
||||
assert_eq!(LowestUnbaked::<Test>::get(), 0);
|
||||
|
||||
next_block();
|
||||
// r1 ends with approval
|
||||
assert_eq!(LowestUnbaked::<Test>::get(), 3);
|
||||
assert_eq!(LowestUnbaked::<Test>::get(), ReferendumCount::<Test>::get());
|
||||
|
||||
// r2 is executed
|
||||
assert_eq!(Balances::free_balance(42), 2);
|
||||
|
||||
next_block();
|
||||
// r1 is executed
|
||||
assert_eq!(Balances::free_balance(42), 1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! 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(RuntimeOrigin::signed(1), r, aye(2)),
|
||||
Error::<Test>::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(RuntimeOrigin::signed(5), r, v),
|
||||
Error::<Test>::InsufficientFunds
|
||||
);
|
||||
let v = AccountVote::Split { aye: 30, nay: 20 };
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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(RuntimeOrigin::signed(5), r, v));
|
||||
assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r));
|
||||
assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 });
|
||||
assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5));
|
||||
assert_eq!(pezpallet_balances::Locks::<Test>::get(&5), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_proposal_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
assert_ok!(propose_set_balance(1, 2, 1));
|
||||
let r = 0;
|
||||
assert!(ReferendumInfoOf::<Test>::get(r).is_none());
|
||||
|
||||
// start of 2 => next referendum scheduled.
|
||||
fast_forward_to(2);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1)));
|
||||
|
||||
assert_eq!(ReferendumCount::<Test>::get(), 1);
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(0),
|
||||
Ok(ReferendumStatus {
|
||||
end: 4,
|
||||
proposal: set_balance_proposal(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::<Test>::ReferendumInvalid);
|
||||
assert!(pezpallet_scheduler::Agenda::<Test>::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(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, big_aye(1)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, big_nay(2)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, big_nay(3)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(4), r, big_aye(4)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, big_nay(5)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, big_nay(5)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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!(pezpallet_balances::TotalIssuance::<Test>::get(), 210);
|
||||
|
||||
let r = Democracy::inject_referendum(
|
||||
2,
|
||||
set_balance_proposal(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(4), r, big_aye(4)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, big_nay(5)));
|
||||
assert_ok!(Democracy::vote(RuntimeOrigin::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);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Miscellaneous additional datatypes.
|
||||
|
||||
use crate::{AccountVote, Conviction, Vote, VoteThreshold};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::{
|
||||
traits::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
|
||||
/// A proposal index.
|
||||
pub type PropIndex = u32;
|
||||
|
||||
/// A referendum index.
|
||||
pub type ReferendumIndex = u32;
|
||||
|
||||
/// Info regarding an ongoing referendum.
|
||||
#[derive(Encode, MaxEncodedLen, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct Tally<Balance> {
|
||||
/// 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, MaxEncodedLen, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo,
|
||||
)]
|
||||
pub struct Delegations<Balance> {
|
||||
/// The number of votes (this is post-conviction).
|
||||
pub votes: Balance,
|
||||
/// The amount of raw capital, used for the turnout.
|
||||
pub capital: Balance,
|
||||
}
|
||||
|
||||
impl<Balance: Saturating> Saturating for Delegations<Balance> {
|
||||
fn saturating_add(self, o: Self) -> Self {
|
||||
Self {
|
||||
votes: self.votes.saturating_add(o.votes),
|
||||
capital: self.capital.saturating_add(o.capital),
|
||||
}
|
||||
}
|
||||
|
||||
fn saturating_sub(self, o: Self) -> Self {
|
||||
Self {
|
||||
votes: self.votes.saturating_sub(o.votes),
|
||||
capital: self.capital.saturating_sub(o.capital),
|
||||
}
|
||||
}
|
||||
|
||||
fn saturating_mul(self, o: Self) -> Self {
|
||||
Self {
|
||||
votes: self.votes.saturating_mul(o.votes),
|
||||
capital: self.capital.saturating_mul(o.capital),
|
||||
}
|
||||
}
|
||||
|
||||
fn saturating_pow(self, exp: usize) -> Self {
|
||||
Self { votes: self.votes.saturating_pow(exp), capital: self.capital.saturating_pow(exp) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Balance: From<u8>
|
||||
+ Zero
|
||||
+ Copy
|
||||
+ CheckedAdd
|
||||
+ CheckedSub
|
||||
+ CheckedMul
|
||||
+ CheckedDiv
|
||||
+ Bounded
|
||||
+ Saturating,
|
||||
> Tally<Balance>
|
||||
{
|
||||
/// 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<Balance>) -> 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<Balance>) -> 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<Balance>) -> 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<Balance>) -> 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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Info regarding an ongoing referendum.
|
||||
#[derive(
|
||||
Encode,
|
||||
MaxEncodedLen,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub struct ReferendumStatus<BlockNumber, Proposal, Balance> {
|
||||
/// When voting on this referendum will end.
|
||||
pub end: BlockNumber,
|
||||
/// The proposal being voted on.
|
||||
pub proposal: Proposal,
|
||||
/// The thresholding mechanism to determine whether it passed.
|
||||
pub threshold: VoteThreshold,
|
||||
/// The delay (in blocks) to wait after a successful referendum before deploying.
|
||||
pub delay: BlockNumber,
|
||||
/// The current tally of votes in this referendum.
|
||||
pub tally: Tally<Balance>,
|
||||
}
|
||||
|
||||
/// Info regarding a referendum, present or past.
|
||||
#[derive(
|
||||
Encode,
|
||||
MaxEncodedLen,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub enum ReferendumInfo<BlockNumber, Proposal, Balance> {
|
||||
/// Referendum is happening, the arg is the block number at which it will end.
|
||||
Ongoing(ReferendumStatus<BlockNumber, Proposal, Balance>),
|
||||
/// Referendum finished at `end`, and has been `approved` or rejected.
|
||||
Finished { approved: bool, end: BlockNumber },
|
||||
}
|
||||
|
||||
impl<BlockNumber, Proposal, Balance: Default> ReferendumInfo<BlockNumber, Proposal, Balance> {
|
||||
/// Create a new instance.
|
||||
pub fn new(
|
||||
end: BlockNumber,
|
||||
proposal: Proposal,
|
||||
threshold: VoteThreshold,
|
||||
delay: BlockNumber,
|
||||
) -> Self {
|
||||
let s = ReferendumStatus { end, proposal, threshold, delay, tally: Tally::default() };
|
||||
ReferendumInfo::Ongoing(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Identifies an owner of a metadata.
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub enum MetadataOwner {
|
||||
/// External proposal.
|
||||
External,
|
||||
/// Public proposal of the index.
|
||||
Proposal(PropIndex),
|
||||
/// Referendum of the index.
|
||||
Referendum(ReferendumIndex),
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The vote datatype.
|
||||
|
||||
use crate::{Conviction, Delegations, ReferendumIndex};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, Input, MaxEncodedLen, Output};
|
||||
use pezframe_support::traits::Get;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
BoundedVec, RuntimeDebug,
|
||||
};
|
||||
|
||||
/// A number of lock periods, plus a vote, one way or the other.
|
||||
#[derive(DecodeWithMemTracking, Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)]
|
||||
pub struct Vote {
|
||||
pub aye: bool,
|
||||
pub conviction: Conviction,
|
||||
}
|
||||
|
||||
impl Encode for Vote {
|
||||
fn encode_to<T: Output + ?Sized>(&self, output: &mut T) {
|
||||
output.push_byte(u8::from(self.conviction) | if self.aye { 0b1000_0000 } else { 0 });
|
||||
}
|
||||
}
|
||||
|
||||
impl MaxEncodedLen for Vote {
|
||||
fn max_encoded_len() -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeLike for Vote {}
|
||||
|
||||
impl Decode for Vote {
|
||||
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
let b = input.read_byte()?;
|
||||
Ok(Vote {
|
||||
aye: (b & 0b1000_0000) == 0b1000_0000,
|
||||
conviction: Conviction::try_from(b & 0b0111_1111)
|
||||
.map_err(|_| codec::Error::from("Invalid conviction"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeInfo for Vote {
|
||||
type Identity = Self;
|
||||
|
||||
fn type_info() -> scale_info::Type {
|
||||
scale_info::Type::builder()
|
||||
.path(scale_info::Path::new("Vote", module_path!()))
|
||||
.composite(
|
||||
scale_info::build::Fields::unnamed()
|
||||
.field(|f| f.ty::<u8>().docs(&["Raw vote byte, encodes aye + conviction"])),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A vote for a referendum of a particular account.
|
||||
#[derive(
|
||||
Encode,
|
||||
DecodeWithMemTracking,
|
||||
MaxEncodedLen,
|
||||
Decode,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub enum AccountVote<Balance> {
|
||||
/// A standard vote, one-way (approve or reject) with a given amount of conviction.
|
||||
Standard { vote: Vote, balance: Balance },
|
||||
/// A split vote with balances given for both ways, and with no conviction, useful for
|
||||
/// teyrchains when voting.
|
||||
Split { aye: Balance, nay: Balance },
|
||||
}
|
||||
|
||||
impl<Balance: Saturating> AccountVote<Balance> {
|
||||
/// Returns `Some` of the lock periods that the account is locked for, assuming that the
|
||||
/// referendum passed iff `approved` is `true`.
|
||||
pub fn locked_if(self, approved: bool) -> Option<(u32, Balance)> {
|
||||
// winning side: can only be removed after the lock period ends.
|
||||
match self {
|
||||
AccountVote::Standard { vote, balance } if vote.aye == approved =>
|
||||
Some((vote.conviction.lock_periods(), balance)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The total balance involved in this vote.
|
||||
pub fn balance(self) -> Balance {
|
||||
match self {
|
||||
AccountVote::Standard { balance, .. } => balance,
|
||||
AccountVote::Split { aye, nay } => aye.saturating_add(nay),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some` with whether the vote is an aye vote if it is standard, otherwise `None` if
|
||||
/// it is split.
|
||||
pub fn as_standard(self) -> Option<bool> {
|
||||
match self {
|
||||
AccountVote::Standard { vote, .. } => Some(vote.aye),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A "prior" lock, i.e. a lock for some now-forgotten reason.
|
||||
#[derive(
|
||||
Encode,
|
||||
MaxEncodedLen,
|
||||
Decode,
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub struct PriorLock<BlockNumber, Balance>(BlockNumber, Balance);
|
||||
|
||||
impl<BlockNumber: Ord + Copy + Zero, Balance: Ord + Copy + Zero> PriorLock<BlockNumber, Balance> {
|
||||
/// Accumulates an additional lock.
|
||||
pub fn accumulate(&mut self, until: BlockNumber, amount: Balance) {
|
||||
self.0 = self.0.max(until);
|
||||
self.1 = self.1.max(amount);
|
||||
}
|
||||
|
||||
pub fn locked(&self) -> Balance {
|
||||
self.1
|
||||
}
|
||||
|
||||
pub fn rejig(&mut self, now: BlockNumber) {
|
||||
if now >= self.0 {
|
||||
self.0 = Zero::zero();
|
||||
self.1 = Zero::zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An indicator for what an account is doing; it can either be delegating or voting.
|
||||
#[derive(Clone, Encode, Decode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
#[codec(mel_bound(skip_type_params(MaxVotes)))]
|
||||
#[scale_info(skip_type_params(MaxVotes))]
|
||||
pub enum Voting<Balance, AccountId, BlockNumber, MaxVotes: Get<u32>> {
|
||||
/// 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: BoundedVec<(ReferendumIndex, AccountVote<Balance>), MaxVotes>,
|
||||
/// The total amount of delegations that this account has received.
|
||||
delegations: Delegations<Balance>,
|
||||
/// Any pre-existing locks from past voting/delegating activity.
|
||||
prior: PriorLock<BlockNumber, Balance>,
|
||||
},
|
||||
/// 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<Balance>,
|
||||
/// Any pre-existing locks from past voting/delegating activity.
|
||||
prior: PriorLock<BlockNumber, Balance>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<Balance: Default, AccountId, BlockNumber: Zero, MaxVotes: Get<u32>> Default
|
||||
for Voting<Balance, AccountId, BlockNumber, MaxVotes>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Voting::Direct {
|
||||
votes: Default::default(),
|
||||
delegations: Default::default(),
|
||||
prior: PriorLock(Zero::zero(), Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Balance: Saturating + Ord + Zero + Copy,
|
||||
BlockNumber: Ord + Copy + Zero,
|
||||
AccountId,
|
||||
MaxVotes: Get<u32>,
|
||||
> Voting<Balance, AccountId, BlockNumber, MaxVotes>
|
||||
{
|
||||
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 must 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, prior, .. } => *balance.max(&prior.locked()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_common(
|
||||
&mut self,
|
||||
delegations: Delegations<Balance>,
|
||||
prior: PriorLock<BlockNumber, Balance>,
|
||||
) {
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn prior(&self) -> &PriorLock<BlockNumber, Balance> {
|
||||
match self {
|
||||
Voting::Direct { prior, .. } => prior,
|
||||
Voting::Delegating { prior, .. } => prior,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Voting thresholds.
|
||||
|
||||
use crate::Tally;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use core::ops::{Add, Div, Mul, Rem};
|
||||
use scale_info::TypeInfo;
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pezsp_runtime::traits::{IntegerSquareRoot, Zero};
|
||||
|
||||
/// A means of determining if a vote is past pass threshold.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Encode,
|
||||
DecodeWithMemTracking,
|
||||
MaxEncodedLen,
|
||||
Decode,
|
||||
pezsp_runtime::RuntimeDebug,
|
||||
TypeInfo,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub enum VoteThreshold {
|
||||
/// A supermajority of approvals is needed to pass this vote.
|
||||
SuperMajorityApprove,
|
||||
/// A supermajority of rejects is needed to fail this vote.
|
||||
SuperMajorityAgainst,
|
||||
/// A simple majority of approvals is needed to pass this vote.
|
||||
SimpleMajority,
|
||||
}
|
||||
|
||||
pub trait Approved<Balance> {
|
||||
/// Given a `tally` of votes and a total size of `electorate`, this returns `true` if the
|
||||
/// overall outcome is in favor of approval according to `self`'s threshold method.
|
||||
fn approved(&self, tally: Tally<Balance>, electorate: Balance) -> bool;
|
||||
}
|
||||
|
||||
/// Return `true` iff `n1 / d1 < n2 / d2`. `d1` and `d2` may not be zero.
|
||||
fn compare_rationals<
|
||||
T: Zero + Mul<T, Output = T> + Div<T, Output = T> + Rem<T, Output = T> + Ord + Copy,
|
||||
>(
|
||||
mut n1: T,
|
||||
mut d1: T,
|
||||
mut n2: T,
|
||||
mut d2: T,
|
||||
) -> bool {
|
||||
// Uses a continued fractional representation for a non-overflowing compare.
|
||||
// Detailed at https://janmr.com/blog/2014/05/comparing-rational-numbers-without-overflow/.
|
||||
loop {
|
||||
let q1 = n1 / d1;
|
||||
let q2 = n2 / d2;
|
||||
if q1 < q2 {
|
||||
return true;
|
||||
}
|
||||
if q2 < q1 {
|
||||
return false;
|
||||
}
|
||||
let r1 = n1 % d1;
|
||||
let r2 = n2 % d2;
|
||||
if r2.is_zero() {
|
||||
return false;
|
||||
}
|
||||
if r1.is_zero() {
|
||||
return true;
|
||||
}
|
||||
n1 = d2;
|
||||
n2 = d1;
|
||||
d1 = r2;
|
||||
d2 = r1;
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Balance: IntegerSquareRoot
|
||||
+ Zero
|
||||
+ Ord
|
||||
+ Add<Balance, Output = Balance>
|
||||
+ Mul<Balance, Output = Balance>
|
||||
+ Div<Balance, Output = Balance>
|
||||
+ Rem<Balance, Output = Balance>
|
||||
+ Copy,
|
||||
> Approved<Balance> for VoteThreshold
|
||||
{
|
||||
fn approved(&self, tally: Tally<Balance>, electorate: Balance) -> bool {
|
||||
let sqrt_voters = tally.turnout.integer_sqrt();
|
||||
let sqrt_electorate = electorate.integer_sqrt();
|
||||
if sqrt_voters.is_zero() {
|
||||
return false;
|
||||
}
|
||||
match *self {
|
||||
VoteThreshold::SuperMajorityApprove =>
|
||||
compare_rationals(tally.nays, sqrt_voters, tally.ayes, sqrt_electorate),
|
||||
VoteThreshold::SuperMajorityAgainst =>
|
||||
compare_rationals(tally.nays, sqrt_electorate, tally.ayes, sqrt_voters),
|
||||
VoteThreshold::SimpleMajority => tally.ayes > tally.nays,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_work() {
|
||||
assert!(!VoteThreshold::SuperMajorityApprove
|
||||
.approved(Tally { ayes: 60, nays: 50, turnout: 110 }, 210));
|
||||
assert!(VoteThreshold::SuperMajorityApprove
|
||||
.approved(Tally { ayes: 100, nays: 50, turnout: 150 }, 210));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user