Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
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