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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -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))
);
});
}
}
+261
View File
@@ -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);
});
}
+255
View File
@@ -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),
}
+243
View File
@@ -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