mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 00:31:07 +00:00
Several tweaks needed for Governance 2.0 (#11124)
* Add stepped curve for referenda * Treasury SpendOrigin * Add tests * Better Origin Or-gating * Reciprocal curve * Tests for reciprical and rounding in PerThings * Tweaks and new quad curve * Const derivation of reciprocal curve parameters * Remove some unneeded code * Actually useful linear curve * Fixes * Provisional curves * Rejig 'turnout' as 'support' * Use TypedGet * Fixes * Enable curve's ceil to be configured * Formatting * Fixes * Fixes * Fixes * Remove EnsureOneOf * Fixes * Fixes * Fixes * Formatting * Fixes * Update frame/support/src/traits/dispatch.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Grumbles * Formatting * Fixes * APIs of VoteTally should include class * Fixes * Fix overlay prefix removal result * Second part of the overlay prefix removal fix. * Formatting * Fixes * Add some tests and make clear rounding algo * Fixes * Formatting * Revert questionable fix * Introduce test for kill_prefix * Fixes * Formatting * Fixes * Fix possible overflow * Docs * Add benchmark test * Formatting * Update frame/referenda/src/types.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Docs * Fixes * Use latest API in tests * Formatting * Whitespace * Use latest API in tests Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
This commit is contained in:
@@ -109,7 +109,6 @@ parameter_types! {
|
||||
pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
|
||||
}
|
||||
|
||||
// impl pallet_treasury::Config for Test {
|
||||
impl pallet_treasury::Config for Test {
|
||||
type PalletId = TreasuryPalletId;
|
||||
type Currency = pallet_balances::Pallet<Test>;
|
||||
@@ -126,6 +125,7 @@ impl pallet_treasury::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type SpendFunds = Bounties;
|
||||
type MaxApprovals = ConstU32<100>;
|
||||
type SpendOrigin = frame_support::traits::NeverEnsureOrigin<u64>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -130,6 +130,7 @@ impl pallet_treasury::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type SpendFunds = Bounties;
|
||||
type MaxApprovals = ConstU32<100>;
|
||||
type SpendOrigin = frame_support::traits::NeverEnsureOrigin<u64>;
|
||||
}
|
||||
parameter_types! {
|
||||
// This will be 50% of the bounty fee.
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::collections::BTreeMap;
|
||||
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok, parameter_types,
|
||||
traits::{ConstU32, ConstU64, Contains, Polling},
|
||||
traits::{ConstU32, ConstU64, Contains, Polling, VoteTally},
|
||||
};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
@@ -166,7 +166,7 @@ impl Polling<TallyOf<Test>> for TestPolls {
|
||||
fn create_ongoing(class: Self::Class) -> Result<Self::Index, ()> {
|
||||
let mut polls = Polls::get();
|
||||
let i = polls.keys().rev().next().map_or(0, |x| x + 1);
|
||||
polls.insert(i, Ongoing(Tally::default(), class));
|
||||
polls.insert(i, Ongoing(Tally::new(0), class));
|
||||
Polls::set(polls);
|
||||
Ok(i)
|
||||
}
|
||||
@@ -271,19 +271,19 @@ fn basic_voting_works() {
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(2, 5)));
|
||||
assert_eq!(tally(3), Tally::from_parts(10, 0, 2));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(2, 5)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 10, 2));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 10, 0));
|
||||
assert_eq!(Balances::usable_balance(1), 8);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(5, 1)));
|
||||
assert_eq!(tally(3), Tally::from_parts(5, 0, 5));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(5, 1)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 5, 5));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 5, 0));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(10, 0)));
|
||||
assert_eq!(tally(3), Tally::from_parts(1, 0, 10));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(10, 0)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 1, 10));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 1, 0));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), None, 3));
|
||||
@@ -300,19 +300,19 @@ fn voting_balance_gets_locked() {
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(2, 5)));
|
||||
assert_eq!(tally(3), Tally::from_parts(10, 0, 2));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(2, 5)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 10, 2));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 10, 0));
|
||||
assert_eq!(Balances::usable_balance(1), 8);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(5, 1)));
|
||||
assert_eq!(tally(3), Tally::from_parts(5, 0, 5));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(5, 1)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 5, 5));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 5, 0));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, aye(10, 0)));
|
||||
assert_eq!(tally(3), Tally::from_parts(1, 0, 10));
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 3, nay(10, 0)));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 1, 10));
|
||||
assert_eq!(tally(3), Tally::from_parts(0, 1, 0));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), None, 3));
|
||||
@@ -376,10 +376,10 @@ fn classwise_delegation_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(
|
||||
vec![
|
||||
(0, Ongoing(Tally::default(), 0)),
|
||||
(1, Ongoing(Tally::default(), 1)),
|
||||
(2, Ongoing(Tally::default(), 2)),
|
||||
(3, Ongoing(Tally::default(), 2)),
|
||||
(0, Ongoing(Tally::new(0), 0)),
|
||||
(1, Ongoing(Tally::new(0), 1)),
|
||||
(2, Ongoing(Tally::new(0), 2)),
|
||||
(3, Ongoing(Tally::new(0), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
@@ -403,9 +403,9 @@ fn classwise_delegation_works() {
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(6, 2, 35), 0)),
|
||||
(1, Ongoing(Tally::from_parts(6, 2, 35), 1)),
|
||||
(2, Ongoing(Tally::from_parts(6, 2, 35), 2)),
|
||||
(0, Ongoing(Tally::from_parts(6, 2, 15), 0)),
|
||||
(1, Ongoing(Tally::from_parts(6, 2, 15), 1)),
|
||||
(2, Ongoing(Tally::from_parts(6, 2, 15), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 0, 0), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
@@ -417,10 +417,10 @@ fn classwise_delegation_works() {
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(6, 2, 35), 0)),
|
||||
(1, Ongoing(Tally::from_parts(6, 2, 35), 1)),
|
||||
(2, Ongoing(Tally::from_parts(6, 2, 35), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 6, 15), 2)),
|
||||
(0, Ongoing(Tally::from_parts(6, 2, 15), 0)),
|
||||
(1, Ongoing(Tally::from_parts(6, 2, 15), 1)),
|
||||
(2, Ongoing(Tally::from_parts(6, 2, 15), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 6, 0), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
@@ -432,10 +432,10 @@ fn classwise_delegation_works() {
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(6, 2, 35), 0)),
|
||||
(1, Ongoing(Tally::from_parts(6, 2, 35), 1)),
|
||||
(2, Ongoing(Tally::from_parts(1, 7, 35), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 1, 10), 2)),
|
||||
(0, Ongoing(Tally::from_parts(6, 2, 15), 0)),
|
||||
(1, Ongoing(Tally::from_parts(6, 2, 15), 1)),
|
||||
(2, Ongoing(Tally::from_parts(1, 7, 10), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 1, 0), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
@@ -451,10 +451,10 @@ fn classwise_delegation_works() {
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(4, 2, 33), 0)),
|
||||
(1, Ongoing(Tally::from_parts(4, 2, 33), 1)),
|
||||
(2, Ongoing(Tally::from_parts(4, 2, 33), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 4, 13), 2)),
|
||||
(0, Ongoing(Tally::from_parts(4, 2, 13), 0)),
|
||||
(1, Ongoing(Tally::from_parts(4, 2, 13), 1)),
|
||||
(2, Ongoing(Tally::from_parts(4, 2, 13), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 4, 0), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
@@ -483,10 +483,10 @@ fn classwise_delegation_works() {
|
||||
assert_eq!(
|
||||
Polls::get(),
|
||||
vec![
|
||||
(0, Ongoing(Tally::from_parts(7, 2, 36), 0)),
|
||||
(1, Ongoing(Tally::from_parts(8, 2, 37), 1)),
|
||||
(2, Ongoing(Tally::from_parts(9, 2, 38), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 9, 18), 2)),
|
||||
(0, Ongoing(Tally::from_parts(7, 2, 16), 0)),
|
||||
(1, Ongoing(Tally::from_parts(8, 2, 17), 1)),
|
||||
(2, Ongoing(Tally::from_parts(9, 2, 18), 2)),
|
||||
(3, Ongoing(Tally::from_parts(0, 9, 0), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
@@ -497,7 +497,7 @@ fn classwise_delegation_works() {
|
||||
#[test]
|
||||
fn redelegation_after_vote_ending_should_keep_lock() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(vec![(0, Ongoing(Tally::default(), 0))].into_iter().collect());
|
||||
Polls::set(vec![(0, Ongoing(Tally::new(0), 0))].into_iter().collect());
|
||||
assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 5));
|
||||
assert_ok!(Voting::vote(Origin::signed(2), 0, aye(10, 1)));
|
||||
Polls::set(vec![(0, Completed(1, true))].into_iter().collect());
|
||||
@@ -515,9 +515,9 @@ fn lock_amalgamation_valid_with_multiple_removed_votes() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(
|
||||
vec![
|
||||
(0, Ongoing(Tally::default(), 0)),
|
||||
(1, Ongoing(Tally::default(), 0)),
|
||||
(2, Ongoing(Tally::default(), 0)),
|
||||
(0, Ongoing(Tally::new(0), 0)),
|
||||
(1, Ongoing(Tally::new(0), 0)),
|
||||
(2, Ongoing(Tally::new(0), 0)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
@@ -587,7 +587,7 @@ fn lock_amalgamation_valid_with_multiple_delegations() {
|
||||
#[test]
|
||||
fn lock_amalgamation_valid_with_move_roundtrip_to_delegation() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(vec![(0, Ongoing(Tally::default(), 0))].into_iter().collect());
|
||||
Polls::set(vec![(0, Ongoing(Tally::new(0), 0))].into_iter().collect());
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 0, aye(5, 1)));
|
||||
Polls::set(vec![(0, Completed(1, true))].into_iter().collect());
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 0));
|
||||
@@ -599,7 +599,7 @@ fn lock_amalgamation_valid_with_move_roundtrip_to_delegation() {
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 0);
|
||||
|
||||
Polls::set(vec![(1, Ongoing(Tally::default(), 0))].into_iter().collect());
|
||||
Polls::set(vec![(1, Ongoing(Tally::new(0), 0))].into_iter().collect());
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 1, aye(5, 2)));
|
||||
Polls::set(vec![(1, Completed(1, true))].into_iter().collect());
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 1));
|
||||
@@ -627,7 +627,7 @@ fn lock_amalgamation_valid_with_move_roundtrip_to_casting() {
|
||||
assert_ok!(Voting::unlock(Origin::signed(1), 0, 1));
|
||||
assert_eq!(Balances::usable_balance(1), 5);
|
||||
|
||||
Polls::set(vec![(0, Ongoing(Tally::default(), 0))].into_iter().collect());
|
||||
Polls::set(vec![(0, Ongoing(Tally::new(0), 0))].into_iter().collect());
|
||||
assert_ok!(Voting::vote(Origin::signed(1), 0, aye(10, 1)));
|
||||
Polls::set(vec![(0, Completed(1, true))].into_iter().collect());
|
||||
assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 0));
|
||||
@@ -688,9 +688,9 @@ fn lock_aggregation_over_different_classes_with_casting_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Polls::set(
|
||||
vec![
|
||||
(0, Ongoing(Tally::default(), 0)),
|
||||
(1, Ongoing(Tally::default(), 1)),
|
||||
(2, Ongoing(Tally::default(), 2)),
|
||||
(0, Ongoing(Tally::new(0), 0)),
|
||||
(1, Ongoing(Tally::new(0), 1)),
|
||||
(2, Ongoing(Tally::new(0), 2)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
@@ -747,10 +747,10 @@ fn errors_with_vote_work() {
|
||||
assert_ok!(Voting::undelegate(Origin::signed(1), 0));
|
||||
Polls::set(
|
||||
vec![
|
||||
(0, Ongoing(Tally::default(), 0)),
|
||||
(1, Ongoing(Tally::default(), 0)),
|
||||
(2, Ongoing(Tally::default(), 0)),
|
||||
(3, Ongoing(Tally::default(), 0)),
|
||||
(0, Ongoing(Tally::new(0), 0)),
|
||||
(1, Ongoing(Tally::new(0), 0)),
|
||||
(2, Ongoing(Tally::new(0), 0)),
|
||||
(3, Ongoing(Tally::new(0), 0)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
|
||||
@@ -17,18 +17,16 @@
|
||||
|
||||
//! Miscellaneous additional datatypes.
|
||||
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
use codec::{Codec, Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
traits::VoteTally, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound,
|
||||
RuntimeDebugNoBound,
|
||||
traits::VoteTally, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
use super::*;
|
||||
use crate::{AccountVote, Conviction, Vote};
|
||||
@@ -36,7 +34,6 @@ use crate::{AccountVote, Conviction, Vote};
|
||||
/// Info regarding an ongoing referendum.
|
||||
#[derive(
|
||||
CloneNoBound,
|
||||
DefaultNoBound,
|
||||
PartialEqNoBound,
|
||||
EqNoBound,
|
||||
RuntimeDebugNoBound,
|
||||
@@ -46,84 +43,84 @@ use crate::{AccountVote, Conviction, Vote};
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
#[scale_info(skip_type_params(Total))]
|
||||
pub struct Tally<
|
||||
Votes: Clone + Default + PartialEq + Eq + sp_std::fmt::Debug + TypeInfo + Codec,
|
||||
Total,
|
||||
> {
|
||||
pub struct Tally<Votes: Clone + PartialEq + Eq + Debug + TypeInfo + Codec, Total> {
|
||||
/// The number of aye votes, expressed in terms of post-conviction lock-vote.
|
||||
pub ayes: Votes,
|
||||
/// The number of nay votes, expressed in terms of post-conviction lock-vote.
|
||||
pub nays: Votes,
|
||||
/// The amount of funds currently expressing its opinion. Pre-conviction.
|
||||
pub turnout: Votes,
|
||||
/// The basic number of aye votes, expressed pre-conviction.
|
||||
pub support: Votes,
|
||||
/// Dummy.
|
||||
dummy: PhantomData<Total>,
|
||||
}
|
||||
|
||||
impl<
|
||||
Votes: Clone
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ sp_std::fmt::Debug
|
||||
+ Copy
|
||||
+ AtLeast32BitUnsigned
|
||||
+ TypeInfo
|
||||
+ Codec,
|
||||
Votes: Clone + Default + PartialEq + Eq + Debug + Copy + AtLeast32BitUnsigned + TypeInfo + Codec,
|
||||
Total: Get<Votes>,
|
||||
> VoteTally<Votes> for Tally<Votes, Total>
|
||||
Class,
|
||||
> VoteTally<Votes, Class> for Tally<Votes, Total>
|
||||
{
|
||||
fn ayes(&self) -> Votes {
|
||||
fn new(_: Class) -> Self {
|
||||
Self { ayes: Zero::zero(), nays: Zero::zero(), support: Zero::zero(), dummy: PhantomData }
|
||||
}
|
||||
|
||||
fn ayes(&self, _: Class) -> Votes {
|
||||
self.ayes
|
||||
}
|
||||
|
||||
fn turnout(&self) -> Perbill {
|
||||
Perbill::from_rational(self.turnout, Total::get())
|
||||
fn support(&self, _: Class) -> Perbill {
|
||||
Perbill::from_rational(self.support, Total::get())
|
||||
}
|
||||
|
||||
fn approval(&self) -> Perbill {
|
||||
fn approval(&self, _: Class) -> Perbill {
|
||||
Perbill::from_rational(self.ayes, self.ayes.saturating_add(self.nays))
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn unanimity() -> Self {
|
||||
Self { ayes: Total::get(), nays: Zero::zero(), turnout: Total::get(), dummy: PhantomData }
|
||||
fn unanimity(_: Class) -> Self {
|
||||
Self { ayes: Total::get(), nays: Zero::zero(), support: Total::get(), dummy: PhantomData }
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn from_requirements(turnout: Perbill, approval: Perbill) -> Self {
|
||||
let turnout = turnout.mul_ceil(Total::get());
|
||||
let ayes = approval.mul_ceil(turnout);
|
||||
Self { ayes, nays: turnout - ayes, turnout, dummy: PhantomData }
|
||||
fn rejection(_: Class) -> Self {
|
||||
Self { ayes: Zero::zero(), nays: Total::get(), support: Total::get(), dummy: PhantomData }
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn from_requirements(support: Perbill, approval: Perbill, _: Class) -> Self {
|
||||
let support = support.mul_ceil(Total::get());
|
||||
let ayes = approval.mul_ceil(support);
|
||||
Self { ayes, nays: support - ayes, support, dummy: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Votes: Clone
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ sp_std::fmt::Debug
|
||||
+ Copy
|
||||
+ AtLeast32BitUnsigned
|
||||
+ TypeInfo
|
||||
+ Codec,
|
||||
Votes: Clone + Default + PartialEq + Eq + Debug + Copy + AtLeast32BitUnsigned + TypeInfo + Codec,
|
||||
Total: Get<Votes>,
|
||||
> Tally<Votes, Total>
|
||||
{
|
||||
/// Create a new tally.
|
||||
pub fn new(vote: Vote, balance: Votes) -> Self {
|
||||
pub fn from_vote(vote: Vote, balance: Votes) -> Self {
|
||||
let Delegations { votes, capital } = vote.conviction.votes(balance);
|
||||
Self {
|
||||
ayes: if vote.aye { votes } else { Zero::zero() },
|
||||
nays: if vote.aye { Zero::zero() } else { votes },
|
||||
turnout: capital,
|
||||
support: capital,
|
||||
dummy: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_parts(ayes: Votes, nays: Votes, turnout: Votes) -> Self {
|
||||
Self { ayes, nays, turnout, dummy: PhantomData }
|
||||
pub fn from_parts(
|
||||
ayes_with_conviction: Votes,
|
||||
nays_with_conviction: Votes,
|
||||
ayes: Votes,
|
||||
) -> Self {
|
||||
Self {
|
||||
ayes: ayes_with_conviction,
|
||||
nays: nays_with_conviction,
|
||||
support: ayes,
|
||||
dummy: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an account's vote into the tally.
|
||||
@@ -131,16 +128,18 @@ impl<
|
||||
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)?,
|
||||
true => {
|
||||
self.support = self.support.checked_add(&capital)?;
|
||||
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.support = self.support.checked_add(&aye.capital)?;
|
||||
self.ayes = self.ayes.checked_add(&aye.votes)?;
|
||||
self.nays = self.nays.checked_add(&nay.votes)?;
|
||||
},
|
||||
@@ -153,16 +152,18 @@ impl<
|
||||
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)?,
|
||||
true => {
|
||||
self.support = self.support.checked_sub(&capital)?;
|
||||
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.support = self.support.checked_sub(&aye.capital)?;
|
||||
self.ayes = self.ayes.checked_sub(&aye.votes)?;
|
||||
self.nays = self.nays.checked_sub(&nay.votes)?;
|
||||
},
|
||||
@@ -172,18 +173,22 @@ impl<
|
||||
|
||||
/// Increment some amount of votes.
|
||||
pub fn increase(&mut self, approve: bool, delegations: Delegations<Votes>) {
|
||||
self.turnout = self.turnout.saturating_add(delegations.capital);
|
||||
match approve {
|
||||
true => self.ayes = self.ayes.saturating_add(delegations.votes),
|
||||
true => {
|
||||
self.support = self.support.saturating_add(delegations.capital);
|
||||
self.ayes = self.ayes.saturating_add(delegations.votes);
|
||||
},
|
||||
false => self.nays = self.nays.saturating_add(delegations.votes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrement some amount of votes.
|
||||
pub fn reduce(&mut self, approve: bool, delegations: Delegations<Votes>) {
|
||||
self.turnout = self.turnout.saturating_sub(delegations.capital);
|
||||
match approve {
|
||||
true => self.ayes = self.ayes.saturating_sub(delegations.votes),
|
||||
true => {
|
||||
self.support = self.support.saturating_sub(delegations.capital);
|
||||
self.ayes = self.ayes.saturating_sub(delegations.votes);
|
||||
},
|
||||
false => self.nays = self.nays.saturating_sub(delegations.votes),
|
||||
}
|
||||
}
|
||||
@@ -196,7 +201,7 @@ impl<
|
||||
pub struct Delegations<Balance> {
|
||||
/// The number of votes (this is post-conviction).
|
||||
pub votes: Balance,
|
||||
/// The amount of raw capital, used for the turnout.
|
||||
/// The amount of raw capital, used for the support.
|
||||
pub capital: Balance,
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate as pallet_identity;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok, ord_parameter_types, parameter_types,
|
||||
traits::{ConstU32, ConstU64, EnsureOneOf},
|
||||
traits::{ConstU32, ConstU64, EitherOfDiverse},
|
||||
BoundedVec,
|
||||
};
|
||||
use frame_system::{EnsureRoot, EnsureSignedBy};
|
||||
@@ -100,8 +100,8 @@ ord_parameter_types! {
|
||||
pub const One: u64 = 1;
|
||||
pub const Two: u64 = 2;
|
||||
}
|
||||
type EnsureOneOrRoot = EnsureOneOf<EnsureRoot<u64>, EnsureSignedBy<One, u64>>;
|
||||
type EnsureTwoOrRoot = EnsureOneOf<EnsureRoot<u64>, EnsureSignedBy<Two, u64>>;
|
||||
type EnsureOneOrRoot = EitherOfDiverse<EnsureRoot<u64>, EnsureSignedBy<One, u64>>;
|
||||
type EnsureTwoOrRoot = EitherOfDiverse<EnsureRoot<u64>, EnsureSignedBy<Two, u64>>;
|
||||
impl pallet_identity::Config for Test {
|
||||
type Event = Event;
|
||||
type Currency = Balances;
|
||||
|
||||
@@ -19,6 +19,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features =
|
||||
] }
|
||||
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
|
||||
serde = { version = "1.0.136", features = ["derive"], optional = true }
|
||||
sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" }
|
||||
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
|
||||
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
|
||||
@@ -39,6 +40,8 @@ std = [
|
||||
"codec/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"sp-runtime/std",
|
||||
"sp-arithmetic/std",
|
||||
"frame-system/std",
|
||||
"scale-info/std",
|
||||
"serde",
|
||||
|
||||
@@ -101,27 +101,27 @@ fn info<T: Config>(index: ReferendumIndex) -> &'static TrackInfoOf<T> {
|
||||
}
|
||||
|
||||
fn make_passing_after<T: Config>(index: ReferendumIndex, period_portion: Perbill) {
|
||||
let turnout = info::<T>(index).min_turnout.threshold(period_portion);
|
||||
let support = info::<T>(index).min_support.threshold(period_portion);
|
||||
let approval = info::<T>(index).min_approval.threshold(period_portion);
|
||||
Referenda::<T>::access_poll(index, |status| {
|
||||
if let PollStatus::Ongoing(tally, ..) = status {
|
||||
*tally = T::Tally::from_requirements(turnout, approval);
|
||||
if let PollStatus::Ongoing(tally, class) = status {
|
||||
*tally = T::Tally::from_requirements(support, approval, class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn make_passing<T: Config>(index: ReferendumIndex) {
|
||||
Referenda::<T>::access_poll(index, |status| {
|
||||
if let PollStatus::Ongoing(tally, ..) = status {
|
||||
*tally = T::Tally::unanimity();
|
||||
if let PollStatus::Ongoing(tally, class) = status {
|
||||
*tally = T::Tally::unanimity(class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn make_failing<T: Config>(index: ReferendumIndex) {
|
||||
Referenda::<T>::access_poll(index, |status| {
|
||||
if let PollStatus::Ongoing(tally, ..) = status {
|
||||
*tally = T::Tally::default();
|
||||
if let PollStatus::Ongoing(tally, class) = status {
|
||||
*tally = T::Tally::rejection(class);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -501,6 +501,7 @@ benchmarks! {
|
||||
let (_caller, index) = create_referendum::<T>();
|
||||
place_deposit::<T>(index);
|
||||
skip_prepare_period::<T>(index);
|
||||
make_failing::<T>(index);
|
||||
nudge::<T>(index);
|
||||
skip_decision_period::<T>(index);
|
||||
}: nudge_referendum(RawOrigin::Root, index)
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
//! In order to become concluded, one of three things must happen:
|
||||
//! - The referendum should remain in an unbroken _Passing_ state for a period of time. This
|
||||
//! is known as the _Confirmation Period_ and is determined by the track. A referendum is considered
|
||||
//! _Passing_ when there is a sufficiently high turnout and approval, given the amount of time it
|
||||
//! _Passing_ when there is a sufficiently high support and approval, given the amount of time it
|
||||
//! has been being decided. Generally the threshold for what counts as being "sufficiently high"
|
||||
//! will reduce over time. The curves setting these thresholds are determined by the track. In this
|
||||
//! case, the referendum is considered _Approved_ and the proposal is scheduled for dispatch.
|
||||
@@ -54,6 +54,10 @@
|
||||
//!
|
||||
//! Once a referendum is concluded, the decision deposit may be refunded.
|
||||
//!
|
||||
//! ## Terms
|
||||
//! - *Support*: The number of aye-votes, pre-conviction, as a proportion of the total number of
|
||||
//! pre-conviction votes able to be cast in the population.
|
||||
//!
|
||||
//! - [`Config`]
|
||||
//! - [`Call`]
|
||||
|
||||
@@ -148,7 +152,12 @@ pub mod pallet {
|
||||
/// The counting type for votes. Usually just balance.
|
||||
type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member;
|
||||
/// The tallying type.
|
||||
type Tally: VoteTally<Self::Votes> + Default + Clone + Codec + Eq + Debug + TypeInfo;
|
||||
type Tally: VoteTally<Self::Votes, TrackIdOf<Self, I>>
|
||||
+ Clone
|
||||
+ Codec
|
||||
+ Eq
|
||||
+ Debug
|
||||
+ TypeInfo;
|
||||
|
||||
// Constants
|
||||
/// The minimum amount to be used as a deposit for a public referendum proposal.
|
||||
@@ -369,7 +378,7 @@ pub mod pallet {
|
||||
submission_deposit,
|
||||
decision_deposit: None,
|
||||
deciding: None,
|
||||
tally: Default::default(),
|
||||
tally: TallyOf::<T, I>::new(track),
|
||||
in_queue: false,
|
||||
alarm: Self::set_alarm(nudge_call, now.saturating_add(T::UndecidingTimeout::get())),
|
||||
};
|
||||
@@ -613,7 +622,7 @@ impl<T: Config<I>, I: 'static> Polling<T::Tally> for Pallet<T, I> {
|
||||
submission_deposit: Deposit { who: dummy_account_id, amount: Zero::zero() },
|
||||
decision_deposit: None,
|
||||
deciding: None,
|
||||
tally: Default::default(),
|
||||
tally: TallyOf::<T, I>::new(class),
|
||||
in_queue: false,
|
||||
alarm: None,
|
||||
};
|
||||
@@ -723,8 +732,9 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
&status.tally,
|
||||
Zero::zero(),
|
||||
track.decision_period,
|
||||
&track.min_turnout,
|
||||
&track.min_support,
|
||||
&track.min_approval,
|
||||
status.track,
|
||||
);
|
||||
status.in_queue = false;
|
||||
Self::deposit_event(Event::<T, I>::DecisionStarted {
|
||||
@@ -740,7 +750,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
None
|
||||
};
|
||||
let deciding_status = DecidingStatus { since: now, confirming };
|
||||
let alarm = Self::decision_time(&deciding_status, &status.tally, track);
|
||||
let alarm = Self::decision_time(&deciding_status, &status.tally, status.track, track);
|
||||
status.deciding = Some(deciding_status);
|
||||
let branch =
|
||||
if is_passing { BeginDecidingBranch::Passing } else { BeginDecidingBranch::Failing };
|
||||
@@ -765,7 +775,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
(r.0, r.1.into())
|
||||
} else {
|
||||
// Add to queue.
|
||||
let item = (index, status.tally.ayes());
|
||||
let item = (index, status.tally.ayes(status.track));
|
||||
status.in_queue = true;
|
||||
TrackQueue::<T, I>::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1));
|
||||
(None, ServiceBranch::Queued)
|
||||
@@ -872,7 +882,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
// Are we already queued for deciding?
|
||||
if status.in_queue {
|
||||
// Does our position in the queue need updating?
|
||||
let ayes = status.tally.ayes();
|
||||
let ayes = status.tally.ayes(status.track);
|
||||
let mut queue = TrackQueue::<T, I>::get(status.track);
|
||||
let maybe_old_pos = queue.iter().position(|(x, _)| *x == index);
|
||||
let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x);
|
||||
@@ -930,8 +940,9 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
&status.tally,
|
||||
now.saturating_sub(deciding.since),
|
||||
track.decision_period,
|
||||
&track.min_turnout,
|
||||
&track.min_support,
|
||||
&track.min_approval,
|
||||
status.track,
|
||||
);
|
||||
branch = if is_passing {
|
||||
match deciding.confirming {
|
||||
@@ -996,7 +1007,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
ServiceBranch::ContinueNotConfirming
|
||||
}
|
||||
};
|
||||
alarm = Self::decision_time(deciding, &status.tally, track);
|
||||
alarm = Self::decision_time(deciding, &status.tally, status.track, track);
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1009,15 +1020,16 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
fn decision_time(
|
||||
deciding: &DecidingStatusOf<T>,
|
||||
tally: &T::Tally,
|
||||
track_id: TrackIdOf<T, I>,
|
||||
track: &TrackInfoOf<T, I>,
|
||||
) -> T::BlockNumber {
|
||||
deciding.confirming.unwrap_or_else(|| {
|
||||
// Set alarm to the point where the current voting would make it pass.
|
||||
let approval = tally.approval();
|
||||
let turnout = tally.turnout();
|
||||
let approval = tally.approval(track_id);
|
||||
let support = tally.support(track_id);
|
||||
let until_approval = track.min_approval.delay(approval);
|
||||
let until_turnout = track.min_turnout.delay(turnout);
|
||||
let offset = until_turnout.max(until_approval);
|
||||
let until_support = track.min_support.delay(support);
|
||||
let offset = until_support.max(until_approval);
|
||||
deciding.since.saturating_add(offset * track.decision_period)
|
||||
})
|
||||
}
|
||||
@@ -1062,16 +1074,18 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
|
||||
/// Determine whether the given `tally` would result in a referendum passing at `elapsed` blocks
|
||||
/// into a total decision `period`, given the two curves for `turnout_needed` and
|
||||
/// into a total decision `period`, given the two curves for `support_needed` and
|
||||
/// `approval_needed`.
|
||||
fn is_passing(
|
||||
tally: &T::Tally,
|
||||
elapsed: T::BlockNumber,
|
||||
period: T::BlockNumber,
|
||||
turnout_needed: &Curve,
|
||||
support_needed: &Curve,
|
||||
approval_needed: &Curve,
|
||||
id: TrackIdOf<T, I>,
|
||||
) -> bool {
|
||||
let x = Perbill::from_rational(elapsed.min(period), period);
|
||||
turnout_needed.passing(x, tally.turnout()) && approval_needed.passing(x, tally.approval())
|
||||
support_needed.passing(x, tally.support(id)) &&
|
||||
approval_needed.passing(x, tally.approval(id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,12 +160,14 @@ impl TracksInfo<u64, u64> for TestTracksInfo {
|
||||
confirm_period: 2,
|
||||
min_enactment_period: 4,
|
||||
min_approval: Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(100),
|
||||
delta: Perbill::from_percent(50),
|
||||
length: Perbill::from_percent(100),
|
||||
floor: Perbill::from_percent(50),
|
||||
ceil: Perbill::from_percent(100),
|
||||
},
|
||||
min_turnout: Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(100),
|
||||
delta: Perbill::from_percent(100),
|
||||
min_support: Curve::LinearDecreasing {
|
||||
length: Perbill::from_percent(100),
|
||||
floor: Perbill::from_percent(0),
|
||||
ceil: Perbill::from_percent(100),
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -180,12 +182,14 @@ impl TracksInfo<u64, u64> for TestTracksInfo {
|
||||
confirm_period: 1,
|
||||
min_enactment_period: 2,
|
||||
min_approval: Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(55),
|
||||
delta: Perbill::from_percent(5),
|
||||
length: Perbill::from_percent(100),
|
||||
floor: Perbill::from_percent(95),
|
||||
ceil: Perbill::from_percent(100),
|
||||
},
|
||||
min_turnout: Curve::LinearDecreasing {
|
||||
begin: Perbill::from_percent(10),
|
||||
delta: Perbill::from_percent(10),
|
||||
min_support: Curve::LinearDecreasing {
|
||||
length: Perbill::from_percent(100),
|
||||
floor: Perbill::from_percent(90),
|
||||
ceil: Perbill::from_percent(100),
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -241,35 +245,48 @@ pub fn new_test_ext_execute_with_cond(execute: impl FnOnce(bool) -> () + Clone)
|
||||
new_test_ext().execute_with(|| execute(true));
|
||||
}
|
||||
|
||||
#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, Default, MaxEncodedLen)]
|
||||
#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, MaxEncodedLen)]
|
||||
pub struct Tally {
|
||||
pub ayes: u32,
|
||||
pub nays: u32,
|
||||
}
|
||||
|
||||
impl VoteTally<u32> for Tally {
|
||||
fn ayes(&self) -> u32 {
|
||||
impl<Class> VoteTally<u32, Class> for Tally {
|
||||
fn new(_: Class) -> Self {
|
||||
Self { ayes: 0, nays: 0 }
|
||||
}
|
||||
|
||||
fn ayes(&self, _: Class) -> u32 {
|
||||
self.ayes
|
||||
}
|
||||
|
||||
fn turnout(&self) -> Perbill {
|
||||
Perbill::from_percent(self.ayes + self.nays)
|
||||
fn support(&self, _: Class) -> Perbill {
|
||||
Perbill::from_percent(self.ayes)
|
||||
}
|
||||
|
||||
fn approval(&self) -> Perbill {
|
||||
Perbill::from_rational(self.ayes, self.ayes + self.nays)
|
||||
fn approval(&self, _: Class) -> Perbill {
|
||||
if self.ayes + self.nays > 0 {
|
||||
Perbill::from_rational(self.ayes, self.ayes + self.nays)
|
||||
} else {
|
||||
Perbill::zero()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn unanimity() -> Self {
|
||||
fn unanimity(_: Class) -> Self {
|
||||
Self { ayes: 100, nays: 0 }
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn from_requirements(turnout: Perbill, approval: Perbill) -> Self {
|
||||
let turnout = turnout.mul_ceil(100u32);
|
||||
let ayes = approval.mul_ceil(turnout);
|
||||
Self { ayes, nays: turnout - ayes }
|
||||
fn rejection(_: Class) -> Self {
|
||||
Self { ayes: 0, nays: 100 }
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn from_requirements(support: Perbill, approval: Perbill, _: Class) -> Self {
|
||||
let ayes = support.mul_ceil(100u32);
|
||||
let nays = ((ayes as u64) * 1_000_000_000u64 / approval.deconstruct() as u64) as u32 - ayes;
|
||||
Self { ayes, nays }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -504,12 +504,14 @@ fn set_balance_proposal_is_correctly_filtered_out() {
|
||||
|
||||
#[test]
|
||||
fn curve_handles_all_inputs() {
|
||||
let test_curve = Curve::LinearDecreasing { begin: Perbill::zero(), delta: Perbill::zero() };
|
||||
let test_curve = Curve::LinearDecreasing {
|
||||
length: Perbill::one(),
|
||||
floor: Perbill::zero(),
|
||||
ceil: Perbill::from_percent(100),
|
||||
};
|
||||
|
||||
let delay = test_curve.delay(Perbill::zero());
|
||||
assert_eq!(delay, Perbill::zero());
|
||||
|
||||
let test_curve = Curve::LinearDecreasing { begin: Perbill::zero(), delta: Perbill::one() };
|
||||
assert_eq!(delay, Perbill::one());
|
||||
|
||||
let threshold = test_curve.threshold(Perbill::one());
|
||||
assert_eq!(threshold, Perbill::zero());
|
||||
|
||||
@@ -21,7 +21,8 @@ use super::*;
|
||||
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
|
||||
use frame_support::{traits::schedule::Anon, Parameter};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_arithmetic::{Rounding::*, SignedRounding::*};
|
||||
use sp_runtime::{FixedI64, PerThing, RuntimeDebug};
|
||||
use sp_std::fmt::Debug;
|
||||
|
||||
pub type BalanceOf<T, I = ()> =
|
||||
@@ -91,40 +92,6 @@ impl<T: Ord, S: Get<u32>> InsertSorted<T> for BoundedVec<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_support::traits::ConstU32;
|
||||
|
||||
#[test]
|
||||
fn insert_sorted_works() {
|
||||
let mut b: BoundedVec<u32, ConstU32<6>> = vec![20, 30, 40].try_into().unwrap();
|
||||
assert!(b.insert_sorted_by_key(10, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(60, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(50, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(!b.insert_sorted_by_key(9, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(11, |&x| x));
|
||||
assert_eq!(&b[..], &[11, 20, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(21, |&x| x));
|
||||
assert_eq!(&b[..], &[20, 21, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(61, |&x| x));
|
||||
assert_eq!(&b[..], &[21, 30, 40, 50, 60, 61][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(51, |&x| x));
|
||||
assert_eq!(&b[..], &[30, 40, 50, 51, 60, 61][..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct DecidingStatus<BlockNumber> {
|
||||
/// When this referendum began being "decided". If confirming, then the
|
||||
@@ -143,7 +110,7 @@ pub struct Deposit<AccountId, Balance> {
|
||||
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
pub struct TrackInfo<Balance, Moment> {
|
||||
/// Name of this track. TODO was &'static str
|
||||
/// Name of this track.
|
||||
pub name: &'static str,
|
||||
/// A limit for the number of referenda on this track that can be being decided at once.
|
||||
/// For Root origin this should generally be just one.
|
||||
@@ -161,9 +128,9 @@ pub struct TrackInfo<Balance, Moment> {
|
||||
/// Minimum aye votes as percentage of overall conviction-weighted votes needed for
|
||||
/// approval as a function of time into decision period.
|
||||
pub min_approval: Curve,
|
||||
/// Minimum turnout as percentage of overall population that is needed for
|
||||
/// approval as a function of time into decision period.
|
||||
pub min_turnout: Curve,
|
||||
/// Minimum pre-conviction aye-votes ("support") as percentage of overall population that is
|
||||
/// needed for approval as a function of time into decision period.
|
||||
pub min_support: Curve,
|
||||
}
|
||||
|
||||
/// Information on the voting tracks.
|
||||
@@ -282,21 +249,186 @@ impl<
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
#[cfg_attr(not(feature = "std"), derive(RuntimeDebug))]
|
||||
pub enum Curve {
|
||||
/// Linear curve starting at `(0, begin)`, ending at `(period, begin - delta)`.
|
||||
LinearDecreasing { begin: Perbill, delta: Perbill },
|
||||
/// Linear curve starting at `(0, ceil)`, proceeding linearly to `(length, floor)`, then
|
||||
/// remaining at `floor` until the end of the period.
|
||||
LinearDecreasing { length: Perbill, floor: Perbill, ceil: Perbill },
|
||||
/// Stepped curve, beginning at `(0, begin)`, then remaining constant for `period`, at which
|
||||
/// point it steps down to `(period, begin - step)`. It then remains constant for another
|
||||
/// `period` before stepping down to `(period * 2, begin - step * 2)`. This pattern continues
|
||||
/// but the `y` component has a lower limit of `end`.
|
||||
SteppedDecreasing { begin: Perbill, end: Perbill, step: Perbill, period: Perbill },
|
||||
/// A recipocal (`K/(x+S)-T`) curve: `factor` is `K` and `x_offset` is `S`, `y_offset` is `T`.
|
||||
Reciprocal { factor: FixedI64, x_offset: FixedI64, y_offset: FixedI64 },
|
||||
}
|
||||
|
||||
/// Calculate the quadratic solution for the given curve.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
const fn pos_quad_solution(a: FixedI64, b: FixedI64, c: FixedI64) -> FixedI64 {
|
||||
const TWO: FixedI64 = FixedI64::from_u32(2);
|
||||
const FOUR: FixedI64 = FixedI64::from_u32(4);
|
||||
b.neg().add(b.mul(b).sub(FOUR.mul(a).mul(c)).sqrt()).div(TWO.mul(a))
|
||||
}
|
||||
|
||||
impl Curve {
|
||||
/// Create a `Curve::Linear` instance from a high-level description.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn make_linear(length: u128, period: u128, floor: FixedI64, ceil: FixedI64) -> Curve {
|
||||
let length = FixedI64::from_rational(length, period).into_perbill();
|
||||
let floor = floor.into_perbill();
|
||||
let ceil = ceil.into_perbill();
|
||||
Curve::LinearDecreasing { length, floor, ceil }
|
||||
}
|
||||
|
||||
/// Create a `Curve::Reciprocal` instance from a high-level description.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn make_reciprocal(
|
||||
delay: u128,
|
||||
period: u128,
|
||||
level: FixedI64,
|
||||
floor: FixedI64,
|
||||
ceil: FixedI64,
|
||||
) -> Curve {
|
||||
let delay = FixedI64::from_rational(delay, period).into_perbill();
|
||||
let mut bounds = (
|
||||
(
|
||||
FixedI64::from_u32(0),
|
||||
Self::reciprocal_from_parts(FixedI64::from_u32(0), floor, ceil),
|
||||
FixedI64::from_inner(i64::max_value()),
|
||||
),
|
||||
(
|
||||
FixedI64::from_u32(1),
|
||||
Self::reciprocal_from_parts(FixedI64::from_u32(1), floor, ceil),
|
||||
FixedI64::from_inner(i64::max_value()),
|
||||
),
|
||||
);
|
||||
const TWO: FixedI64 = FixedI64::from_u32(2);
|
||||
while (bounds.1).0.sub((bounds.0).0).into_inner() > 1 {
|
||||
let factor = (bounds.0).0.add((bounds.1).0).div(TWO);
|
||||
let curve = Self::reciprocal_from_parts(factor, floor, ceil);
|
||||
let curve_level = FixedI64::from_perbill(curve.const_threshold(delay));
|
||||
if curve_level.into_inner() > level.into_inner() {
|
||||
bounds = (bounds.0, (factor, curve, curve_level.sub(level)));
|
||||
} else {
|
||||
bounds = ((factor, curve, level.sub(curve_level)), bounds.1);
|
||||
}
|
||||
}
|
||||
if (bounds.0).2.into_inner() < (bounds.1).2.into_inner() {
|
||||
(bounds.0).1
|
||||
} else {
|
||||
(bounds.1).1
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Curve::Reciprocal` instance from basic parameters.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
const fn reciprocal_from_parts(factor: FixedI64, floor: FixedI64, ceil: FixedI64) -> Self {
|
||||
let delta = ceil.sub(floor);
|
||||
let x_offset = pos_quad_solution(delta, delta, factor.neg());
|
||||
let y_offset = floor.sub(factor.div(FixedI64::from_u32(1).add(x_offset)));
|
||||
Curve::Reciprocal { factor, x_offset, y_offset }
|
||||
}
|
||||
|
||||
/// Print some info on the curve.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn info(&self, days: u32, name: impl std::fmt::Display) {
|
||||
let hours = days * 24;
|
||||
println!("Curve {} := {:?}:", name, self);
|
||||
println!(" t + 0h: {:?}", self.threshold(Perbill::zero()));
|
||||
println!(" t + 1h: {:?}", self.threshold(Perbill::from_rational(1, hours)));
|
||||
println!(" t + 2h: {:?}", self.threshold(Perbill::from_rational(2, hours)));
|
||||
println!(" t + 3h: {:?}", self.threshold(Perbill::from_rational(3, hours)));
|
||||
println!(" t + 6h: {:?}", self.threshold(Perbill::from_rational(6, hours)));
|
||||
println!(" t + 12h: {:?}", self.threshold(Perbill::from_rational(12, hours)));
|
||||
println!(" t + 24h: {:?}", self.threshold(Perbill::from_rational(24, hours)));
|
||||
let mut l = 0;
|
||||
for &(n, d) in [(1, 12), (1, 8), (1, 4), (1, 2), (3, 4), (1, 1)].iter() {
|
||||
let t = days * n / d;
|
||||
if t != l {
|
||||
println!(" t + {}d: {:?}", t, self.threshold(Perbill::from_rational(t, days)));
|
||||
l = t;
|
||||
}
|
||||
}
|
||||
let t = |p: Perbill| -> std::string::String {
|
||||
if p.is_one() {
|
||||
"never".into()
|
||||
} else {
|
||||
let minutes = p * (hours * 60);
|
||||
if minutes < 60 {
|
||||
format!("{} minutes", minutes)
|
||||
} else if minutes < 8 * 60 && minutes % 60 != 0 {
|
||||
format!("{} hours {} minutes", minutes / 60, minutes % 60)
|
||||
} else if minutes < 72 * 60 {
|
||||
format!("{} hours", minutes / 60)
|
||||
} else if minutes / 60 % 24 == 0 {
|
||||
format!("{} days", minutes / 60 / 24)
|
||||
} else {
|
||||
format!("{} days {} hours", minutes / 60 / 24, minutes / 60 % 24)
|
||||
}
|
||||
}
|
||||
};
|
||||
if self.delay(Perbill::from_percent(49)) < Perbill::one() {
|
||||
println!(" 30% threshold: {}", t(self.delay(Perbill::from_percent(30))));
|
||||
println!(" 10% threshold: {}", t(self.delay(Perbill::from_percent(10))));
|
||||
println!(" 3% threshold: {}", t(self.delay(Perbill::from_percent(3))));
|
||||
println!(" 1% threshold: {}", t(self.delay(Perbill::from_percent(1))));
|
||||
println!(" 0.1% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 1_000))));
|
||||
println!(" 0.01% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 10_000))));
|
||||
} else {
|
||||
println!(
|
||||
" 99.9% threshold: {}",
|
||||
t(self.delay(Perbill::from_rational(999u32, 1_000)))
|
||||
);
|
||||
println!(" 99% threshold: {}", t(self.delay(Perbill::from_percent(99))));
|
||||
println!(" 95% threshold: {}", t(self.delay(Perbill::from_percent(95))));
|
||||
println!(" 90% threshold: {}", t(self.delay(Perbill::from_percent(90))));
|
||||
println!(" 75% threshold: {}", t(self.delay(Perbill::from_percent(75))));
|
||||
println!(" 60% threshold: {}", t(self.delay(Perbill::from_percent(60))));
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the `y` value for the given `x` value.
|
||||
pub(crate) fn threshold(&self, x: Perbill) -> Perbill {
|
||||
match self {
|
||||
Self::LinearDecreasing { begin, delta } => *begin - (*delta * x).min(*begin),
|
||||
Self::LinearDecreasing { length, floor, ceil } =>
|
||||
*ceil - (x.min(*length).saturating_div(*length, Down) * (*ceil - *floor)),
|
||||
Self::SteppedDecreasing { begin, end, step, period } =>
|
||||
(*begin - (step.int_mul(x.int_div(*period))).min(*begin)).max(*end),
|
||||
Self::Reciprocal { factor, x_offset, y_offset } => factor
|
||||
.checked_rounding_div(FixedI64::from(x) + *x_offset, Low)
|
||||
.map(|yp| (yp + *y_offset).into_clamped_perthing())
|
||||
.unwrap_or_else(Perbill::one),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the `y` value for the given `x` value.
|
||||
///
|
||||
/// This is a partial implementation designed only for use in const functions.
|
||||
const fn const_threshold(&self, x: Perbill) -> Perbill {
|
||||
match self {
|
||||
Self::Reciprocal { factor, x_offset, y_offset } => {
|
||||
match factor.checked_rounding_div(FixedI64::from_perbill(x).add(*x_offset), Low) {
|
||||
Some(yp) => (yp.add(*y_offset)).into_perbill(),
|
||||
None => Perbill::one(),
|
||||
}
|
||||
},
|
||||
_ => panic!("const_threshold cannot be used on this curve"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the smallest `x` value such that `passing` returns `true` when passed along with
|
||||
/// the given `y` value.
|
||||
///
|
||||
/// If `passing` never returns `true` for any value of `x` when paired with `y`, then
|
||||
/// `Perbill::one` may be returned.
|
||||
///
|
||||
/// ```nocompile
|
||||
/// let c = Curve::LinearDecreasing { begin: Perbill::one(), delta: Perbill::one() };
|
||||
/// // ^^^ Can be any curve.
|
||||
@@ -307,12 +439,27 @@ impl Curve {
|
||||
/// ```
|
||||
pub fn delay(&self, y: Perbill) -> Perbill {
|
||||
match self {
|
||||
Self::LinearDecreasing { begin, delta } =>
|
||||
if delta.is_zero() {
|
||||
*delta
|
||||
Self::LinearDecreasing { length, floor, ceil } =>
|
||||
if y < *floor {
|
||||
Perbill::one()
|
||||
} else if y > *ceil {
|
||||
Perbill::zero()
|
||||
} else {
|
||||
(*begin - y.min(*begin)).min(*delta) / *delta
|
||||
(*ceil - y).saturating_div(*ceil - *floor, Up).saturating_mul(*length)
|
||||
},
|
||||
Self::SteppedDecreasing { begin, end, step, period } =>
|
||||
if y < *end {
|
||||
Perbill::one()
|
||||
} else {
|
||||
period.int_mul((*begin - y.min(*begin) + step.less_epsilon()).int_div(*step))
|
||||
},
|
||||
Self::Reciprocal { factor, x_offset, y_offset } => {
|
||||
let y = FixedI64::from(y);
|
||||
let maybe_term = factor.checked_rounding_div(y - *y_offset, High);
|
||||
maybe_term
|
||||
.and_then(|term| (term - *x_offset).try_into_perthing().ok())
|
||||
.unwrap_or_else(Perbill::one)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,14 +473,176 @@ impl Curve {
|
||||
impl Debug for Curve {
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
|
||||
match self {
|
||||
Self::LinearDecreasing { begin, delta } => {
|
||||
Self::LinearDecreasing { length, floor, ceil } => {
|
||||
write!(
|
||||
f,
|
||||
"Linear[(0%, {}%) -> (100%, {}%)]",
|
||||
*begin * 100u32,
|
||||
(*begin - *delta) * 100u32,
|
||||
"Linear[(0%, {:?}) -> ({:?}, {:?}) -> (100%, {:?})]",
|
||||
ceil, length, floor, floor,
|
||||
)
|
||||
},
|
||||
Self::SteppedDecreasing { begin, end, step, period } => {
|
||||
write!(
|
||||
f,
|
||||
"Stepped[(0%, {:?}) -> (100%, {:?}) by ({:?}, {:?})]",
|
||||
begin, end, period, step,
|
||||
)
|
||||
},
|
||||
Self::Reciprocal { factor, x_offset, y_offset } => {
|
||||
write!(
|
||||
f,
|
||||
"Reciprocal[factor of {:?}, x_offset of {:?}, y_offset of {:?}]",
|
||||
factor, x_offset, y_offset,
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_support::traits::ConstU32;
|
||||
use sp_runtime::PerThing;
|
||||
|
||||
const fn percent(x: u128) -> FixedI64 {
|
||||
FixedI64::from_rational(x, 100)
|
||||
}
|
||||
|
||||
const TIP_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100));
|
||||
const TIP_SUP: Curve = Curve::make_reciprocal(1, 28, percent(4), percent(0), percent(50));
|
||||
const ROOT_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100));
|
||||
const ROOT_SUP: Curve = Curve::make_linear(28, 28, percent(0), percent(50));
|
||||
const WHITE_APP: Curve =
|
||||
Curve::make_reciprocal(16, 28 * 24, percent(96), percent(50), percent(100));
|
||||
const WHITE_SUP: Curve = Curve::make_reciprocal(1, 28, percent(20), percent(10), percent(50));
|
||||
const SMALL_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100));
|
||||
const SMALL_SUP: Curve = Curve::make_reciprocal(8, 28, percent(1), percent(0), percent(50));
|
||||
const MID_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100));
|
||||
const MID_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50));
|
||||
const BIG_APP: Curve = Curve::make_linear(23, 28, percent(50), percent(100));
|
||||
const BIG_SUP: Curve = Curve::make_reciprocal(16, 28, percent(1), percent(0), percent(50));
|
||||
const HUGE_APP: Curve = Curve::make_linear(28, 28, percent(50), percent(100));
|
||||
const HUGE_SUP: Curve = Curve::make_reciprocal(20, 28, percent(1), percent(0), percent(50));
|
||||
const PARAM_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100));
|
||||
const PARAM_SUP: Curve = Curve::make_reciprocal(7, 28, percent(10), percent(0), percent(50));
|
||||
const ADMIN_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100));
|
||||
const ADMIN_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50));
|
||||
|
||||
// TODO: ceil for linear.
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn check_curves() {
|
||||
TIP_APP.info(28u32, "Tip Approval");
|
||||
TIP_SUP.info(28u32, "Tip Support");
|
||||
ROOT_APP.info(28u32, "Root Approval");
|
||||
ROOT_SUP.info(28u32, "Root Support");
|
||||
WHITE_APP.info(28u32, "Whitelist Approval");
|
||||
WHITE_SUP.info(28u32, "Whitelist Support");
|
||||
SMALL_APP.info(28u32, "Small Spend Approval");
|
||||
SMALL_SUP.info(28u32, "Small Spend Support");
|
||||
MID_APP.info(28u32, "Mid Spend Approval");
|
||||
MID_SUP.info(28u32, "Mid Spend Support");
|
||||
BIG_APP.info(28u32, "Big Spend Approval");
|
||||
BIG_SUP.info(28u32, "Big Spend Support");
|
||||
HUGE_APP.info(28u32, "Huge Spend Approval");
|
||||
HUGE_SUP.info(28u32, "Huge Spend Support");
|
||||
PARAM_APP.info(28u32, "Mid-tier Parameter Change Approval");
|
||||
PARAM_SUP.info(28u32, "Mid-tier Parameter Change Support");
|
||||
ADMIN_APP.info(28u32, "Admin (e.g. Cancel Slash) Approval");
|
||||
ADMIN_SUP.info(28u32, "Admin (e.g. Cancel Slash) Support");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_sorted_works() {
|
||||
let mut b: BoundedVec<u32, ConstU32<6>> = vec![20, 30, 40].try_into().unwrap();
|
||||
assert!(b.insert_sorted_by_key(10, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(60, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(50, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(!b.insert_sorted_by_key(9, |&x| x));
|
||||
assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(11, |&x| x));
|
||||
assert_eq!(&b[..], &[11, 20, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(21, |&x| x));
|
||||
assert_eq!(&b[..], &[20, 21, 30, 40, 50, 60][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(61, |&x| x));
|
||||
assert_eq!(&b[..], &[21, 30, 40, 50, 60, 61][..]);
|
||||
|
||||
assert!(b.insert_sorted_by_key(51, |&x| x));
|
||||
assert_eq!(&b[..], &[30, 40, 50, 51, 60, 61][..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn translated_reciprocal_works() {
|
||||
let c: Curve = Curve::Reciprocal {
|
||||
factor: FixedI64::from_float(0.03125),
|
||||
x_offset: FixedI64::from_float(0.0363306838226),
|
||||
y_offset: FixedI64::from_float(0.139845532427),
|
||||
};
|
||||
c.info(28u32, "Test");
|
||||
|
||||
for i in 0..9_696_969u32 {
|
||||
let query = Perbill::from_rational(i, 9_696_969);
|
||||
// Determine the nearest point in time when the query will be above threshold.
|
||||
let delay_needed = c.delay(query);
|
||||
// Ensure that it actually does pass at that time, or that it will never pass.
|
||||
assert!(delay_needed.is_one() || c.passing(delay_needed, query));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stepped_decreasing_works() {
|
||||
fn pc(x: u32) -> Perbill {
|
||||
Perbill::from_percent(x)
|
||||
}
|
||||
|
||||
let c =
|
||||
Curve::SteppedDecreasing { begin: pc(80), end: pc(30), step: pc(10), period: pc(15) };
|
||||
|
||||
for i in 0..9_696_969u32 {
|
||||
let query = Perbill::from_rational(i, 9_696_969);
|
||||
// Determine the nearest point in time when the query will be above threshold.
|
||||
let delay_needed = c.delay(query);
|
||||
// Ensure that it actually does pass at that time, or that it will never pass.
|
||||
assert!(delay_needed.is_one() || c.passing(delay_needed, query));
|
||||
}
|
||||
|
||||
assert_eq!(c.threshold(pc(0)), pc(80));
|
||||
assert_eq!(c.threshold(pc(15).less_epsilon()), pc(80));
|
||||
assert_eq!(c.threshold(pc(15)), pc(70));
|
||||
assert_eq!(c.threshold(pc(30).less_epsilon()), pc(70));
|
||||
assert_eq!(c.threshold(pc(30)), pc(60));
|
||||
assert_eq!(c.threshold(pc(45).less_epsilon()), pc(60));
|
||||
assert_eq!(c.threshold(pc(45)), pc(50));
|
||||
assert_eq!(c.threshold(pc(60).less_epsilon()), pc(50));
|
||||
assert_eq!(c.threshold(pc(60)), pc(40));
|
||||
assert_eq!(c.threshold(pc(75).less_epsilon()), pc(40));
|
||||
assert_eq!(c.threshold(pc(75)), pc(30));
|
||||
assert_eq!(c.threshold(pc(100)), pc(30));
|
||||
|
||||
assert_eq!(c.delay(pc(100)), pc(0));
|
||||
assert_eq!(c.delay(pc(80)), pc(0));
|
||||
assert_eq!(c.delay(pc(80).less_epsilon()), pc(15));
|
||||
assert_eq!(c.delay(pc(70)), pc(15));
|
||||
assert_eq!(c.delay(pc(70).less_epsilon()), pc(30));
|
||||
assert_eq!(c.delay(pc(60)), pc(30));
|
||||
assert_eq!(c.delay(pc(60).less_epsilon()), pc(45));
|
||||
assert_eq!(c.delay(pc(50)), pc(45));
|
||||
assert_eq!(c.delay(pc(50).less_epsilon()), pc(60));
|
||||
assert_eq!(c.delay(pc(40)), pc(60));
|
||||
assert_eq!(c.delay(pc(40).less_epsilon()), pc(75));
|
||||
assert_eq!(c.delay(pc(30)), pc(75));
|
||||
assert_eq!(c.delay(pc(30).less_epsilon()), pc(100));
|
||||
assert_eq!(c.delay(pc(0)), pc(100));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate as scheduler;
|
||||
use frame_support::{
|
||||
ord_parameter_types, parameter_types,
|
||||
traits::{
|
||||
ConstU32, ConstU64, Contains, EnsureOneOf, EqualPrivilegeOnly, OnFinalize, OnInitialize,
|
||||
ConstU32, ConstU64, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize,
|
||||
},
|
||||
weights::constants::RocksDbWeight,
|
||||
};
|
||||
@@ -174,7 +174,7 @@ impl Config for Test {
|
||||
type PalletsOrigin = OriginCaller;
|
||||
type Call = Call;
|
||||
type MaximumWeight = MaximumSchedulerWeight;
|
||||
type ScheduleOrigin = EnsureOneOf<EnsureRoot<u64>, EnsureSignedBy<One, u64>>;
|
||||
type ScheduleOrigin = EitherOfDiverse<EnsureRoot<u64>, EnsureSignedBy<One, u64>>;
|
||||
type MaxScheduledPerBlock = ConstU32<10>;
|
||||
type WeightInfo = ();
|
||||
type OriginPrivilegeCmp = EqualPrivilegeOnly;
|
||||
|
||||
@@ -990,7 +990,7 @@ mod tests {
|
||||
|
||||
let (validator_stash, nominators) = create_validator_with_nominators::<Test>(
|
||||
n,
|
||||
<Test as Config>::MaxNominatorRewardedPerValidator::get(),
|
||||
<<Test as Config>::MaxNominatorRewardedPerValidator as Get<_>>::get(),
|
||||
false,
|
||||
RewardDestination::Staked,
|
||||
)
|
||||
@@ -1015,7 +1015,7 @@ mod tests {
|
||||
|
||||
let (validator_stash, _nominators) = create_validator_with_nominators::<Test>(
|
||||
n,
|
||||
<Test as Config>::MaxNominatorRewardedPerValidator::get(),
|
||||
<<Test as Config>::MaxNominatorRewardedPerValidator as Get<_>>::get(),
|
||||
false,
|
||||
RewardDestination::Staked,
|
||||
)
|
||||
|
||||
@@ -3388,7 +3388,7 @@ fn six_session_delay() {
|
||||
#[test]
|
||||
fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
for i in 0..=<Test as Config>::MaxNominatorRewardedPerValidator::get() {
|
||||
for i in 0..=<<Test as Config>::MaxNominatorRewardedPerValidator as Get<_>>::get() {
|
||||
let stash = 10_000 + i as AccountId;
|
||||
let controller = 20_000 + i as AccountId;
|
||||
let balance = 10_000 + i as Balance;
|
||||
@@ -3411,7 +3411,7 @@ fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward(
|
||||
mock::make_all_reward_payment(1);
|
||||
|
||||
// Assert only nominators from 1 to Max are rewarded
|
||||
for i in 0..=<Test as Config>::MaxNominatorRewardedPerValidator::get() {
|
||||
for i in 0..=<<Test as Config>::MaxNominatorRewardedPerValidator as Get<_>>::get() {
|
||||
let stash = 10_000 + i as AccountId;
|
||||
let balance = 10_000 + i as Balance;
|
||||
if stash == 10_000 {
|
||||
@@ -3649,7 +3649,8 @@ fn payout_stakers_handles_weight_refund() {
|
||||
// Note: this test relies on the assumption that `payout_stakers_alive_staked` is solely used by
|
||||
// `payout_stakers` to calculate the weight of each payout op.
|
||||
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
|
||||
let max_nom_rewarded = <Test as Config>::MaxNominatorRewardedPerValidator::get();
|
||||
let max_nom_rewarded =
|
||||
<<Test as Config>::MaxNominatorRewardedPerValidator as Get<_>>::get();
|
||||
// Make sure the configured value is meaningful for our use.
|
||||
assert!(max_nom_rewarded >= 4);
|
||||
let half_max_nom_rewarded = max_nom_rewarded / 2;
|
||||
|
||||
@@ -350,6 +350,13 @@ macro_rules! parameter_types {
|
||||
I::from(Self::get())
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::traits::TypedGet for $name {
|
||||
type Type = $type;
|
||||
fn get() -> $type {
|
||||
Self::get()
|
||||
}
|
||||
}
|
||||
};
|
||||
(IMPL $name:ident, $type:ty, $value:expr) => {
|
||||
impl $name {
|
||||
@@ -364,6 +371,13 @@ macro_rules! parameter_types {
|
||||
I::from(Self::get())
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::traits::TypedGet for $name {
|
||||
type Type = $type;
|
||||
fn get() -> $type {
|
||||
Self::get()
|
||||
}
|
||||
}
|
||||
};
|
||||
(IMPL_STORAGE $name:ident, $type:ty, $value:expr) => {
|
||||
impl $name {
|
||||
@@ -397,6 +411,13 @@ macro_rules! parameter_types {
|
||||
I::from(Self::get())
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::traits::TypedGet for $name {
|
||||
type Type = $type;
|
||||
fn get() -> $type {
|
||||
Self::get()
|
||||
}
|
||||
}
|
||||
};
|
||||
(
|
||||
$( #[ $attr:meta ] )*
|
||||
@@ -805,7 +826,7 @@ pub mod tests {
|
||||
};
|
||||
use codec::{Codec, EncodeLike};
|
||||
use frame_support::traits::CrateVersion;
|
||||
use sp_io::TestExternalities;
|
||||
use sp_io::{MultiRemovalResults, TestExternalities};
|
||||
use sp_std::result;
|
||||
|
||||
/// A PalletInfo implementation which just panics.
|
||||
@@ -1066,15 +1087,16 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_map_basic_insert_remove_remove_prefix_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
type DoubleMap = DataDM;
|
||||
fn double_map_basic_insert_remove_remove_prefix_with_commit_should_work() {
|
||||
let key1 = 17u32;
|
||||
let key2 = 18u32;
|
||||
type DoubleMap = DataDM;
|
||||
let mut e = new_test_ext();
|
||||
e.execute_with(|| {
|
||||
// initialized during genesis
|
||||
assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64);
|
||||
|
||||
// get / insert / take
|
||||
let key1 = 17u32;
|
||||
let key2 = 18u32;
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 0u64);
|
||||
DoubleMap::insert(&key1, &key2, &4u64);
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 4u64);
|
||||
@@ -1082,9 +1104,7 @@ pub mod tests {
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 0u64);
|
||||
|
||||
// mutate
|
||||
DoubleMap::mutate(&key1, &key2, |val| {
|
||||
*val = 15;
|
||||
});
|
||||
DoubleMap::mutate(&key1, &key2, |val| *val = 15);
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 15u64);
|
||||
|
||||
// remove
|
||||
@@ -1096,13 +1116,62 @@ pub mod tests {
|
||||
DoubleMap::insert(&key1, &(key2 + 1), &4u64);
|
||||
DoubleMap::insert(&(key1 + 1), &key2, &4u64);
|
||||
DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64);
|
||||
});
|
||||
e.commit_all().unwrap();
|
||||
e.execute_with(|| {
|
||||
assert!(matches!(
|
||||
DoubleMap::clear_prefix(&key1, u32::max_value(), None),
|
||||
// Note this is the incorrect answer (for now), since we are using v2 of
|
||||
// `clear_prefix`.
|
||||
// When we switch to v3, then this will become:
|
||||
// sp_io::MultiRemovalResults::NoneLeft { db: 0, total: 2 },
|
||||
sp_io::MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 },
|
||||
MultiRemovalResults { maybe_cursor: None, backend: 2, unique: 2, loops: 2 }
|
||||
));
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 0u64);
|
||||
assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64);
|
||||
assert_eq!(DoubleMap::get(&(key1 + 1), &key2), 4u64);
|
||||
assert_eq!(DoubleMap::get(&(key1 + 1), &(key2 + 1)), 4u64);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_map_basic_insert_remove_remove_prefix_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let key1 = 17u32;
|
||||
let key2 = 18u32;
|
||||
type DoubleMap = DataDM;
|
||||
|
||||
// initialized during genesis
|
||||
assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64);
|
||||
|
||||
// get / insert / take
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 0u64);
|
||||
DoubleMap::insert(&key1, &key2, &4u64);
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 4u64);
|
||||
assert_eq!(DoubleMap::take(&key1, &key2), 4u64);
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 0u64);
|
||||
|
||||
// mutate
|
||||
DoubleMap::mutate(&key1, &key2, |val| *val = 15);
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 15u64);
|
||||
|
||||
// remove
|
||||
DoubleMap::remove(&key1, &key2);
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 0u64);
|
||||
|
||||
// remove prefix
|
||||
DoubleMap::insert(&key1, &key2, &4u64);
|
||||
DoubleMap::insert(&key1, &(key2 + 1), &4u64);
|
||||
DoubleMap::insert(&(key1 + 1), &key2, &4u64);
|
||||
DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64);
|
||||
// all in overlay
|
||||
assert!(matches!(
|
||||
DoubleMap::clear_prefix(&key1, u32::max_value(), None),
|
||||
MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 }
|
||||
));
|
||||
// Note this is the incorrect answer (for now), since we are using v2 of
|
||||
// `clear_prefix`.
|
||||
// When we switch to v3, then this will become:
|
||||
// MultiRemovalResults:: { maybe_cursor: None, backend: 0, unique: 2, loops: 2 },
|
||||
assert!(matches!(
|
||||
DoubleMap::clear_prefix(&key1, u32::max_value(), None),
|
||||
MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 }
|
||||
));
|
||||
assert_eq!(DoubleMap::get(&key1, &key2), 0u64);
|
||||
assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64);
|
||||
@@ -1321,7 +1390,7 @@ pub mod pallet_prelude {
|
||||
},
|
||||
traits::{
|
||||
ConstU32, EnsureOrigin, Get, GetDefault, GetStorageVersion, Hooks, IsType,
|
||||
PalletInfoAccess, StorageInfoTrait, StorageVersion,
|
||||
PalletInfoAccess, StorageInfoTrait, StorageVersion, TypedGet,
|
||||
},
|
||||
weights::{DispatchClass, Pays, Weight},
|
||||
Blake2_128, Blake2_128Concat, Blake2_256, CloneNoBound, DebugNoBound, EqNoBound, Identity,
|
||||
|
||||
@@ -59,7 +59,7 @@ pub use misc::{
|
||||
ConstU32, ConstU64, ConstU8, DefensiveSaturating, EnsureInherentsAreFirst, EqualPrivilegeOnly,
|
||||
EstimateCallFee, ExecuteBlock, ExtrinsicCall, Get, GetBacking, GetDefault, HandleLifetime,
|
||||
IsSubType, IsType, Len, OffchainWorker, OnKilledAccount, OnNewAccount, PreimageProvider,
|
||||
PreimageRecipient, PrivilegeCmp, SameOrOther, Time, TryCollect, TryDrop, UnixTime,
|
||||
PreimageRecipient, PrivilegeCmp, SameOrOther, Time, TryCollect, TryDrop, TypedGet, UnixTime,
|
||||
WrapperKeepOpaque, WrapperOpaque,
|
||||
};
|
||||
#[doc(hidden)]
|
||||
@@ -93,9 +93,11 @@ pub use storage::{
|
||||
};
|
||||
|
||||
mod dispatch;
|
||||
#[allow(deprecated)]
|
||||
pub use dispatch::EnsureOneOf;
|
||||
pub use dispatch::{
|
||||
AsEnsureOriginWithArg, DispatchableWithStorageLayer, EnsureOneOf, EnsureOrigin,
|
||||
EnsureOriginWithArg, OriginTrait, UnfilteredDispatchable,
|
||||
AsEnsureOriginWithArg, DispatchableWithStorageLayer, EitherOf, EitherOfDiverse, EnsureOrigin,
|
||||
EnsureOriginWithArg, NeverEnsureOrigin, OriginTrait, UnfilteredDispatchable,
|
||||
};
|
||||
|
||||
mod voting;
|
||||
|
||||
@@ -43,6 +43,19 @@ pub trait EnsureOrigin<OuterOrigin> {
|
||||
fn successful_origin() -> OuterOrigin;
|
||||
}
|
||||
|
||||
/// `EnsureOrigin` implementation that always fails.
|
||||
pub struct NeverEnsureOrigin<Success>(sp_std::marker::PhantomData<Success>);
|
||||
impl<OO, Success> EnsureOrigin<OO> for NeverEnsureOrigin<Success> {
|
||||
type Success = Success;
|
||||
fn try_origin(o: OO) -> Result<Success, OO> {
|
||||
Err(o)
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn successful_origin() -> OO {
|
||||
panic!("No `successful_origin` possible for `NeverEnsureOrigin`")
|
||||
}
|
||||
}
|
||||
|
||||
/// Some sort of check on the origin is performed by this object.
|
||||
pub trait EnsureOriginWithArg<OuterOrigin, Argument> {
|
||||
/// A return type.
|
||||
@@ -163,13 +176,16 @@ pub trait OriginTrait: Sized {
|
||||
fn signed(by: Self::AccountId) -> Self;
|
||||
}
|
||||
|
||||
/// The "OR gate" implementation of `EnsureOrigin`.
|
||||
/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L`
|
||||
/// and `R`, with them combined using an `Either` type.
|
||||
///
|
||||
/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first.
|
||||
pub struct EnsureOneOf<L, R>(sp_std::marker::PhantomData<(L, R)>);
|
||||
///
|
||||
/// Successful origin is derived from the left side.
|
||||
pub struct EitherOfDiverse<L, R>(sp_std::marker::PhantomData<(L, R)>);
|
||||
|
||||
impl<OuterOrigin, L: EnsureOrigin<OuterOrigin>, R: EnsureOrigin<OuterOrigin>>
|
||||
EnsureOrigin<OuterOrigin> for EnsureOneOf<L, R>
|
||||
EnsureOrigin<OuterOrigin> for EitherOfDiverse<L, R>
|
||||
{
|
||||
type Success = Either<L::Success, R::Success>;
|
||||
fn try_origin(o: OuterOrigin) -> Result<Self::Success, OuterOrigin> {
|
||||
@@ -183,17 +199,53 @@ impl<OuterOrigin, L: EnsureOrigin<OuterOrigin>, R: EnsureOrigin<OuterOrigin>>
|
||||
}
|
||||
}
|
||||
|
||||
/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L`
|
||||
/// and `R`, with them combined using an `Either` type.
|
||||
///
|
||||
/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first.
|
||||
///
|
||||
/// Successful origin is derived from the left side.
|
||||
#[deprecated = "Use `EitherOfDiverse` instead"]
|
||||
pub type EnsureOneOf<L, R> = EitherOfDiverse<L, R>;
|
||||
|
||||
/// "OR gate" implementation of `EnsureOrigin`, `Success` type for both `L` and `R` must
|
||||
/// be equal.
|
||||
///
|
||||
/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first.
|
||||
///
|
||||
/// Successful origin is derived from the left side.
|
||||
pub struct EitherOf<L, R>(sp_std::marker::PhantomData<(L, R)>);
|
||||
|
||||
impl<
|
||||
OuterOrigin,
|
||||
L: EnsureOrigin<OuterOrigin>,
|
||||
R: EnsureOrigin<OuterOrigin, Success = L::Success>,
|
||||
> EnsureOrigin<OuterOrigin> for EitherOf<L, R>
|
||||
{
|
||||
type Success = L::Success;
|
||||
fn try_origin(o: OuterOrigin) -> Result<Self::Success, OuterOrigin> {
|
||||
L::try_origin(o).or_else(|o| R::try_origin(o))
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn successful_origin() -> OuterOrigin {
|
||||
L::successful_origin()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::traits::{ConstBool, ConstU8, TypedGet};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
struct EnsureSuccess;
|
||||
struct EnsureFail;
|
||||
struct EnsureSuccess<V>(PhantomData<V>);
|
||||
struct EnsureFail<T>(PhantomData<T>);
|
||||
|
||||
impl EnsureOrigin<()> for EnsureSuccess {
|
||||
type Success = ();
|
||||
impl<V: TypedGet> EnsureOrigin<()> for EnsureSuccess<V> {
|
||||
type Success = V::Type;
|
||||
fn try_origin(_: ()) -> Result<Self::Success, ()> {
|
||||
Ok(())
|
||||
Ok(V::get())
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn successful_origin() -> () {
|
||||
@@ -201,8 +253,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl EnsureOrigin<()> for EnsureFail {
|
||||
type Success = ();
|
||||
impl<T> EnsureOrigin<()> for EnsureFail<T> {
|
||||
type Success = T;
|
||||
fn try_origin(_: ()) -> Result<Self::Success, ()> {
|
||||
Err(())
|
||||
}
|
||||
@@ -213,10 +265,46 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_one_of_test() {
|
||||
assert!(<EnsureOneOf<EnsureSuccess, EnsureSuccess>>::try_origin(()).is_ok());
|
||||
assert!(<EnsureOneOf<EnsureSuccess, EnsureFail>>::try_origin(()).is_ok());
|
||||
assert!(<EnsureOneOf<EnsureFail, EnsureSuccess>>::try_origin(()).is_ok());
|
||||
assert!(<EnsureOneOf<EnsureFail, EnsureFail>>::try_origin(()).is_err());
|
||||
fn either_of_diverse_works() {
|
||||
assert_eq!(
|
||||
EitherOfDiverse::<
|
||||
EnsureSuccess<ConstBool<true>>,
|
||||
EnsureSuccess<ConstU8<0>>,
|
||||
>::try_origin(()).unwrap().left(),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
EitherOfDiverse::<EnsureSuccess<ConstBool<true>>, EnsureFail<u8>>::try_origin(())
|
||||
.unwrap()
|
||||
.left(),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
EitherOfDiverse::<EnsureFail<bool>, EnsureSuccess<ConstU8<0>>>::try_origin(())
|
||||
.unwrap()
|
||||
.right(),
|
||||
Some(0u8)
|
||||
);
|
||||
assert!(EitherOfDiverse::<EnsureFail<bool>, EnsureFail<u8>>::try_origin(()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn either_of_works() {
|
||||
assert_eq!(
|
||||
EitherOf::<
|
||||
EnsureSuccess<ConstBool<true>>,
|
||||
EnsureSuccess<ConstBool<false>>,
|
||||
>::try_origin(()).unwrap(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
EitherOf::<EnsureSuccess<ConstBool<true>>, EnsureFail<bool>>::try_origin(()).unwrap(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
EitherOf::<EnsureFail<bool>, EnsureSuccess<ConstBool<false>>>::try_origin(()).unwrap(),
|
||||
false
|
||||
);
|
||||
assert!(EitherOf::<EnsureFail<bool>, EnsureFail<bool>>::try_origin(()).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +387,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for querying a single value from a type defined in the trait.
|
||||
///
|
||||
/// It is not required that the value is constant.
|
||||
pub trait TypedGet {
|
||||
/// The type which is returned.
|
||||
type Type;
|
||||
/// Return the current value.
|
||||
fn get() -> Self::Type;
|
||||
}
|
||||
|
||||
/// A trait for querying a single value from a type.
|
||||
///
|
||||
/// It is not required that the value is constant.
|
||||
@@ -423,6 +433,12 @@ macro_rules! impl_const_get {
|
||||
Some(T)
|
||||
}
|
||||
}
|
||||
impl<const T: $t> TypedGet for $name<T> {
|
||||
type Type = $t;
|
||||
fn get() -> $t {
|
||||
T
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -95,16 +95,18 @@ impl<B: UniqueSaturatedInto<u64> + UniqueSaturatedFrom<u128>> CurrencyToVote<B>
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VoteTally<Votes> {
|
||||
fn ayes(&self) -> Votes;
|
||||
fn turnout(&self) -> Perbill;
|
||||
fn approval(&self) -> Perbill;
|
||||
pub trait VoteTally<Votes, Class> {
|
||||
fn new(_: Class) -> Self;
|
||||
fn ayes(&self, class: Class) -> Votes;
|
||||
fn support(&self, class: Class) -> Perbill;
|
||||
fn approval(&self, class: Class) -> Perbill;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn unanimity() -> Self;
|
||||
fn unanimity(class: Class) -> Self;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn from_requirements(turnout: Perbill, approval: Perbill) -> Self;
|
||||
fn rejection(class: Class) -> Self;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn from_requirements(support: Perbill, approval: Perbill, class: Class) -> Self;
|
||||
}
|
||||
|
||||
pub enum PollStatus<Tally, Moment, Class> {
|
||||
None,
|
||||
Ongoing(Tally, Class),
|
||||
|
||||
@@ -86,7 +86,7 @@ use frame_support::{
|
||||
storage,
|
||||
traits::{
|
||||
ConstU32, Contains, EnsureOrigin, Get, HandleLifetime, OnKilledAccount, OnNewAccount,
|
||||
OriginTrait, PalletInfo, SortedMembers, StoredMap,
|
||||
OriginTrait, PalletInfo, SortedMembers, StoredMap, TypedGet,
|
||||
},
|
||||
weights::{
|
||||
extract_actual_weight, DispatchClass, DispatchInfo, PerDispatchClass, RuntimeDbWeight,
|
||||
@@ -787,6 +787,29 @@ impl<O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, Acco
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnsureRootWithSuccess<AccountId, Success>(
|
||||
sp_std::marker::PhantomData<(AccountId, Success)>,
|
||||
);
|
||||
impl<
|
||||
O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>,
|
||||
AccountId,
|
||||
Success: TypedGet,
|
||||
> EnsureOrigin<O> for EnsureRootWithSuccess<AccountId, Success>
|
||||
{
|
||||
type Success = Success::Type;
|
||||
fn try_origin(o: O) -> Result<Self::Success, O> {
|
||||
o.into().and_then(|o| match o {
|
||||
RawOrigin::Root => Ok(Success::get()),
|
||||
r => Err(O::from(r)),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn successful_origin() -> O {
|
||||
O::from(RawOrigin::Root)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnsureSigned<AccountId>(sp_std::marker::PhantomData<AccountId>);
|
||||
impl<O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, AccountId: Decode>
|
||||
EnsureOrigin<O> for EnsureSigned<AccountId>
|
||||
|
||||
@@ -144,6 +144,7 @@ impl pallet_treasury::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type SpendFunds = ();
|
||||
type MaxApprovals = ConstU32<100>;
|
||||
type SpendOrigin = frame_support::traits::NeverEnsureOrigin<u64>;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const TipFindersFee: Percent = Percent::from_percent(20);
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
use super::{Pallet as Treasury, *};
|
||||
|
||||
use frame_benchmarking::{account, benchmarks_instance_pallet};
|
||||
use frame_support::{ensure, traits::OnInitialize};
|
||||
use frame_support::{
|
||||
dispatch::UnfilteredDispatchable,
|
||||
ensure,
|
||||
traits::{EnsureOrigin, OnInitialize},
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
@@ -57,7 +61,21 @@ fn setup_pot_account<T: Config<I>, I: 'static>() {
|
||||
let _ = T::Currency::make_free_balance_be(&pot_account, value);
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::Event) {
|
||||
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
spend {
|
||||
let (_, value, beneficiary_lookup) = setup_proposal::<T, _>(SEED);
|
||||
let origin = T::SpendOrigin::successful_origin();
|
||||
let beneficiary = T::Lookup::lookup(beneficiary_lookup.clone()).unwrap();
|
||||
let call = Call::<T, I>::spend { amount: value, beneficiary: beneficiary_lookup };
|
||||
}: { call.dispatch_bypass_filter(origin)? }
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::SpendApproved { proposal_index: 0, amount: value, beneficiary }.into())
|
||||
}
|
||||
|
||||
propose_spend {
|
||||
let (caller, value, beneficiary_lookup) = setup_proposal::<T, _>(SEED);
|
||||
// Whitelist caller account from further DB operations.
|
||||
|
||||
@@ -198,6 +198,11 @@ pub mod pallet {
|
||||
/// NOTE: This parameter is also used within the Bounties Pallet extension if enabled.
|
||||
#[pallet::constant]
|
||||
type MaxApprovals: Get<u32>;
|
||||
|
||||
/// The origin required for approving spends from the treasury outside of the proposal
|
||||
/// process. The `Success` value is the maximum amount that this origin is allowed to
|
||||
/// spend at a time.
|
||||
type SpendOrigin: EnsureOrigin<Self::Origin, Success = BalanceOf<Self, I>>;
|
||||
}
|
||||
|
||||
/// Number of proposals that have been made.
|
||||
@@ -275,6 +280,12 @@ pub mod pallet {
|
||||
Rollover { rollover_balance: BalanceOf<T, I> },
|
||||
/// Some funds have been deposited.
|
||||
Deposit { value: BalanceOf<T, I> },
|
||||
/// A new spend proposal has been approved.
|
||||
SpendApproved {
|
||||
proposal_index: ProposalIndex,
|
||||
amount: BalanceOf<T, I>,
|
||||
beneficiary: T::AccountId,
|
||||
},
|
||||
}
|
||||
|
||||
/// Error for the treasury pallet.
|
||||
@@ -286,6 +297,9 @@ pub mod pallet {
|
||||
InvalidIndex,
|
||||
/// Too many approvals in the queue.
|
||||
TooManyApprovals,
|
||||
/// The spend origin is valid but the amount it is allowed to spend is lower than the
|
||||
/// amount to be spent.
|
||||
InsufficientPermission,
|
||||
/// Proposal has not been approved.
|
||||
ProposalNotApproved,
|
||||
}
|
||||
@@ -393,6 +407,40 @@ pub mod pallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Propose and approve a spend of treasury funds.
|
||||
///
|
||||
/// - `origin`: Must be `SpendOrigin` with the `Success` value being at least `amount`.
|
||||
/// - `amount`: The amount to be transferred from the treasury to the `beneficiary`.
|
||||
/// - `beneficiary`: The destination account for the transfer.
|
||||
///
|
||||
/// NOTE: For record-keeping purposes, the proposer is deemed to be equivalent to the
|
||||
/// beneficiary.
|
||||
#[pallet::weight(T::WeightInfo::spend())]
|
||||
pub fn spend(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] amount: BalanceOf<T, I>,
|
||||
beneficiary: <T::Lookup as StaticLookup>::Source,
|
||||
) -> DispatchResult {
|
||||
let max_amount = T::SpendOrigin::ensure_origin(origin)?;
|
||||
let beneficiary = T::Lookup::lookup(beneficiary)?;
|
||||
|
||||
ensure!(amount <= max_amount, Error::<T, I>::InsufficientPermission);
|
||||
let proposal_index = Self::proposal_count();
|
||||
Approvals::<T, I>::try_append(proposal_index)
|
||||
.map_err(|_| Error::<T, I>::TooManyApprovals)?;
|
||||
let proposal = Proposal {
|
||||
proposer: beneficiary.clone(),
|
||||
value: amount,
|
||||
beneficiary: beneficiary.clone(),
|
||||
bond: Default::default(),
|
||||
};
|
||||
Proposals::<T, I>::insert(proposal_index, proposal);
|
||||
ProposalCount::<T, I>::put(proposal_index + 1);
|
||||
|
||||
Self::deposit_event(Event::SpendApproved { proposal_index, amount, beneficiary });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Force a previously approved proposal to be removed from the approval queue.
|
||||
/// The original deposit will no longer be returned.
|
||||
///
|
||||
|
||||
@@ -24,7 +24,7 @@ use std::cell::RefCell;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
traits::{BadOrigin, BlakeTwo256, IdentityLookup},
|
||||
};
|
||||
|
||||
use frame_support::{
|
||||
@@ -101,8 +101,26 @@ parameter_types! {
|
||||
pub const ProposalBond: Permill = Permill::from_percent(5);
|
||||
pub const Burn: Permill = Permill::from_percent(50);
|
||||
pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
|
||||
pub const MaxApprovals: u32 = 100;
|
||||
}
|
||||
pub struct TestSpendOrigin;
|
||||
impl frame_support::traits::EnsureOrigin<Origin> for TestSpendOrigin {
|
||||
type Success = u64;
|
||||
fn try_origin(o: Origin) -> Result<Self::Success, Origin> {
|
||||
Result::<frame_system::RawOrigin<_>, Origin>::from(o).and_then(|o| match o {
|
||||
frame_system::RawOrigin::Root => Ok(u64::max_value()),
|
||||
frame_system::RawOrigin::Signed(10) => Ok(5),
|
||||
frame_system::RawOrigin::Signed(11) => Ok(10),
|
||||
frame_system::RawOrigin::Signed(12) => Ok(20),
|
||||
frame_system::RawOrigin::Signed(13) => Ok(50),
|
||||
r => Err(Origin::from(r)),
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn successful_origin() -> Origin {
|
||||
Origin::root()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type PalletId = TreasuryPalletId;
|
||||
type Currency = pallet_balances::Pallet<Test>;
|
||||
@@ -119,6 +137,7 @@ impl Config for Test {
|
||||
type WeightInfo = ();
|
||||
type SpendFunds = ();
|
||||
type MaxApprovals = ConstU32<100>;
|
||||
type SpendOrigin = TestSpendOrigin;
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
@@ -141,6 +160,51 @@ fn genesis_config_works() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spend_origin_permissioning_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(Treasury::spend(Origin::signed(1), 1, 1), BadOrigin);
|
||||
assert_noop!(
|
||||
Treasury::spend(Origin::signed(10), 6, 1),
|
||||
Error::<Test>::InsufficientPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Treasury::spend(Origin::signed(11), 11, 1),
|
||||
Error::<Test>::InsufficientPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Treasury::spend(Origin::signed(12), 21, 1),
|
||||
Error::<Test>::InsufficientPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Treasury::spend(Origin::signed(13), 51, 1),
|
||||
Error::<Test>::InsufficientPermission
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spend_origin_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Check that accumulate works when we have Some value in Dummy already.
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Treasury::spend(Origin::signed(10), 5, 6));
|
||||
assert_ok!(Treasury::spend(Origin::signed(10), 5, 6));
|
||||
assert_ok!(Treasury::spend(Origin::signed(10), 5, 6));
|
||||
assert_ok!(Treasury::spend(Origin::signed(10), 5, 6));
|
||||
assert_ok!(Treasury::spend(Origin::signed(11), 10, 6));
|
||||
assert_ok!(Treasury::spend(Origin::signed(12), 20, 6));
|
||||
assert_ok!(Treasury::spend(Origin::signed(13), 50, 6));
|
||||
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(1);
|
||||
assert_eq!(Balances::free_balance(6), 0);
|
||||
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(2);
|
||||
assert_eq!(Balances::free_balance(6), 100);
|
||||
assert_eq!(Treasury::pot(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minting_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -372,7 +436,7 @@ fn max_approvals_limited() {
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), u64::MAX);
|
||||
Balances::make_free_balance_be(&0, u64::MAX);
|
||||
|
||||
for _ in 0..MaxApprovals::get() {
|
||||
for _ in 0..<Test as Config>::MaxApprovals::get() {
|
||||
assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3));
|
||||
assert_ok!(Treasury::approve_proposal(Origin::root(), 0));
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ use sp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pallet_treasury.
|
||||
pub trait WeightInfo {
|
||||
fn spend() -> Weight;
|
||||
fn propose_spend() -> Weight;
|
||||
fn reject_proposal() -> Weight;
|
||||
fn approve_proposal(p: u32, ) -> Weight;
|
||||
@@ -54,6 +55,13 @@ pub trait WeightInfo {
|
||||
/// Weights for pallet_treasury using the Substrate node and recommended hardware.
|
||||
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
// Storage: Treasury ProposalCount (r:1 w:1)
|
||||
// Storage: Treasury Proposals (r:0 w:1)
|
||||
fn spend() -> Weight {
|
||||
(22_063_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Treasury ProposalCount (r:1 w:1)
|
||||
// Storage: Treasury Proposals (r:0 w:1)
|
||||
fn propose_spend() -> Weight {
|
||||
@@ -100,6 +108,13 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
// Storage: Treasury ProposalCount (r:1 w:1)
|
||||
// Storage: Treasury Proposals (r:0 w:1)
|
||||
fn spend() -> Weight {
|
||||
(22_063_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
// Storage: Treasury ProposalCount (r:1 w:1)
|
||||
// Storage: Treasury Proposals (r:0 w:1)
|
||||
fn propose_spend() -> Weight {
|
||||
|
||||
Reference in New Issue
Block a user