Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Update treasury weights. #5723

Merged
merged 16 commits into from
Apr 28, 2020
Merged
10 changes: 10 additions & 0 deletions frame/elections-phragmen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ use frame_support::{
traits::{
Currency, Get, LockableCurrency, LockIdentifier, ReservableCurrency, WithdrawReasons,
ChangeMembers, OnUnbalanced, WithdrawReason, Contains, BalanceStatus, InitializeMembers,
ContainsLengthBound,
}
};
use sp_phragmen::{build_support_map, ExtendedBalance, VoteWeight, PhragmenResult};
Expand Down Expand Up @@ -880,6 +881,15 @@ impl<T: Trait> Contains<T::AccountId> for Module<T> {
}
}

impl<T: Trait> ContainsLengthBound for Module<T> {
fn min_len() -> usize { 0 }

/// Implementation uses a parameter type so calling is cost-free.
fn max_len() -> usize {
Self::desired_members() as usize
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
12 changes: 9 additions & 3 deletions frame/support/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,7 @@ impl<T: Default> Get<T> for () {
fn get() -> T { T::default() }
}

/// A trait for querying whether a type can be said to statically "contain" a value. Similar
/// in nature to `Get`, except it is designed to be lazy rather than active (you can't ask it to
/// enumerate all values that it contains) and work for multiple values rather than just one.
/// A trait for querying whether a type can be said to "contain" a value.
apopiak marked this conversation as resolved.
Show resolved Hide resolved
pub trait Contains<T: Ord> {
/// Return `true` if this "contains" the given value `t`.
fn contains(t: &T) -> bool { Self::sorted_members().binary_search(t).is_ok() }
Expand All @@ -211,6 +209,14 @@ pub trait Contains<T: Ord> {
fn add(_t: &T) { unimplemented!() }
}

/// A trait for querying bound for the length of an implementation of `Contains`
pub trait ContainsLengthBound {
/// Minimum number of elements contained
fn min_len() -> usize;
/// Maximum number of elements contained
fn max_len() -> usize;
}

/// Determiner to say whether a given account is unused.
pub trait IsDeadAccount<AccountId> {
/// Is the given account dead?
Expand Down
119 changes: 76 additions & 43 deletions frame/treasury/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ use sp_runtime::{Permill, ModuleId, Percent, RuntimeDebug, traits::{
Zero, StaticLookup, AccountIdConversion, Saturating, Hash, BadOrigin
}};
use frame_support::weights::{Weight, DispatchClass};
use frame_support::traits::{Contains, EnsureOrigin};
use frame_support::traits::{Contains, ContainsLengthBound, EnsureOrigin};
use codec::{Encode, Decode};
use frame_system::{self as system, ensure_signed, ensure_root};

Expand All @@ -124,7 +124,9 @@ pub trait Trait: frame_system::Trait {
type RejectOrigin: EnsureOrigin<Self::Origin>;

/// Origin from which tippers must come.
type Tippers: Contains<Self::AccountId>;
///
/// `ContainsLengthBound::max_len` must be cost free (i.e. no storage read or heavy operation).
type Tippers: Contains<Self::AccountId> + ContainsLengthBound;

/// The period for which a tip remains open after is has achieved threshold tippers.
type TipCountdown: Get<Self::BlockNumber>;
Expand Down Expand Up @@ -326,11 +328,11 @@ decl_module! {
/// proposal is awarded.
///
/// # <weight>
/// - O(1).
/// - Limited storage reads.
/// - One DB change, one extra DB entry.
/// - Complexity: O(1)
/// - DbReads: `ProposalCount`, `origin account`
/// - DbWrites: `ProposalCount`, `Proposals`, `origin account`
/// # </weight>
#[weight = 500_000_000]
#[weight = 120_000_000 + T::DbWeight::get().reads_writes(1, 2)]
fn propose_spend(
origin,
#[compact] value: BalanceOf<T>,
Expand All @@ -353,11 +355,11 @@ decl_module! {
/// Reject a proposed spend. The original deposit will be slashed.
///
/// # <weight>
/// - O(1).
/// - Limited storage reads.
/// - One DB clear.
/// - Complexity: O(1)
/// - DbReads: `Proposals`, `rejected proposer account`
/// - DbWrites: `Proposals`, `rejected proposer account`
/// # </weight>
#[weight = (100_000_000, DispatchClass::Operational)]
#[weight = (130_000_000 + T::DbWeight::get().reads_writes(2, 2), DispatchClass::Operational)]
fn reject_proposal(origin, #[compact] proposal_id: ProposalIndex) {
T::RejectOrigin::try_origin(origin)
.map(|_| ())
Expand All @@ -375,11 +377,11 @@ decl_module! {
/// and the original deposit will be returned.
///
/// # <weight>
/// - O(1).
/// - Limited storage reads.
/// - One DB change.
/// - Complexity: O(1).
/// - DbReads: `Proposals`, `Approvals`
/// - DbWrite: `Approvals`
/// # </weight>
#[weight = (100_000_000, DispatchClass::Operational)]
#[weight = (34_000_000 + T::DbWeight::get().reads_writes(2, 1), DispatchClass::Operational)]
fn approve_proposal(origin, #[compact] proposal_id: ProposalIndex) {
T::ApproveOrigin::try_origin(origin)
.map(|_| ())
Expand All @@ -403,12 +405,12 @@ decl_module! {
/// Emits `NewTip` if successful.
///
/// # <weight>
/// - `O(R)` where `R` length of `reason`.
/// - One balance operation.
/// - One storage mutation (codec `O(R)`).
/// - One event.
/// - Complexity: `O(R)` where `R` length of `reason`.
/// - encoding and hashing of 'reason'
/// - DbReads: `Reasons`, `Tips`, `who account data`
/// - DbWrites: `Tips`, `who account data`
/// # </weight>
#[weight = 100_000_000]
#[weight = 140_000_000 + 4_000 * reason.len() as Weight + T::DbWeight::get().reads_writes(3, 2)]
fn report_awesome(origin, reason: Vec<u8>, who: T::AccountId) {
let finder = ensure_signed(origin)?;

Expand Down Expand Up @@ -445,12 +447,12 @@ decl_module! {
/// Emits `TipRetracted` if successful.
///
/// # <weight>
/// - `O(T)`
/// - One balance operation.
/// - Two storage removals (one read, codec `O(T)`).
/// - One event.
/// - Complexity: `O(1)`
/// - Depends on the length of `T::Hash` which is fixed.
/// - DbReads: `Tips`, `origin account`
/// - DbWrites: `Reasons`, `Tips`, `origin account`
/// # </weight>
#[weight = 50_000_000]
#[weight = 120_000_000 + T::DbWeight::get().reads_writes(1, 2)]
fn retract_tip(origin, hash: T::Hash) {
let who = ensure_signed(origin)?;
let tip = Tips::<T>::get(&hash).ok_or(Error::<T>::UnknownTip)?;
Expand All @@ -477,12 +479,18 @@ decl_module! {
/// Emits `NewTip` if successful.
///
/// # <weight>
/// - `O(R + T)` where `R` length of `reason`, `T` is the number of tippers. `T` is
/// naturally capped as a membership set, `R` is limited through transaction-size.
/// - Two storage insertions (codecs `O(R)`, `O(T)`), one read `O(1)`.
/// - One event.
/// - Complexity: `O(R + T)` where `R` length of `reason`, `T` is the number of tippers.
/// - `O(T)`: decoding `Tipper` vec of length `T`
/// `T` is charged as upper bound given by `ContainsLengthBound`.
/// The actual cost depends on the implementation of `T::Tippers`.
/// - `O(R)`: hashing and encoding of reason of length `R`
/// - DbReads: `Tippers`, `Reasons`
/// - DbWrites: `Reasons`, `Tips`
/// # </weight>
#[weight = 150_000_000]
#[weight = 110_000_000
+ 4_000 * reason.len() as Weight
+ 480_000 * T::Tippers::max_len() as Weight
+ T::DbWeight::get().reads_writes(2, 2)]
fn tip_new(origin, reason: Vec<u8>, who: T::AccountId, tip_value: BalanceOf<T>) {
let tipper = ensure_signed(origin)?;
ensure!(T::Tippers::contains(&tipper), BadOrigin);
Expand Down Expand Up @@ -512,11 +520,18 @@ decl_module! {
/// has started.
///
/// # <weight>
/// - `O(T)`
/// - One storage mutation (codec `O(T)`), one storage read `O(1)`.
/// - Up to one event.
/// - Complexity: `O(T)` where `T` is the number of tippers.
/// decoding `Tipper` vec of length `T`, insert tip and check closing,
/// `T` is charged as upper bound given by `ContainsLengthBound`.
/// The actual cost depends on the implementation of `T::Tippers`.
///
/// Actually weight could be lower as it depends on how many tips are in `OpenTip` but it
/// is weighted as if almost full i.e of length `T-1`.
/// - DbReads: `Tippers`, `Tips`
/// - DbWrites: `Tips`
/// # </weight>
#[weight = 50_000_000]
#[weight = 68_000_000 + 2_000_000 * T::Tippers::max_len() as Weight
+ T::DbWeight::get().reads_writes(2, 1)]
fn tip(origin, hash: T::Hash, tip_value: BalanceOf<T>) {
let tipper = ensure_signed(origin)?;
ensure!(T::Tippers::contains(&tipper), BadOrigin);
Expand All @@ -538,11 +553,15 @@ decl_module! {
/// as the hash of the tuple of the original tip `reason` and the beneficiary account ID.
///
/// # <weight>
/// - `O(T)`
/// - One storage retrieval (codec `O(T)`) and two removals.
/// - Up to three balance operations.
/// - Complexity: `O(T)` where `T` is the number of tippers.
/// decoding `Tipper` vec of length `T`.
/// `T` is charged as upper bound given by `ContainsLengthBound`.
/// The actual cost depends on the implementation of `T::Tippers`.
/// - DbReads: `Tips`, `Tippers`, `tip finder`
/// - DbWrites: `Reasons`, `Tips`, `Tippers`, `tip finder`
/// # </weight>
#[weight = 50_000_000]
#[weight = 220_000_000 + 1_100_000 * T::Tippers::max_len() as Weight
+ T::DbWeight::get().reads_writes(3, 3)]
fn close_tip(origin, hash: T::Hash) {
ensure_signed(origin)?;

Expand All @@ -555,13 +574,23 @@ decl_module! {
Self::payout_tip(hash, tip);
}

/// # <weight>
/// - Complexity: `O(A)` where `A` is the number of approvals
/// - Db reads and writes: `Approvals`, `pot account data`
/// - Db reads and writes per approval:
/// `Proposals`, `proposer account data`, `beneficiary account data`
/// - The weight is overestimated if some approvals got missed.
/// # </weight>
fn on_initialize(n: T::BlockNumber) -> Weight {
// Check to see if we should spend some funds!
if (n % T::SpendPeriod::get()).is_zero() {
Self::spend_funds();
}
let approvals_len = Self::spend_funds();

0
270_000_000 * approvals_len
+ T::DbWeight::get().reads_writes(2 + approvals_len * 3, 2 + approvals_len * 3)
} else {
0
}
}
}
}
Expand Down Expand Up @@ -653,14 +682,15 @@ impl<T: Trait> Module<T> {
Self::deposit_event(RawEvent::TipClosed(hash, tip.who, payout));
}

// Spend some money!
fn spend_funds() {
/// Spend some money! returns number of approvals before spend.
fn spend_funds() -> u64 {
let mut budget_remaining = Self::pot();
Self::deposit_event(RawEvent::Spending(budget_remaining));

let mut missed_any = false;
let mut imbalance = <PositiveImbalanceOf<T>>::zero();
Approvals::mutate(|v| {
let prior_approvals_len = Approvals::mutate(|v| {
let prior_approvals_len = v.len() as u64;
v.retain(|&index| {
// Should always be true, but shouldn't panic if false or we're screwed.
if let Some(p) = Self::proposals(index) {
Expand All @@ -684,6 +714,7 @@ impl<T: Trait> Module<T> {
false
}
});
prior_approvals_len
});

if !missed_any {
Expand All @@ -710,6 +741,8 @@ impl<T: Trait> Module<T> {
}

Self::deposit_event(RawEvent::Rollover(budget_remaining));

prior_approvals_len
}

/// Return the amount of money in the pot.
Expand Down
6 changes: 6 additions & 0 deletions frame/treasury/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ impl Contains<u64> for TenToFourteen {
})
}
}
impl ContainsLengthBound for TenToFourteen {
fn max_len() -> usize {
TEN_TO_FOURTEEN.with(|v| v.borrow().len())
}
fn min_len() -> usize { 0 }
}
parameter_types! {
pub const ProposalBond: Permill = Permill::from_percent(5);
pub const ProposalBondMinimum: u64 = 1;
Expand Down