......@@ -585,3 +585,26 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() {
);
});
}
#[test]
fn report_equivocation_has_valid_weight() {
// the weight depends on the size of the validator set,
// but there's a lower bound of 100 validators.
assert!(
(1..=100)
.map(weight_for::report_equivocation::<Test>)
.collect::<Vec<_>>()
.windows(2)
.all(|w| w[0] == w[1])
);
// after 100 validators the weight should keep increasing
// with every extra validator.
assert!(
(100..=1000)
.map(weight_for::report_equivocation::<Test>)
.collect::<Vec<_>>()
.windows(2)
.all(|w| w[0] < w[1])
);
}
......@@ -45,8 +45,8 @@ benchmarks_instance! {
_{ }
set_members {
let m in 1 .. MAX_MEMBERS;
let n in 1 .. MAX_MEMBERS;
let m in 1 .. T::MaxMembers::get();
let n in 1 .. T::MaxMembers::get();
let p in 1 .. T::MaxProposals::get();
// Set old members.
......@@ -63,7 +63,7 @@ benchmarks_instance! {
SystemOrigin::Root.into(),
old_members.clone(),
Some(last_old_member.clone()),
MAX_MEMBERS,
T::MaxMembers::get(),
)?;
// Set a high threshold for proposals passing so that they stay around.
......@@ -104,15 +104,15 @@ benchmarks_instance! {
new_members.push(last_member.clone());
}
}: _(SystemOrigin::Root, new_members.clone(), Some(last_member), MAX_MEMBERS)
}: _(SystemOrigin::Root, new_members.clone(), Some(last_member), T::MaxMembers::get())
verify {
new_members.sort();
assert_eq!(Collective::<T, _>::members(), new_members);
}
execute {
let m in 1 .. MAX_MEMBERS;
let b in 1 .. MAX_BYTES;
let m in 1 .. T::MaxMembers::get();
let bytes_in_storage = b + size_of::<u32>() as u32;
......@@ -126,7 +126,7 @@ benchmarks_instance! {
let caller: T::AccountId = whitelisted_caller();
members.push(caller.clone());
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members, None, MAX_MEMBERS)?;
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members, None, T::MaxMembers::get())?;
let proposal: T::Proposal = SystemCall::<T>::remark(vec![1; b as usize]).into();
......@@ -141,8 +141,8 @@ benchmarks_instance! {
// This tests when execution would happen immediately after proposal
propose_execute {
let m in 1 .. MAX_MEMBERS;
let b in 1 .. MAX_BYTES;
let m in 1 .. T::MaxMembers::get();
let bytes_in_storage = b + size_of::<u32>() as u32;
......@@ -156,7 +156,7 @@ benchmarks_instance! {
let caller: T::AccountId = whitelisted_caller();
members.push(caller.clone());
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members, None, MAX_MEMBERS)?;
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members, None, T::MaxMembers::get())?;
let proposal: T::Proposal = SystemCall::<T>::remark(vec![1; b as usize]).into();
let threshold = 1;
......@@ -172,9 +172,9 @@ benchmarks_instance! {
// This tests when proposal is created and queued as "proposed"
propose_proposed {
let m in 2 .. MAX_MEMBERS;
let p in 1 .. T::MaxProposals::get();
let b in 1 .. MAX_BYTES;
let m in 2 .. T::MaxMembers::get();
let p in 1 .. T::MaxProposals::get();
let bytes_in_storage = b + size_of::<u32>() as u32;
......@@ -186,7 +186,7 @@ benchmarks_instance! {
}
let caller: T::AccountId = whitelisted_caller();
members.push(caller.clone());
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members, None, MAX_MEMBERS)?;
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members, None, T::MaxMembers::get())?;
let threshold = m;
// Add previous proposals.
......@@ -215,7 +215,7 @@ benchmarks_instance! {
vote {
// We choose 5 as a minimum so we always trigger a vote in the voting loop (`for j in ...`)
let m in 5 .. MAX_MEMBERS;
let m in 5 .. T::MaxMembers::get();
let p = T::MaxProposals::get();
let b = MAX_BYTES;
......@@ -231,7 +231,7 @@ benchmarks_instance! {
}
let voter: T::AccountId = account("voter", 0, SEED);
members.push(voter.clone());
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members.clone(), None, MAX_MEMBERS)?;
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members.clone(), None, T::MaxMembers::get())?;
// Threshold is 1 less than the number of members so that one person can vote nay
let threshold = m - 1;
......@@ -277,6 +277,9 @@ benchmarks_instance! {
// Voter switches vote to nay, but does not kill the vote, just updates + inserts
let approve = false;
// Whitelist voter account from further DB operations.
let voter_key = frame_system::Account::<T>::hashed_key_for(&voter);
frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into());
}: _(SystemOrigin::Signed(voter), last_hash.clone(), index, approve)
verify {
// All proposals exist and the last proposal has just been updated.
......@@ -288,11 +291,11 @@ benchmarks_instance! {
close_early_disapproved {
// We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`)
let m in 4 .. MAX_MEMBERS;
let m in 4 .. T::MaxMembers::get();
let p in 1 .. T::MaxProposals::get();
let b in 1 .. MAX_BYTES;
let bytes_in_storage = b + size_of::<u32>() as u32;
let bytes = 100;
let bytes_in_storage = bytes + size_of::<u32>() as u32;
// Construct `members`.
let mut members = vec![];
......@@ -304,7 +307,7 @@ benchmarks_instance! {
}
let voter: T::AccountId = account("voter", 0, SEED);
members.push(voter.clone());
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members.clone(), None, MAX_MEMBERS)?;
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members.clone(), None, T::MaxMembers::get())?;
// Threshold is total members so that one nay will disapprove the vote
let threshold = m;
......@@ -313,7 +316,7 @@ benchmarks_instance! {
let mut last_hash = T::Hash::default();
for i in 0 .. p {
// Proposals should be different so that different proposal hashes are generated
let proposal: T::Proposal = SystemCall::<T>::remark(vec![i as u8; b as usize]).into();
let proposal: T::Proposal = SystemCall::<T>::remark(vec![i as u8; bytes as usize]).into();
Collective::<T, _>::propose(
SystemOrigin::Signed(proposer.clone()).into(),
threshold,
......@@ -356,6 +359,9 @@ benchmarks_instance! {
approve,
)?;
// Whitelist voter account from further DB operations.
let voter_key = frame_system::Account::<T>::hashed_key_for(&voter);
frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into());
}: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::max_value(), bytes_in_storage)
verify {
// The last proposal is removed.
......@@ -364,10 +370,10 @@ benchmarks_instance! {
}
close_early_approved {
let b in 1 .. MAX_BYTES;
// We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`)
let m in 4 .. MAX_MEMBERS;
let m in 4 .. T::MaxMembers::get();
let p in 1 .. T::MaxProposals::get();
let b in 1 .. MAX_BYTES;
let bytes_in_storage = b + size_of::<u32>() as u32;
......@@ -379,7 +385,7 @@ benchmarks_instance! {
}
let caller: T::AccountId = whitelisted_caller();
members.push(caller.clone());
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members.clone(), None, MAX_MEMBERS)?;
Collective::<T, _>::set_members(SystemOrigin::Root.into(), members.clone(), None, T::MaxMembers::get())?;
// Threshold is 2 so any two ayes will approve the vote
let threshold = 2;
......@@ -446,11 +452,11 @@ benchmarks_instance! {
close_disapproved {
// We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`)
let m in 4 .. MAX_MEMBERS;
let m in 4 .. T::MaxMembers::get();
let p in 1 .. T::MaxProposals::get();
let b in 1 .. MAX_BYTES;
let bytes_in_storage = b + size_of::<u32>() as u32;
let bytes = 100;
let bytes_in_storage = bytes + size_of::<u32>() as u32;
// Construct `members`.
let mut members = vec![];
......@@ -464,7 +470,7 @@ benchmarks_instance! {
SystemOrigin::Root.into(),
members.clone(),
Some(caller.clone()),
MAX_MEMBERS,
T::MaxMembers::get(),
)?;
// Threshold is one less than total members so that two nays will disapprove the vote
......@@ -474,7 +480,7 @@ benchmarks_instance! {
let mut last_hash = T::Hash::default();
for i in 0 .. p {
// Proposals should be different so that different proposal hashes are generated
let proposal: T::Proposal = SystemCall::<T>::remark(vec![i as u8; b as usize]).into();
let proposal: T::Proposal = SystemCall::<T>::remark(vec![i as u8; bytes as usize]).into();
Collective::<T, _>::propose(
SystemOrigin::Signed(caller.clone()).into(),
threshold,
......@@ -517,10 +523,10 @@ benchmarks_instance! {
}
close_approved {
let b in 1 .. MAX_BYTES;
// We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`)
let m in 4 .. MAX_MEMBERS;
let m in 4 .. T::MaxMembers::get();
let p in 1 .. T::MaxProposals::get();
let b in 1 .. MAX_BYTES;
let bytes_in_storage = b + size_of::<u32>() as u32;
......@@ -536,7 +542,7 @@ benchmarks_instance! {
SystemOrigin::Root.into(),
members.clone(),
Some(caller.clone()),
MAX_MEMBERS,
T::MaxMembers::get(),
)?;
// Threshold is two, so any two ayes will pass the vote
......@@ -579,6 +585,54 @@ benchmarks_instance! {
assert_eq!(Collective::<T, _>::proposals().len(), (p - 1) as usize);
assert_last_event::<T, I>(RawEvent::Executed(last_hash, Err(DispatchError::BadOrigin)).into());
}
disapprove_proposal {
let p in 1 .. T::MaxProposals::get();
let m = 3;
let b = MAX_BYTES;
let bytes_in_storage = b + size_of::<u32>() as u32;
// Construct `members`.
let mut members = vec![];
for i in 0 .. m - 1 {
let member = account("member", i, SEED);
members.push(member);
}
let caller: T::AccountId = account("caller", 0, SEED);
members.push(caller.clone());
Collective::<T, _>::set_members(
SystemOrigin::Root.into(),
members.clone(),
Some(caller.clone()),
T::MaxMembers::get(),
)?;
// Threshold is one less than total members so that two nays will disapprove the vote
let threshold = m - 1;
// Add proposals
let mut last_hash = T::Hash::default();
for i in 0 .. p {
// Proposals should be different so that different proposal hashes are generated
let proposal: T::Proposal = SystemCall::<T>::remark(vec![i as u8; b as usize]).into();
Collective::<T, _>::propose(
SystemOrigin::Signed(caller.clone()).into(),
threshold,
Box::new(proposal.clone()),
bytes_in_storage,
)?;
last_hash = T::Hashing::hash_of(&proposal);
}
System::<T>::set_block_number(T::BlockNumber::max_value());
assert_eq!(Collective::<T, _>::proposals().len(), p as usize);
}: _(SystemOrigin::Root, last_hash)
verify {
assert_eq!(Collective::<T, _>::proposals().len(), (p - 1) as usize);
assert_last_event::<T, I>(RawEvent::Disapproved(last_hash).into());
}
}
#[cfg(test)]
......@@ -649,4 +703,11 @@ mod tests {
assert_ok!(test_benchmark_close_approved::<Test>());
});
}
#[test]
fn disapprove_proposal() {
new_test_ext().execute_with(|| {
assert_ok!(test_benchmark_disapprove_proposal::<Test>());
});
}
}
// Copyright (C) 2020 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.
//! Default weights for the Collective Pallet
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0-rc6
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight};
impl crate::WeightInfo for () {
fn set_members(m: u32, n: u32, p: u32, ) -> Weight {
(0 as Weight)
.saturating_add((21040000 as Weight).saturating_mul(m as Weight))
.saturating_add((173000 as Weight).saturating_mul(n as Weight))
.saturating_add((31595000 as Weight).saturating_mul(p as Weight))
.saturating_add(DbWeight::get().reads(2 as Weight))
.saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(p as Weight)))
.saturating_add(DbWeight::get().writes(2 as Weight))
.saturating_add(DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight)))
}
fn execute(b: u32, m: u32, ) -> Weight {
(43359000 as Weight)
.saturating_add((4000 as Weight).saturating_mul(b as Weight))
.saturating_add((123000 as Weight).saturating_mul(m as Weight))
.saturating_add(DbWeight::get().reads(1 as Weight))
}
fn propose_execute(b: u32, m: u32, ) -> Weight {
(54134000 as Weight)
.saturating_add((4000 as Weight).saturating_mul(b as Weight))
.saturating_add((239000 as Weight).saturating_mul(m as Weight))
.saturating_add(DbWeight::get().reads(2 as Weight))
}
fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight {
(90650000 as Weight)
.saturating_add((5000 as Weight).saturating_mul(b as Weight))
.saturating_add((152000 as Weight).saturating_mul(m as Weight))
.saturating_add((970000 as Weight).saturating_mul(p as Weight))
.saturating_add(DbWeight::get().reads(4 as Weight))
.saturating_add(DbWeight::get().writes(4 as Weight))
}
fn vote(m: u32, ) -> Weight {
(74460000 as Weight)
.saturating_add((290000 as Weight).saturating_mul(m as Weight))
.saturating_add(DbWeight::get().reads(2 as Weight))
.saturating_add(DbWeight::get().writes(1 as Weight))
}
fn close_early_disapproved(m: u32, p: u32, ) -> Weight {
(86360000 as Weight)
.saturating_add((232000 as Weight).saturating_mul(m as Weight))
.saturating_add((954000 as Weight).saturating_mul(p as Weight))
.saturating_add(DbWeight::get().reads(3 as Weight))
.saturating_add(DbWeight::get().writes(3 as Weight))
}
fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight {
(123653000 as Weight)
.saturating_add((1000 as Weight).saturating_mul(b as Weight))
.saturating_add((287000 as Weight).saturating_mul(m as Weight))
.saturating_add((920000 as Weight).saturating_mul(p as Weight))
.saturating_add(DbWeight::get().reads(4 as Weight))
.saturating_add(DbWeight::get().writes(3 as Weight))
}
fn close_disapproved(m: u32, p: u32, ) -> Weight {
(95395000 as Weight)
.saturating_add((236000 as Weight).saturating_mul(m as Weight))
.saturating_add((965000 as Weight).saturating_mul(p as Weight))
.saturating_add(DbWeight::get().reads(4 as Weight))
.saturating_add(DbWeight::get().writes(3 as Weight))
}
fn close_approved(b: u32, m: u32, p: u32, ) -> Weight {
(135284000 as Weight)
.saturating_add((4000 as Weight).saturating_mul(b as Weight))
.saturating_add((218000 as Weight).saturating_mul(m as Weight))
.saturating_add((951000 as Weight).saturating_mul(p as Weight))
.saturating_add(DbWeight::get().reads(5 as Weight))
.saturating_add(DbWeight::get().writes(3 as Weight))
}
fn disapprove_proposal(p: u32, ) -> Weight {
(50500000 as Weight)
.saturating_add((966000 as Weight).saturating_mul(p as Weight))
.saturating_add(DbWeight::get().reads(1 as Weight))
.saturating_add(DbWeight::get().writes(3 as Weight))
}
}
......@@ -20,7 +20,7 @@
//!
//! The membership can be provided in one of two ways: either directly, using the Root-dispatchable
//! function `set_members`, or indirectly, through implementing the `ChangeMembers`.
//! The pallet assumes that the amount of members stays at or below `MAX_MEMBERS` for its weight
//! The pallet assumes that the amount of members stays at or below `MaxMembers` for its weight
//! calculations, but enforces this neither in `set_members` nor in `change_members_sorted`.
//!
//! A "prime" member may be set allowing their vote to act as the default vote in case of any
......@@ -60,6 +60,8 @@ use frame_system::{self as system, ensure_signed, ensure_root};
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
mod default_weight;
/// Simple index type for proposal counting.
pub type ProposalIndex = u32;
......@@ -69,35 +71,17 @@ pub type ProposalIndex = u32;
/// vote exactly once, therefore also the number of votes for any given motion.
pub type MemberCount = u32;
/// The maximum number of members supported by the pallet. Used for weight estimation.
///
/// NOTE:
/// + Benchmarks will need to be re-run and weights adjusted if this changes.
/// + This pallet assumes that dependents keep to the limit without enforcing it.
pub const MAX_MEMBERS: MemberCount = 100;
pub trait WeightInfo {
fn set_members(m: u32, n: u32, p: u32, ) -> Weight;
fn execute(m: u32, b: u32, ) -> Weight;
fn propose_execute(m: u32, b: u32, ) -> Weight;
fn propose_proposed(m: u32, p: u32, b: u32, ) -> Weight;
fn execute(b: u32, m: u32, ) -> Weight;
fn propose_execute(b: u32, m: u32, ) -> Weight;
fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight;
fn vote(m: u32, ) -> Weight;
fn close_early_disapproved(m: u32, p: u32, b: u32, ) -> Weight;
fn close_early_approved(m: u32, p: u32, b: u32, ) -> Weight;
fn close_disapproved(m: u32, p: u32, b: u32, ) -> Weight;
fn close_approved(m: u32, p: u32, b: u32, ) -> Weight;
}
impl WeightInfo for () {
fn set_members(_m: u32, _n: u32, _p: u32, ) -> Weight { 1_000_000_000 }
fn execute(_m: u32, _b: u32, ) -> Weight { 1_000_000_000 }
fn propose_execute(_m: u32, _b: u32, ) -> Weight { 1_000_000_000 }
fn propose_proposed(_m: u32, _p: u32, _b: u32, ) -> Weight { 1_000_000_000 }
fn vote(_m: u32, ) -> Weight { 1_000_000_000 }
fn close_early_disapproved(_m: u32, _p: u32, _b: u32, ) -> Weight { 1_000_000_000 }
fn close_early_approved(_m: u32, _p: u32, _b: u32, ) -> Weight { 1_000_000_000 }
fn close_disapproved(_m: u32, _p: u32, _b: u32, ) -> Weight { 1_000_000_000 }
fn close_approved(_m: u32, _p: u32, _b: u32, ) -> Weight { 1_000_000_000 }
fn close_early_disapproved(m: u32, p: u32, ) -> Weight;
fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight;
fn close_disapproved(m: u32, p: u32, ) -> Weight;
fn close_approved(b: u32, m: u32, p: u32, ) -> Weight;
fn disapprove_proposal(p: u32, ) -> Weight;
}
pub trait Trait<I: Instance=DefaultInstance>: frame_system::Trait {
......@@ -117,7 +101,14 @@ pub trait Trait<I: Instance=DefaultInstance>: frame_system::Trait {
type MotionDuration: Get<Self::BlockNumber>;
/// Maximum number of proposals allowed to be active in parallel.
type MaxProposals: Get<u32>;
type MaxProposals: Get<ProposalIndex>;
/// The maximum number of members supported by the pallet. Used for weight estimation.
///
/// NOTE:
/// + Benchmarks will need to be re-run and weights adjusted if this changes.
/// + This pallet assumes that dependents keep to the limit without enforcing it.
type MaxMembers: Get<MemberCount>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
......@@ -233,131 +224,6 @@ decl_error! {
}
}
/// Functions for calcuating the weight of dispatchables.
mod weight_for {
use frame_support::{traits::Get, weights::Weight};
use super::{Trait, Instance};
/// Calculate the weight for `set_members`.
///
/// Based on benchmark:
/// 0 + M * 20.47 + N * 0.109 + P * 26.29 µs (min squares analysis)
///
/// Note: The complexity of `set_members` is quadratic (`O(MP + N)`), so the linear approximation
/// of the benchmark is not always permissible. It is here, though, because the linear approximation
/// covered the range of possible values and we estimate weight via the worst case (max paramter
/// values) before execution so we can be sure that we are only overestimating.
pub(crate) fn set_members<T: Trait<I>, I: Instance>(
old_count: Weight,
new_count: Weight,
proposals: Weight,
) -> Weight {
let db = T::DbWeight::get();
db.reads_writes(1, 1) // mutate `Members`
.saturating_add(db.writes(1)) // set `Prime`
.saturating_add(db.reads(1)) // read `Proposals`
.saturating_add(db.reads_writes(proposals, proposals)) // update votes (`Voting`)
.saturating_add(old_count.saturating_mul(21_000_000)) // M
.saturating_add(new_count.saturating_mul(110_000)) // N
.saturating_add(proposals.saturating_mul(27_000_000)) // P
}
/// Calculate the weight for `execute`.
///
/// Based on benchmark:
/// 22.62 + M * 0.115 + B * 0.003 µs (min squares analysis)
pub(crate) fn execute<T: Trait<I>, I: Instance>(
members: Weight,
proposal: Weight,
length: Weight,
) -> Weight {
T::DbWeight::get().reads(1) // read members for `is_member`
.saturating_add(23_000_000) // constant
.saturating_add(length.saturating_mul(4_000)) // B
.saturating_add(members.saturating_mul(120_000)) // M
.saturating_add(proposal) // P
}
/// Calculate the weight for `propose` if the proposal is executed straight away (`threshold < 2`).
///
/// Based on benchmark:
/// 28.12 + M * 0.218 + B * 0.003 µs (min squares analysis)
pub(crate) fn propose_execute<T: Trait<I>, I: Instance>(
members: Weight,
proposal: Weight,
length: Weight,
) -> Weight {
T::DbWeight::get().reads(2) // `is_member` + `contains_key`
.saturating_add(29_000_000) // constant
.saturating_add(length.saturating_mul(3_000)) // B
.saturating_add(members.saturating_mul(220_000)) // M
.saturating_add(proposal) // P1
}
/// Calculate the weight for `propose` if the proposal is put up for a vote (`threshold >= 2`).
///
/// Based on benchmark:
/// 49.75 + M * 0.105 + P2 0.502 + B * 0.006 µs (min squares analysis)
pub(crate) fn propose_proposed<T: Trait<I>, I: Instance>(
members: Weight,
proposals: Weight,
length: Weight,
) -> Weight {
T::DbWeight::get().reads(2) // `is_member` + `contains_key`
.saturating_add(T::DbWeight::get().reads_writes(2, 4)) // `proposal` insertion
.saturating_add(50_000_000) // constant
.saturating_add(length.saturating_mul(6_000)) // B
.saturating_add(members.saturating_mul(110_000)) // M
.saturating_add(proposals.saturating_mul(510_000)) // P2
}
/// Calculate the weight for `vote`.
///
/// Based on benchmark:
/// 24.03 + M * 0.349 + P * 0.119 + B * 0.003 µs (min squares analysis)
pub(crate) fn vote<T: Trait<I>, I: Instance>(
members: Weight,
) -> Weight {
T::DbWeight::get().reads(1) // read `Members`
.saturating_add(T::DbWeight::get().reads_writes(1, 1)) // mutate `Voting`
.saturating_add(30_000_000) // constant
.saturating_add(members.saturating_mul(500_000)) // M
}
/// Calculate the weight for `close`.
///
/// Based on benchmarks:
/// - early disapproved: 37.21 + M * 0.239 + P2 * 0.466 + B * 0.002 µs (min squares analysis)
/// - early approved: 50.82 + M * 0.211 + P2 * 0.478 + B * 0.008 µs (min squares analysis)
/// - disapproved: 51.08 + M * 0.224 + P2 * 0.475 + B * 0.003 µs (min squares analysis)
/// - approved: 65.95 + M * 0.226 + P2 * 0.487 + B * 0.005 µs (min squares analysis)
pub(crate) fn close<T: Trait<I>, I: Instance>(
members: Weight,
proposal_weight: Weight,
proposals: Weight,
length: Weight,
) -> Weight {
let db = T::DbWeight::get();
close_without_finalize::<T, I>(members, length)
.saturating_add(db.reads(1)) // `Prime`
.saturating_add(db.writes(1)) // `Proposals`
.saturating_add(db.writes(1)) // `Voting`
.saturating_add(proposal_weight) // P1
.saturating_add(proposals.saturating_mul(490_000)) // P2
}
/// Calculate the weight for `close` without the call to `approve/disapprove_proposal`.
pub(crate) fn close_without_finalize<T: Trait<I>, I: Instance>(
members: Weight,
length: Weight,
) -> Weight {
T::DbWeight::get().reads(3) // `Members`, `Voting`, `ProposalOf`
.saturating_add(66_000_000) // constant
.saturating_add(length.saturating_mul(8_000)) // B
.saturating_add(members.saturating_mul(250_000)) // M
}
}
/// Return the weight of a dispatch call result as an `Option`.
///
/// Will return the weight regardless of what the state of the result is.
......@@ -385,7 +251,7 @@ decl_module! {
///
/// Requires root origin.
///
/// NOTE: Does not enforce the expected `MAX_MEMBERS` limit on the amount of members, but
/// NOTE: Does not enforce the expected `MaxMembers` limit on the amount of members, but
/// the weight estimations rely on it to estimate dispatchable weight.
///
/// # <weight>
......@@ -401,10 +267,10 @@ decl_module! {
/// - 1 storage write (codec `O(1)`) for deleting the old `prime` and setting the new one
/// # </weight>
#[weight = (
weight_for::set_members::<T, I>(
(*old_count).into(), // M
new_members.len() as Weight, // N
T::MaxProposals::get().into(), // P
T::WeightInfo::set_members(
*old_count, // M
new_members.len() as u32, // N
T::MaxProposals::get() // P
),
DispatchClass::Operational
)]
......@@ -414,10 +280,10 @@ decl_module! {
old_count: MemberCount,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
if new_members.len() > MAX_MEMBERS as usize {
if new_members.len() > T::MaxMembers::get() as usize {
debug::error!(
"New members count exceeds maximum amount of members expected. (expected: {}, actual: {})",
MAX_MEMBERS,
T::MaxMembers::get(),
new_members.len()
);
}
......@@ -435,10 +301,10 @@ decl_module! {
<Self as ChangeMembers<T::AccountId>>::set_members_sorted(&new_members, &old);
Prime::<T, I>::set(prime);
Ok(Some(weight_for::set_members::<T, I>(
old.len() as Weight, // M
new_members.len() as Weight, // N
T::MaxProposals::get().into(), // P
Ok(Some(T::WeightInfo::set_members(
old.len() as u32, // M
new_members.len() as u32, // N
T::MaxProposals::get(), // P
)).into())
}
......@@ -453,11 +319,10 @@ decl_module! {
/// - 1 event
/// # </weight>
#[weight = (
weight_for::execute::<T, I>(
MAX_MEMBERS.into(),
proposal.get_dispatch_info().weight,
*length_bound as Weight,
),
T::WeightInfo::execute(
*length_bound, // B
T::MaxMembers::get(), // M
).saturating_add(proposal.get_dispatch_info().weight), // P
DispatchClass::Operational
)]
fn execute(origin,
......@@ -476,11 +341,12 @@ decl_module! {
RawEvent::MemberExecuted(proposal_hash, result.map(|_| ()).map_err(|e| e.error))
);
Ok(get_result_weight(result).map(|w| weight_for::execute::<T, I>(
members.len() as Weight,
w,
proposal_len as Weight
)).into())
Ok(get_result_weight(result).map(|w| {
T::WeightInfo::execute(
proposal_len as u32, // B
members.len() as u32, // M
).saturating_add(w) // P
}).into())
}
/// Add a new proposal to either be voted on or executed directly.
......@@ -512,16 +378,15 @@ decl_module! {
/// # </weight>
#[weight = (
if *threshold < 2 {
weight_for::propose_execute::<T, I>(
MAX_MEMBERS.into(), // M
proposal.get_dispatch_info().weight, // P1
*length_bound as Weight, // B
)
T::WeightInfo::propose_execute(
*length_bound, // B
T::MaxMembers::get(), // M
).saturating_add(proposal.get_dispatch_info().weight) // P1
} else {
weight_for::propose_proposed::<T, I>(
MAX_MEMBERS.into(), // M
T::MaxProposals::get().into(), // P2
*length_bound as Weight, // B
T::WeightInfo::propose_proposed(
*length_bound, // B
T::MaxMembers::get(), // M
T::MaxProposals::get(), // P2
)
},
DispatchClass::Operational
......@@ -547,11 +412,12 @@ decl_module! {
RawEvent::Executed(proposal_hash, result.map(|_| ()).map_err(|e| e.error))
);
Ok(get_result_weight(result).map(|w| weight_for::propose_execute::<T, I>(
members.len() as Weight, // M
w, // P1
proposal_len as Weight, // B
)).into())
Ok(get_result_weight(result).map(|w| {
T::WeightInfo::propose_execute(
proposal_len as u32, // B
members.len() as u32, // M
).saturating_add(w) // P1
}).into())
} else {
let active_proposals =
<Proposals<T, I>>::try_mutate(|proposals| -> Result<usize, DispatchError> {
......@@ -571,10 +437,10 @@ decl_module! {
Self::deposit_event(RawEvent::Proposed(who, index, proposal_hash, threshold));
Ok(Some(weight_for::propose_proposed::<T, I>(
members.len() as Weight, // M
active_proposals as Weight, // P2
proposal_len as Weight, // B
Ok(Some(T::WeightInfo::propose_proposed(
proposal_len as u32, // B
members.len() as u32, // M
active_proposals as u32, // P2
)).into())
}
}
......@@ -592,7 +458,7 @@ decl_module! {
/// - 1 event
/// # </weight>
#[weight = (
weight_for::vote::<T, I>(MAX_MEMBERS.into()),
T::WeightInfo::vote(T::MaxMembers::get()),
DispatchClass::Operational
)]
fn vote(origin,
......@@ -636,7 +502,7 @@ decl_module! {
Voting::<T, I>::insert(&proposal, voting);
Ok(Some(weight_for::vote::<T, I>(members.len() as Weight)).into())
Ok(Some(T::WeightInfo::vote(members.len() as u32)).into())
}
/// Close a vote that is either approved, disapproved or whose voting period has ended.
......@@ -667,12 +533,17 @@ decl_module! {
/// - up to 3 events
/// # </weight>
#[weight = (
weight_for::close::<T, I>(
MAX_MEMBERS.into(), // `M`
*proposal_weight_bound, // `P1`
T::MaxProposals::get().into(), // `P2`
*length_bound as Weight, // B
),
{
let b = *length_bound;
let m = T::MaxMembers::get();
let p1 = *proposal_weight_bound;
let p2 = T::MaxProposals::get();
T::WeightInfo::close_early_approved(b, m, p2)
.max(T::WeightInfo::close_early_disapproved(m, p2))
.max(T::WeightInfo::close_approved(b, m, p2))
.max(T::WeightInfo::close_disapproved(m, p2))
.saturating_add(p1)
},
DispatchClass::Operational
)]
fn close(origin,
......@@ -699,17 +570,17 @@ decl_module! {
proposal_weight_bound
)?;
Self::deposit_event(RawEvent::Closed(proposal_hash, yes_votes, no_votes));
let approve_weight = Self::do_approve_proposal(seats, voting, proposal_hash, proposal);
let (proposal_weight, proposal_count) =
Self::do_approve_proposal(seats, voting, proposal_hash, proposal);
return Ok(Some(
weight_for::close_without_finalize::<T, I>(seats.into(), len as Weight)
.saturating_add(approve_weight)
T::WeightInfo::close_early_approved(len as u32, seats, proposal_count)
.saturating_add(proposal_weight)
).into());
} else if disapproved {
Self::deposit_event(RawEvent::Closed(proposal_hash, yes_votes, no_votes));
let disapprove_weight = Self::do_disapprove_proposal(proposal_hash);
let proposal_count = Self::do_disapprove_proposal(proposal_hash);
return Ok(Some(
weight_for::close_without_finalize::<T, I>(seats.into(), 0)
.saturating_add(disapprove_weight)
T::WeightInfo::close_early_disapproved(seats, proposal_count)
).into());
}
......@@ -733,19 +604,17 @@ decl_module! {
proposal_weight_bound
)?;
Self::deposit_event(RawEvent::Closed(proposal_hash, yes_votes, no_votes));
let approve_weight = Self::do_approve_proposal(seats, voting, proposal_hash, proposal);
let (proposal_weight, proposal_count) =
Self::do_approve_proposal(seats, voting, proposal_hash, proposal);
return Ok(Some(
weight_for::close_without_finalize::<T, I>(seats.into(), len as Weight)
.saturating_add(T::DbWeight::get().reads(1)) // read `Prime`
.saturating_add(approve_weight)
T::WeightInfo::close_approved(len as u32, seats, proposal_count)
.saturating_add(proposal_weight)
).into());
} else {
Self::deposit_event(RawEvent::Closed(proposal_hash, yes_votes, no_votes));
let disapprove_weight = Self::do_disapprove_proposal(proposal_hash);
let proposal_count = Self::do_disapprove_proposal(proposal_hash);
return Ok(Some(
weight_for::close_without_finalize::<T, I>(seats.into(), 0)
.saturating_add(T::DbWeight::get().reads(1)) // read `Prime`
.saturating_add(disapprove_weight)
T::WeightInfo::close_disapproved(seats, proposal_count)
).into());
}
}
......@@ -759,18 +628,15 @@ decl_module! {
///
/// # <weight>
/// Complexity: O(P) where P is the number of max proposals
/// Base Weight: .49 * P
/// DB Weight:
/// * Reads: Proposals
/// * Writes: Voting, Proposals, ProposalOf
/// # </weight>
#[weight = T::DbWeight::get().reads_writes(1, 3) // `Voting`, `Proposals`, `ProposalOf`
.saturating_add(490_000 * Weight::from(T::MaxProposals::get())) // P2
]
#[weight = T::WeightInfo::disapprove_proposal(T::MaxProposals::get())]
fn disapprove_proposal(origin, proposal_hash: T::Hash) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let actual_weight = Self::do_disapprove_proposal(proposal_hash);
Ok(Some(actual_weight).into())
let proposal_count = Self::do_disapprove_proposal(proposal_hash);
Ok(Some(T::WeightInfo::disapprove_proposal(proposal_count)).into())
}
}
}
......@@ -822,8 +688,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
voting: Votes<T::AccountId, T::BlockNumber>,
proposal_hash: T::Hash,
proposal: <T as Trait<I>>::Proposal,
) -> Weight {
let mut weight: Weight = 0;
) -> (Weight, u32) {
Self::deposit_event(RawEvent::Approved(proposal_hash));
let dispatch_weight = proposal.get_dispatch_info().weight;
......@@ -832,23 +697,21 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
Self::deposit_event(
RawEvent::Executed(proposal_hash, result.map(|_| ()).map_err(|e| e.error))
);
weight = weight.saturating_add(
// default to the dispatch info weight for safety
get_result_weight(result).unwrap_or(dispatch_weight) // P1
);
// default to the dispatch info weight for safety
let proposal_weight = get_result_weight(result).unwrap_or(dispatch_weight); // P1
let remove_proposal_weight = Self::remove_proposal(proposal_hash);
weight.saturating_add(remove_proposal_weight)
let proposal_count = Self::remove_proposal(proposal_hash);
(proposal_weight, proposal_count)
}
fn do_disapprove_proposal(proposal_hash: T::Hash) -> Weight {
fn do_disapprove_proposal(proposal_hash: T::Hash) -> u32 {
// disapproved
Self::deposit_event(RawEvent::Disapproved(proposal_hash));
Self::remove_proposal(proposal_hash)
}
// Removes a proposal from the pallet, cleaning up votes and the vector of proposals.
fn remove_proposal(proposal_hash: T::Hash) -> Weight {
fn remove_proposal(proposal_hash: T::Hash) -> u32 {
// remove proposal and vote
ProposalOf::<T, I>::remove(&proposal_hash);
Voting::<T, I>::remove(&proposal_hash);
......@@ -856,15 +719,14 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
proposals.retain(|h| h != &proposal_hash);
proposals.len() + 1 // calculate weight based on original length
});
T::DbWeight::get().reads_writes(1, 3) // `Voting`, `Proposals`, `ProposalOf`
.saturating_add(490_000 * num_proposals as Weight) // P2
num_proposals as u32
}
}
impl<T: Trait<I>, I: Instance> ChangeMembers<T::AccountId> for Module<T, I> {
/// Update the members of the collective. Votes are updated and the prime is reset.
///
/// NOTE: Does not enforce the expected `MAX_MEMBERS` limit on the amount of members, but
/// NOTE: Does not enforce the expected `MaxMembers` limit on the amount of members, but
/// the weight estimations rely on it to estimate dispatchable weight.
///
/// # <weight>
......@@ -884,10 +746,10 @@ impl<T: Trait<I>, I: Instance> ChangeMembers<T::AccountId> for Module<T, I> {
outgoing: &[T::AccountId],
new: &[T::AccountId],
) {
if new.len() > MAX_MEMBERS as usize {
if new.len() > T::MaxMembers::get() as usize {
debug::error!(
"New members count exceeds maximum amount of members expected. (expected: {}, actual: {})",
MAX_MEMBERS,
T::MaxMembers::get(),
new.len()
);
}
......@@ -1047,6 +909,7 @@ mod tests {
pub const AvailableBlockRatio: Perbill = Perbill::one();
pub const MotionDuration: u64 = 3;
pub const MaxProposals: u32 = 100;
pub const MaxMembers: u32 = 100;
}
impl frame_system::Trait for Test {
type BaseCallFilter = ();
......@@ -1081,6 +944,7 @@ mod tests {
type Event = Event;
type MotionDuration = MotionDuration;
type MaxProposals = MaxProposals;
type MaxMembers = MaxMembers;
type WeightInfo = ();
}
impl Trait for Test {
......@@ -1089,6 +953,7 @@ mod tests {
type Event = Event;
type MotionDuration = MotionDuration;
type MaxProposals = MaxProposals;
type MaxMembers = MaxMembers;
type WeightInfo = ();
}
......@@ -1164,7 +1029,7 @@ mod tests {
#[test]
fn proposal_weight_limit_works_on_approve() {
new_test_ext().execute_with(|| {
let proposal = Call::Collective(crate::Call::set_members(vec![1, 2, 3], None, MAX_MEMBERS));
let proposal = Call::Collective(crate::Call::set_members(vec![1, 2, 3], None, MaxMembers::get()));
let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32);
let proposal_weight = proposal.get_dispatch_info().weight;
let hash = BlakeTwo256::hash_of(&proposal);
......@@ -1184,7 +1049,7 @@ mod tests {
#[test]
fn proposal_weight_limit_ignored_on_disapprove() {
new_test_ext().execute_with(|| {
let proposal = Call::Collective(crate::Call::set_members(vec![1, 2, 3], None, MAX_MEMBERS));
let proposal = Call::Collective(crate::Call::set_members(vec![1, 2, 3], None, MaxMembers::get()));
let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32);
let proposal_weight = proposal.get_dispatch_info().weight;
let hash = BlakeTwo256::hash_of(&proposal);
......@@ -1205,7 +1070,7 @@ mod tests {
let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32);
let proposal_weight = proposal.get_dispatch_info().weight;
let hash = BlakeTwo256::hash_of(&proposal);
assert_ok!(Collective::set_members(Origin::root(), vec![1, 2, 3], Some(3), MAX_MEMBERS));
assert_ok!(Collective::set_members(Origin::root(), vec![1, 2, 3], Some(3), MaxMembers::get()));
assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()), proposal_len));
assert_ok!(Collective::vote(Origin::signed(2), hash.clone(), 0, true));
......@@ -1230,7 +1095,7 @@ mod tests {
let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32);
let proposal_weight = proposal.get_dispatch_info().weight;
let hash = BlakeTwo256::hash_of(&proposal);
assert_ok!(Collective::set_members(Origin::root(), vec![1, 2, 3], Some(1), MAX_MEMBERS));
assert_ok!(Collective::set_members(Origin::root(), vec![1, 2, 3], Some(1), MaxMembers::get()));
assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()), proposal_len));
assert_ok!(Collective::vote(Origin::signed(2), hash.clone(), 0, true));
......@@ -1298,7 +1163,7 @@ mod tests {
Collective::voting(&hash),
Some(Votes { index: 0, threshold: 3, ayes: vec![1, 2], nays: vec![], end })
);
assert_ok!(Collective::set_members(Origin::root(), vec![2, 3, 4], None, MAX_MEMBERS));
assert_ok!(Collective::set_members(Origin::root(), vec![2, 3, 4], None, MaxMembers::get()));
assert_eq!(
Collective::voting(&hash),
Some(Votes { index: 0, threshold: 3, ayes: vec![2], nays: vec![], end })
......@@ -1313,7 +1178,7 @@ mod tests {
Collective::voting(&hash),
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![3], end })
);
assert_ok!(Collective::set_members(Origin::root(), vec![2, 4], None, MAX_MEMBERS));
assert_ok!(Collective::set_members(Origin::root(), vec![2, 4], None, MaxMembers::get()));
assert_eq!(
Collective::voting(&hash),
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![], end })
......@@ -1371,7 +1236,7 @@ mod tests {
#[test]
fn correct_validate_and_get_proposal() {
new_test_ext().execute_with(|| {
let proposal = Call::Collective(crate::Call::set_members(vec![1, 2, 3], None, MAX_MEMBERS));
let proposal = Call::Collective(crate::Call::set_members(vec![1, 2, 3], None, MaxMembers::get()));
let length = proposal.encode().len() as u32;
assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()), length));
......
......@@ -87,7 +87,7 @@ use codec::{Encode, Decode};
use sp_std::prelude::*;
use sp_runtime::{
DispatchError, RuntimeDebug, Perbill,
traits::{Zero, StaticLookup, Convert},
traits::{Zero, StaticLookup, Convert, Saturating},
};
use frame_support::{
decl_storage, decl_event, ensure, decl_module, decl_error,
......@@ -904,14 +904,20 @@ impl<T: Trait> Module<T> {
to_votes(Self::locked_stake_of(who))
};
let voters_and_votes = Voting::<T>::iter()
.map(|(voter, (stake, targets))| { (voter, to_votes(stake), targets) })
// used for prime election.
let voters_and_stakes = Voting::<T>::iter()
.map(|(voter, (stake, targets))| { (voter, stake, targets) })
.collect::<Vec<_>>();
// used for phragmen.
let voters_and_votes = voters_and_stakes.iter()
.cloned()
.map(|(voter, stake, targets)| { (voter, to_votes(stake), targets)} )
.collect::<Vec<_>>();
let maybe_phragmen_result = sp_npos_elections::seq_phragmen::<T::AccountId, Perbill>(
num_to_elect,
0,
candidates,
voters_and_votes.clone(),
voters_and_votes,
);
if let Some(ElectionResult { winners, assignments }) = maybe_phragmen_result {
......@@ -965,17 +971,26 @@ impl<T: Trait> Module<T> {
// save the members, sorted based on account id.
new_members.sort_by(|i, j| i.0.cmp(&j.0));
let mut prime_votes: Vec<_> = new_members.iter().map(|c| (&c.0, VoteWeight::zero())).collect();
for (_, stake, targets) in voters_and_votes.into_iter() {
for (votes, who) in targets.iter()
// Now we select a prime member using a [Borda count](https://en.wikipedia.org/wiki/Borda_count).
// We weigh everyone's vote for that new member by a multiplier based on the order
// of the votes. i.e. the first person a voter votes for gets a 16x multiplier,
// the next person gets a 15x multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16)
let mut prime_votes: Vec<_> = new_members.iter().map(|c| (&c.0, BalanceOf::<T>::zero())).collect();
for (_, stake, targets) in voters_and_stakes.into_iter() {
for (vote_multiplier, who) in targets.iter()
.enumerate()
.map(|(votes, who)| ((MAXIMUM_VOTE - votes) as u32, who))
.map(|(vote_position, who)| ((MAXIMUM_VOTE - vote_position) as u32, who))
{
if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) {
prime_votes[i].1 += stake * votes as VoteWeight;
prime_votes[i].1 = prime_votes[i].1.saturating_add(
stake.saturating_mul(vote_multiplier.into())
);
}
}
}
// We then select the new member with the highest weighted stake. In the case of
// a tie, the last person in the list with the tied score is selected. This is
// the person with the "highest" account id based on the sort above.
let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone());
// new_members_ids is sorted by account id.
......
......@@ -22,7 +22,7 @@ sp-core = { version = "2.0.0-rc6", default-features = false, path = "../../primi
sp-runtime = { version = "2.0.0-rc6", default-features = false, path = "../../primitives/runtime" }
sp-std = { version = "2.0.0-rc6", default-features = false, path = "../../primitives/std" }
sp-io = { version = "2.0.0-rc6", default-features = false, path = "../../primitives/io" }
primitive-types = { version = "0.7.0", default-features = false, features = ["rlp"] }
primitive-types = { version = "0.7.0", default-features = false, features = ["rlp", "byteorder"] }
rlp = { version = "0.4", default-features = false }
evm = { version = "0.17", default-features = false }
sha3 = { version = "0.8", default-features = false }
......
......@@ -376,7 +376,7 @@ mod weight_for {
pub fn report_equivocation<T: super::Trait>(validator_count: u32) -> Weight {
// we take the validator set count from the membership proof to
// calculate the weight but we set a floor of 100 validators.
let validator_count = validator_count.min(100) as u64;
let validator_count = validator_count.max(100) as u64;
// worst case we are considering is that the given offender
// is backed by 200 nominators
......
......@@ -842,3 +842,26 @@ fn always_schedules_a_change_on_new_session_when_stalled() {
assert_eq!(Grandpa::current_set_id(), 2);
});
}
#[test]
fn report_equivocation_has_valid_weight() {
// the weight depends on the size of the validator set,
// but there's a lower bound of 100 validators.
assert!(
(1..=100)
.map(weight_for::report_equivocation::<Test>)
.collect::<Vec<_>>()
.windows(2)
.all(|w| w[0] == w[1])
);
// after 100 validators the weight should keep increasing
// with every extra validator.
assert!(
(100..=1000)
.map(weight_for::report_equivocation::<Test>)
.collect::<Vec<_>>()
.windows(2)
.all(|w| w[0] < w[1])
);
}
......@@ -149,12 +149,12 @@ decl_module! {
ensure!(name.len() <= T::MaxLength::get(), Error::<T>::TooLong);
let deposit = if let Some((_, deposit)) = <NameOf<T>>::get(&sender) {
Self::deposit_event(RawEvent::NameSet(sender.clone()));
Self::deposit_event(RawEvent::NameChanged(sender.clone()));
deposit
} else {
let deposit = T::ReservationFee::get();
T::Currency::reserve(&sender, deposit.clone())?;
Self::deposit_event(RawEvent::NameChanged(sender.clone()));
Self::deposit_event(RawEvent::NameSet(sender.clone()));
deposit
};
......
......@@ -125,7 +125,7 @@ fn create_offender<T: Trait>(n: u32, nominators: u32) -> Result<Offender<T>, &'s
RawOrigin::Signed(nominator_stash.clone()).into(),
nominator_controller_lookup.clone(),
amount.clone(),
reward_destination,
reward_destination.clone(),
)?;
let selected_validators: Vec<LookupSourceOf<T>> = vec![controller_lookup.clone()];
......
......@@ -425,16 +425,18 @@ pub enum StakerStatus<AccountId> {
/// A destination account for payment.
#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug)]
pub enum RewardDestination {
pub enum RewardDestination<AccountId> {
/// Pay into the stash account, increasing the amount at stake accordingly.
Staked,
/// Pay into the stash account, not increasing the amount at stake.
Stash,
/// Pay into the controller account.
Controller,
/// Pay into a specified account.
Account(AccountId),
}
impl Default for RewardDestination {
impl<AccountId> Default for RewardDestination<AccountId> {
fn default() -> Self {
RewardDestination::Staked
}
......@@ -1049,7 +1051,7 @@ decl_storage! {
=> Option<StakingLedger<T::AccountId, BalanceOf<T>>>;
/// Where the reward payment should be made. Keyed by stash.
pub Payee get(fn payee): map hasher(twox_64_concat) T::AccountId => RewardDestination;
pub Payee get(fn payee): map hasher(twox_64_concat) T::AccountId => RewardDestination<T::AccountId>;
/// The map from (wannabe) validator stash key to the preferences of that validator.
pub Validators get(fn validators):
......@@ -1496,7 +1498,7 @@ decl_module! {
pub fn bond(origin,
controller: <T::Lookup as StaticLookup>::Source,
#[compact] value: BalanceOf<T>,
payee: RewardDestination,
payee: RewardDestination<T::AccountId>,
) {
let stash = ensure_signed(origin)?;
......@@ -1830,7 +1832,7 @@ decl_module! {
/// - Write: Payee
/// # </weight>
#[weight = 11 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(1, 1)]
fn set_payee(origin, payee: RewardDestination) {
fn set_payee(origin, payee: RewardDestination<T::AccountId>) {
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
let stash = &ledger.stash;
......@@ -2489,6 +2491,9 @@ impl<T: Trait> Module<T> {
Self::update_ledger(&controller, &l);
r
}),
RewardDestination::Account(dest_account) => {
Some(T::Currency::deposit_creating(&dest_account, amount))
}
}
}
......
......@@ -4359,7 +4359,7 @@ fn test_payout_stakers() {
// We also test that `payout_extra_nominators` works.
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
let balance = 1000;
// Create three validators:
// Create a validator:
bond_validator(11, 10, balance); // Default(64)
// Create nominators, targeting stash of validators
......@@ -4597,15 +4597,12 @@ fn on_initialize_weight_is_correct() {
});
}
#[test]
fn payout_creates_controller() {
// Here we will test validator can set `max_nominators_payout` and it works.
// We also test that `payout_extra_nominators` works.
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
let balance = 1000;
// Create three validators:
bond_validator(11, 10, balance); // Default(64)
// Create a validator:
bond_validator(11, 10, balance);
// Create a stash/controller pair
bond_nominator(1234, 1337, 100, vec![11]);
......@@ -4626,3 +4623,32 @@ fn payout_creates_controller() {
assert!(Balances::free_balance(1337) > 0);
})
}
#[test]
fn payout_to_any_account_works() {
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
let balance = 1000;
// Create a validator:
bond_validator(11, 10, balance); // Default(64)
// Create a stash/controller pair
bond_nominator(1234, 1337, 100, vec![11]);
// Update payout location
assert_ok!(Staking::set_payee(Origin::signed(1337), RewardDestination::Account(42)));
// Reward Destination account doesn't exist
assert_eq!(Balances::free_balance(42), 0);
mock::start_era(1);
Staking::reward_by_ids(vec![(11, 1)]);
// Compute total payout now for whole duration as other parameter won't change
let total_payout_0 = current_total_payout_for_duration(3 * 1000);
assert!(total_payout_0 > 100); // Test is meaningful if reward something
mock::start_era(2);
assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 1));
// Payment is successful
assert!(Balances::free_balance(42) > 0);
})
}
......@@ -26,7 +26,7 @@ hash-db = { version = "0.15.2", default-features = false }
hash256-std-hasher = { version = "0.15.2", default-features = false }
base58 = { version = "0.1.0", optional = true }
rand = { version = "0.7.3", optional = true, features = ["small_rng"] }
substrate-bip39 = { version = "0.4.1", optional = true }
substrate-bip39 = { version = "0.4.2", optional = true }
tiny-bip39 = { version = "0.7", optional = true }
regex = { version = "1.3.1", optional = true }
num-traits = { version = "0.2.8", default-features = false }
......
......@@ -12,7 +12,7 @@ description = "I/O for Substrate runtimes"
futures = "0.3.4"
futures-core = "0.3.4"
lazy_static = "1.4.0"
prometheus = { version = "0.9.0", default-features = false }
prometheus = { version = "0.10.0", default-features = false }
futures-timer = "3.0.2"
[features]
......
......@@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
log = "0.4.8"
prometheus = { version = "0.9", default-features = false }
prometheus = { version = "0.10.0", default-features = false }
futures-util = { version = "0.3.1", default-features = false, features = ["io"] }
derive_more = "0.99"
......